pgcrypto 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|