pgcrypto 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/CHANGES +4 -0
- data/LICENSE +20 -0
- data/README.markdown +95 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/lib/pgcrypto.rb +55 -0
- data/lib/pgcrypto/active_record.rb +107 -0
- data/lib/pgcrypto/arel.rb +22 -0
- data/lib/pgcrypto/key.rb +55 -0
- data/lib/pgcrypto/table_manager.rb +18 -0
- metadata +59 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/CHANGES
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) Phillip Sasser
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
PGCrypto for ActiveRecord::Base
|
2
|
+
===
|
3
|
+
|
4
|
+
**PGCrypto** adds seamless column-level encryption to your ActiveRecord::Base subclasses. It's literally *one giant hack,*
|
5
|
+
so I make no promises as to its efficacy in the real world beyond my tiny, Rails-3.2-based utopia.
|
6
|
+
|
7
|
+
Installation
|
8
|
+
-
|
9
|
+
|
10
|
+
You need to have PGCrypto installed before this guy will work. [LMGTFY](http://lmgtfy.com/?q=how+to+install+pgcrypto).
|
11
|
+
|
12
|
+
1. Add this to your Gemfile:
|
13
|
+
|
14
|
+
gem "pgcrypto"
|
15
|
+
|
16
|
+
2. Do this now:
|
17
|
+
|
18
|
+
bundle
|
19
|
+
|
20
|
+
3. Now point it to your public and private GPG keys:
|
21
|
+
|
22
|
+
PGCrypto.keys[:private] = {:path => "~/.keys/private.key"}
|
23
|
+
PGCrypto.keys[:public] = {:path => "~/.keys/public.key"}
|
24
|
+
|
25
|
+
4. PGCrypto columns are named `attribute_encrypted` and in the `binary` format, so do something like this:
|
26
|
+
|
27
|
+
add_column :users, :social\_security\_number\_encrypted, :binary
|
28
|
+
|
29
|
+
5. Tell the User class to encrypt and decrypt the `social_security_number` attribute on the fly:
|
30
|
+
|
31
|
+
class User < ActiveRecord::Base
|
32
|
+
# ... all kinds of neat stuff ...
|
33
|
+
|
34
|
+
pgcrypto :social_security_number
|
35
|
+
|
36
|
+
# ... some other fun stuff
|
37
|
+
end
|
38
|
+
|
39
|
+
6. Profit
|
40
|
+
|
41
|
+
User.create!(:social_security_number => "466-99-1234") #=> #<User with stuff>
|
42
|
+
User.last.social_security_number #=> "466-99-1234"
|
43
|
+
|
44
|
+
BAM. It looks innocuous on your end, but on the back end that beast is storing the social security number in
|
45
|
+
a GPG-encrypted column that can only be decrypted with your secure key.
|
46
|
+
|
47
|
+
Keys
|
48
|
+
-
|
49
|
+
|
50
|
+
You can tell PGCrypto about your keys in a number of fun ways. The most straightforward is to assign the actual
|
51
|
+
content of the key manually:
|
52
|
+
|
53
|
+
PGCrypto.keys[:private] = "-----BEGIN PGP PRIVATE KEY BLOCK----- ..."
|
54
|
+
|
55
|
+
You can also give it more specific stuff:
|
56
|
+
|
57
|
+
PGCrypto.keys[:private] = {:path => ".private.key", :armored => true, :password => "myKeyPASSwhichizneededBRO"}
|
58
|
+
|
59
|
+
This is especially important if you password protect your private key files (and you SHOULD, for the record)!
|
60
|
+
|
61
|
+
You can also specify different keys for different purposes:
|
62
|
+
|
63
|
+
PGCrypto.keys[:user_public] = {:path => '.user_public.key'}
|
64
|
+
PGCrypto.keys[:user_private] = {:path => '.user_private.key'}
|
65
|
+
|
66
|
+
If you do that, just tell PGCrypto which keys to use on which columns, using an optional hash on the end of the `pgcrypto` call:
|
67
|
+
|
68
|
+
class User < ActiveRecord::Base
|
69
|
+
pgcrypto :social_security_number, :private_key => :user_private, :public_key => :user_public
|
70
|
+
end
|
71
|
+
|
72
|
+
FINALLY, if you want to bundle your public key with your application, PGCrypto will automatically load Rails.root/.pgcrypto,
|
73
|
+
so feel free to put your public key in there. I recommend deploy-time passing of your private key and password, to ensure it
|
74
|
+
doesn't wind up in any long-term storage on the server:
|
75
|
+
|
76
|
+
PGCrypto.keys[:private] = {:value => ENV['PRIVATE_KEY'], :password => ENV['PRIVATE_KEY_PASSWORD']}
|
77
|
+
|
78
|
+
Warranty
|
79
|
+
-
|
80
|
+
|
81
|
+
As I mentioned before, this library is one HUGE hack. When you're using something like this alongside data that needs to be
|
82
|
+
well protected, this is just scratching the surface. This will make it easy to follow the basics of asymmetric, GPG-based,
|
83
|
+
column-level encryption in PostgreSQL but that's about it.
|
84
|
+
|
85
|
+
As such, the author and Delightful Widgets Inc. offer ABSOLUTELY NO GODDAMN WARRANTY. As I mentioned, this works great in our
|
86
|
+
Rails 3.2 world, but YMMV if your version of Arel or ActiveRecord are ahead or behind ours. Sorry, folks.
|
87
|
+
|
88
|
+
WTF NO TESTS?!!
|
89
|
+
-
|
90
|
+
|
91
|
+
Nope. We built this inside of a production application, and used its test suite to debug everything. Since this is really just
|
92
|
+
a preview release, I haven't written a suite for it yet. Sorry.
|
93
|
+
|
94
|
+
Authored by Flip Sasser
|
95
|
+
Copyright (C) 2012 Delightful Widgets, Inc.
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
task :default => :spec
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'spec/rake/spectask'
|
7
|
+
|
8
|
+
desc "Run all examples"
|
9
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
10
|
+
t.spec_files = FileList['spec/**/*.rb']
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run all examples with RCov"
|
14
|
+
Spec::Rake::SpecTask.new('spec:rcov') do |t|
|
15
|
+
t.spec_files = FileList['spec/**/*.rb']
|
16
|
+
t.rcov = true
|
17
|
+
t.rcov_opts = ['--exclude', 'spec,gem']
|
18
|
+
end
|
19
|
+
rescue LoadError
|
20
|
+
puts "Could not load Rspec. To run tests, use `gem install rspec`"
|
21
|
+
end
|
22
|
+
|
23
|
+
begin
|
24
|
+
require 'jeweler'
|
25
|
+
Jeweler::Tasks.new do |gemspec|
|
26
|
+
gemspec.name = "pgcrypto"
|
27
|
+
gemspec.summary = "A transparent ActiveRecord::Base extension for encrypted columns"
|
28
|
+
gemspec.description = %{
|
29
|
+
PGCrypto is an ActiveRecord::Base extension that allows you to asymmetrically
|
30
|
+
encrypt PostgreSQL columns with as little trouble as possible. It's totally
|
31
|
+
freaking rad.
|
32
|
+
}
|
33
|
+
gemspec.email = "flip@x451.com"
|
34
|
+
gemspec.homepage = "http://github.com/Plinq/pgcrypto"
|
35
|
+
gemspec.authors = ["Flip Sasser"]
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/pgcrypto.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'pgcrypto/active_record'
|
2
|
+
require 'pgcrypto/arel'
|
3
|
+
require 'pgcrypto/key'
|
4
|
+
require 'pgcrypto/table_manager'
|
5
|
+
|
6
|
+
module PGCrypto
|
7
|
+
class << self
|
8
|
+
def [](key)
|
9
|
+
(@table_manager ||= TableManager.new)[key]
|
10
|
+
end
|
11
|
+
|
12
|
+
def keys
|
13
|
+
@keys ||= KeyManager.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Error < StandardError; end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def pgcrypto(*column_names)
|
21
|
+
|
22
|
+
options = column_names.last.is_a?(Hash) ? column_names.pop : {}
|
23
|
+
options = {:type => :pgp}.merge(options)
|
24
|
+
|
25
|
+
column_names.map(&:to_s).each do |column_name|
|
26
|
+
encrypted_column_name = "#{column_name}_encrypted"
|
27
|
+
unless columns_hash[encrypted_column_name]
|
28
|
+
puts "WARNING: You defined #{column_name} as an encrypted column, but you don't have a corresponding #{encrypted_column_name} column in your database!"
|
29
|
+
end
|
30
|
+
# Stash the encryption type in our module so various monkeypatches can access it later!
|
31
|
+
PGCrypto[table_name][encrypted_column_name] = options.symbolize_keys!
|
32
|
+
|
33
|
+
# Add attribute readers/writers to keep this baby as fluid and clean as possible.
|
34
|
+
class_eval <<-encrypted_attribute_writer
|
35
|
+
def #{column_name}
|
36
|
+
@attributes["#{column_name}"]
|
37
|
+
end
|
38
|
+
|
39
|
+
# We write the attribute twice - once as the alias so the accessor keeps working, and once
|
40
|
+
# so the actual attribute value is dirty and will be queued up for assignment
|
41
|
+
def #{column_name}=(value)
|
42
|
+
@attributes["#{column_name}"] = value
|
43
|
+
write_attribute(:#{encrypted_column_name}, value)
|
44
|
+
end
|
45
|
+
encrypted_attribute_writer
|
46
|
+
# Did you notice how I was all, "clean as possible" before I fucked w/AR's internal
|
47
|
+
# instance variables rather than use the API? *Hilarious.*
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
PGCrypto.keys[:public] = {:path => '.pgcrypto'} if File.file?('.pgcrypto')
|
54
|
+
|
55
|
+
ActiveRecord::Base.extend PGCrypto::ClassMethods if defined? ActiveRecord::Base
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
2
|
+
|
3
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
4
|
+
alias :original_to_sql :to_sql
|
5
|
+
def to_sql(arel)
|
6
|
+
case arel
|
7
|
+
when Arel::InsertManager
|
8
|
+
pgcrypto_tweak_insert(arel)
|
9
|
+
when Arel::SelectManager
|
10
|
+
pgcrypto_tweak_select(arel)
|
11
|
+
when Arel::UpdateManager
|
12
|
+
pgcrypto_tweak_update(arel)
|
13
|
+
end
|
14
|
+
original_to_sql(arel)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def pgcrypto_tweak_insert(arel)
|
19
|
+
table = arel.ast.relation.name
|
20
|
+
unless PGCrypto[table].empty?
|
21
|
+
arel.ast.columns.each_with_index do |column, i|
|
22
|
+
if options = PGCrypto[table][column.name]
|
23
|
+
key = PGCrypto.keys[options[:public_key] || :public]
|
24
|
+
value = arel.ast.values.expressions[i]
|
25
|
+
quoted_value = quote_string(value)
|
26
|
+
encryption_instruction = %[pgp_pub_encrypt(#{quoted_value}, #{key.dearmored})]
|
27
|
+
arel.ast.values.expressions[i] = Arel::Nodes::SqlLiteral.new(encryption_instruction)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def pgcrypto_tweak_select(arel)
|
34
|
+
# We start by looping through each "core," which is just
|
35
|
+
# a SelectStatement, and tweaking both *what* it's selecting
|
36
|
+
# and correcting plain-text queries against an encrypted
|
37
|
+
# column...
|
38
|
+
joins = {}
|
39
|
+
arel.ast.cores.each do |core|
|
40
|
+
# Now we loop through each SelectStatement's actual selction -
|
41
|
+
# typically it's just '*'; and, in fact, that one of the only
|
42
|
+
# things we care about!
|
43
|
+
new_projections = []
|
44
|
+
core.projections.each do |projection|
|
45
|
+
next unless projection.respond_to?(:relation)
|
46
|
+
# The one other situation we might care about is if the projection is
|
47
|
+
# selecting a specifically encrypted column, in which case, we want to
|
48
|
+
# _wrap_ it. See how that's different?
|
49
|
+
if !PGCrypto[projection.relation.name].empty?
|
50
|
+
# Okay, so first, check if it's a broad select
|
51
|
+
if projection.name == '*'
|
52
|
+
# In this case, we want to just grap all the keys from columns in this table
|
53
|
+
# and select them fancy-like
|
54
|
+
PGCrypto[projection.relation.name].each do |column, options|
|
55
|
+
new_projections.push(pgcrypto_tweak_select_column(column, options, joins))
|
56
|
+
end
|
57
|
+
elsif options = PGCrypto[projection.relation.name][projection.name]
|
58
|
+
# And in this case, we're just selecting a single column!
|
59
|
+
new_projections.push(pgcrypto_tweak_select_column(projection.name, options, joins))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
core.projections.push(*new_projections)
|
64
|
+
|
65
|
+
# Dios mio! What an operation! Now we'll do something similar for the WHERE statements
|
66
|
+
core.wheres.each do |where|
|
67
|
+
# Now loop through the children to encrypt them for the SELECT
|
68
|
+
where.children.each do |child|
|
69
|
+
if options = PGCrypto[child.left.relation.name]["#{child.left.name}_encrypted"]
|
70
|
+
key = PGCrypto.keys[options[:private_key] || :private]
|
71
|
+
joins[key.name] ||= "#{key.dearmored} AS #{key.name}_key"
|
72
|
+
child.left = Arel::Nodes::SqlLiteral.new("pgp_pub_decrypt(#{child.left.name}_encrypted, keys.#{key.name}_key)")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
unless joins.empty?
|
78
|
+
arel.join(Arel::Nodes::SqlLiteral.new("CROSS JOIN (SELECT #{joins.values.join(', ')}) AS keys"))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def pgcrypto_tweak_select_column(column, options, joins)
|
83
|
+
return nil unless options[:type] == :pgp
|
84
|
+
key = PGCrypto.keys[options[:private_key] || :private]
|
85
|
+
select = %[pgp_pub_decrypt(#{column}, keys.#{key.name}_key#{", '#{key.password}'" if key.password}) AS "#{column.to_s.gsub(/_encrypted$/, '')}"]
|
86
|
+
joins[key.name] ||= "#{key.dearmored} AS #{key.name}_key"
|
87
|
+
Arel::Nodes::SqlLiteral.new(select)
|
88
|
+
end
|
89
|
+
|
90
|
+
def pgcrypto_tweak_update(arel)
|
91
|
+
# Loop through the assignments and make sure we take care of that whole
|
92
|
+
# NULL value thing!
|
93
|
+
arel.ast.values.each do |value|
|
94
|
+
if options = PGCrypto[value.left.relation.name][value.left.name]
|
95
|
+
case value.right
|
96
|
+
when NilClass
|
97
|
+
value.right = Arel::Nodes::SqlLiteral.new('NULL')
|
98
|
+
else
|
99
|
+
key = PGCrypto.keys[options[:public_key] || :public]
|
100
|
+
quoted_right = quote_string(value.right)
|
101
|
+
encryption_instruction = %[pgp_pub_encrypt('#{quoted_right}', #{key.dearmored})]
|
102
|
+
value.right = Arel::Nodes::SqlLiteral.new(encryption_instruction)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'arel/visitors/postgresql'
|
2
|
+
|
3
|
+
# We override some fun stuff in the PostgreSQL visitor class inside of Arel.
|
4
|
+
# This is the _most_ direct approach to tweaking the SQL to INSERT, SELECT,
|
5
|
+
# and UPDATE values as encrypted. Unfortunately, the visitor API doesn't
|
6
|
+
# give us access to managers as well as nodes, so we have use the public
|
7
|
+
# Arel API via the connection adapter's to_sql method. Then we tweak the
|
8
|
+
# more specific bits here!
|
9
|
+
|
10
|
+
Arel::Visitors::PostgreSQL.class_eval do
|
11
|
+
alias :original_visit_Arel_Nodes_Assignment :visit_Arel_Nodes_Assignment
|
12
|
+
def visit_Arel_Nodes_Assignment(assignment)
|
13
|
+
# Hijack the normally inoccuous assignment that happens, seeing as how
|
14
|
+
# Arel normally forwards this shit to someone else and I hate it.
|
15
|
+
if PGCrypto[assignment.left.relation.name][assignment.left.name]
|
16
|
+
# raise "#{visit(assignment.left)} = #{visit(assignment.right)}"
|
17
|
+
"#{visit(assignment.left)} = #{visit(assignment.right)}"
|
18
|
+
else
|
19
|
+
original_visit_Arel_Nodes_Assignment(assignment)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/pgcrypto/key.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module PGCrypto
|
2
|
+
class KeyManager < Hash
|
3
|
+
def []=(key, value)
|
4
|
+
unless value.is_a?(Key)
|
5
|
+
value = Key.new(value)
|
6
|
+
end
|
7
|
+
value.name = key
|
8
|
+
super key, value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Key
|
13
|
+
attr_accessor :name, :password, :value
|
14
|
+
attr_reader :path
|
15
|
+
attr_writer :armored
|
16
|
+
|
17
|
+
def armored?
|
18
|
+
@armored
|
19
|
+
end
|
20
|
+
|
21
|
+
def dearmored
|
22
|
+
"#{'dearmor(' if armored?}'#{self}'#{')' if armored?}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(options = {})
|
26
|
+
if options.is_a?(String)
|
27
|
+
self.value = key
|
28
|
+
elsif options.is_a?(Hash)
|
29
|
+
options.each do |key, value|
|
30
|
+
send("#{key}=", value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def path=(keyfile)
|
36
|
+
keyfile = File.expand_path(keyfile)
|
37
|
+
raise PGCrypto::Error, "\#{keyfile} does not exist!" unless File.file?(keyfile)
|
38
|
+
@path = keyfile
|
39
|
+
self.value = File.read(keyfile)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
def value=(key)
|
47
|
+
if key =~ /^-----BEGIN PGP /
|
48
|
+
self.armored = true
|
49
|
+
else
|
50
|
+
self.armored = false
|
51
|
+
end
|
52
|
+
@value = key.dup.freeze
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module PGCrypto
|
2
|
+
class Table < Hash
|
3
|
+
def [](key)
|
4
|
+
super(key.to_sym)
|
5
|
+
end
|
6
|
+
|
7
|
+
def []=(key, value)
|
8
|
+
super key.to_sym, value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TableManager < Table
|
13
|
+
def [](key)
|
14
|
+
return {} unless key
|
15
|
+
super(key) || self[key] = Table.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pgcrypto
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Flip Sasser
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-24 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! "\n PGCrypto is an ActiveRecord::Base extension that allows you
|
15
|
+
to asymmetrically\n encrypt PostgreSQL columns with as little trouble as possible.
|
16
|
+
It's totally\n freaking rad.\n "
|
17
|
+
email: flip@x451.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files:
|
21
|
+
- LICENSE
|
22
|
+
- README.markdown
|
23
|
+
files:
|
24
|
+
- .rspec
|
25
|
+
- CHANGES
|
26
|
+
- LICENSE
|
27
|
+
- README.markdown
|
28
|
+
- Rakefile
|
29
|
+
- VERSION
|
30
|
+
- lib/pgcrypto.rb
|
31
|
+
- lib/pgcrypto/active_record.rb
|
32
|
+
- lib/pgcrypto/arel.rb
|
33
|
+
- lib/pgcrypto/key.rb
|
34
|
+
- lib/pgcrypto/table_manager.rb
|
35
|
+
homepage: http://github.com/Plinq/pgcrypto
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project:
|
55
|
+
rubygems_version: 1.8.10
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: A transparent ActiveRecord::Base extension for encrypted columns
|
59
|
+
test_files: []
|