strongbox 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.textile +178 -0
- data/Rakefile +43 -0
- data/init.rb +1 -0
- data/lib/strongbox.rb +75 -0
- data/lib/strongbox/lock.rb +115 -0
- data/rails/init.rb +1 -0
- metadata +70 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 Joseph A. Ilacqua, Jr
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
h1. Strongbox
|
2
|
+
|
3
|
+
Strongbox provides Public Key Encryption for ActiveRecord. By using a public key
|
4
|
+
sensitive information can be encrypted and stored automatically. Once stored a
|
5
|
+
password is required to access the information.
|
6
|
+
|
7
|
+
Because the largest amount of data that can practically be encrypted with a public
|
8
|
+
key is 245 byte, by default Strongbox uses a two layer approach. First it encrypts
|
9
|
+
the attribute using symmetric encryption with a randomly generated key and
|
10
|
+
initialization vector (IV) (which can just be through to as a second key), then it
|
11
|
+
encrypts those with the public key.
|
12
|
+
|
13
|
+
Strongbox stores the encrypted attribute in a database column by the same name, i.e.
|
14
|
+
if you tell Strongbox to encrypt "secret" then it will be store in "secret" in the
|
15
|
+
database, just as the unencrypted attribute would be. If symmetric encryption is used
|
16
|
+
(the default) two additional columns "secret_key" and "secret_iv" are needed as well.
|
17
|
+
|
18
|
+
The attribute is automatically encrypted simply by setting it:
|
19
|
+
|
20
|
+
user.secret = "Shhhhhhh..."
|
21
|
+
|
22
|
+
and decrypted by calling the "decrypt" method with the private key password.
|
23
|
+
|
24
|
+
plain_text = user.secret.decrypt 'letmein'
|
25
|
+
|
26
|
+
h2. Quick Start
|
27
|
+
|
28
|
+
In your model:
|
29
|
+
|
30
|
+
class User < ActiveRecord::Base
|
31
|
+
encrypt_with_public_key :secret,
|
32
|
+
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem'),
|
33
|
+
end
|
34
|
+
|
35
|
+
In your migrations:
|
36
|
+
|
37
|
+
class AddSecretColumnsToUser < ActiveRecord::Migration
|
38
|
+
def self.up
|
39
|
+
add_column :users, :secret, binary
|
40
|
+
add_column :users, :secret_key, binary
|
41
|
+
add_column :users, :secret_iv, binary
|
42
|
+
end
|
43
|
+
def self.down
|
44
|
+
remove_column :users, :secret
|
45
|
+
remove_column :users, :secret_key
|
46
|
+
remove_column :users, :secret_iv
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
Generate a key pair:
|
51
|
+
|
52
|
+
(Choose a strong password.)
|
53
|
+
|
54
|
+
openssl genrsa -des3 -out config/private.pem 2048
|
55
|
+
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
56
|
+
cat config/private.pem config/public.pem >> config/keypair.pem
|
57
|
+
|
58
|
+
In your views and forms you don't need to do anything special to encrypt data. To
|
59
|
+
decrypt call:
|
60
|
+
|
61
|
+
user.secret.decrypt 'password'
|
62
|
+
|
63
|
+
h2. Gem installation (Rails 2.1+)
|
64
|
+
|
65
|
+
In config/environment.rb:
|
66
|
+
|
67
|
+
config.gem "spikex-strongbox",
|
68
|
+
:lib => 'strongbox',
|
69
|
+
:source => 'http://gems.github.com',
|
70
|
+
|
71
|
+
h2. Usage
|
72
|
+
|
73
|
+
_encrypt_with_public_key_ sets up the attribute it's called on for automatic
|
74
|
+
encryption. It's simplest form is:
|
75
|
+
|
76
|
+
class User < ActiveRecord::Base
|
77
|
+
encrypt_with_public_key :secret,
|
78
|
+
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Which will encrypt the attribute "secret". The attribute will be encrypted using
|
83
|
+
symmetric encryption with an automatically generated key and IV encrypted using the
|
84
|
+
public key. This requires three columns in the database "secret", "secret_key", and
|
85
|
+
"secret_iv" (see below).
|
86
|
+
|
87
|
+
Options to encrypt_with_public_key are:
|
88
|
+
|
89
|
+
:public_key - Path to the public key file. Overrides :keypair.
|
90
|
+
|
91
|
+
:private_key - Path to the private key file. Overrides :keypair.
|
92
|
+
|
93
|
+
:keypair - Path to a file containing both the public and private keys.
|
94
|
+
|
95
|
+
:symmetric :always/:never - Encrypt the date using symmetric encryption. The public
|
96
|
+
key is used to encrypt an automatically generated key and IV. This allows for large
|
97
|
+
amounts of data to be encrypted. The size of data that can be encrypted directly with
|
98
|
+
the public is limit to key size (in bytes) - 11. So a 2048 key can encrypt *245 bytes*. Defaults to :always
|
99
|
+
|
100
|
+
:symmetric_cipher - Cipher to use for symmetric encryption. Defaults to *'aes-256-cbc'*. Other ciphers support by OpenSSL may be used.
|
101
|
+
|
102
|
+
:base64 true/false - Use Base64 encoding to convert encrypted data to text. Use when
|
103
|
+
binary save data storage is not available. Defaults to *false*
|
104
|
+
|
105
|
+
:padding - Method used to pad data encrypted with the public key. Defaults to
|
106
|
+
RSA_PKCS1_PADDING. The default should be fine unless you are dealing with legacy
|
107
|
+
data.
|
108
|
+
|
109
|
+
For example, encrypting a small attribute, providing only the public key for extra
|
110
|
+
security, and Base64 encoding the encrypted data:
|
111
|
+
|
112
|
+
class User < ActiveRecord::Base
|
113
|
+
validates_length_of :pin_code, :is => 4
|
114
|
+
encrypt_with_public_key :pin_code,
|
115
|
+
:symmetric => :never
|
116
|
+
:base64 => true
|
117
|
+
:public_key => File.join(RAILS_ROOT,'config','public.pem'),
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
h2. Key Generation
|
122
|
+
|
123
|
+
Generate a key pair:
|
124
|
+
|
125
|
+
openssl genrsa -des3 -out config/private.pem 2048
|
126
|
+
Generating RSA private key, 2048 bit long modulus
|
127
|
+
......+++
|
128
|
+
.+++
|
129
|
+
e is 65537 (0x10001)
|
130
|
+
Enter pass phrase for config/private.pem:
|
131
|
+
Verifying - Enter pass phrase for config/private.pem:
|
132
|
+
|
133
|
+
and extract the the public key:
|
134
|
+
|
135
|
+
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
136
|
+
Enter pass phrase for config/private.pem:
|
137
|
+
writing RSA key
|
138
|
+
|
139
|
+
If you are going to leave the private key installed it's easiest to create a single
|
140
|
+
key pair file:
|
141
|
+
|
142
|
+
cat config/private.pem config/public.pem >> config/keypair.pem
|
143
|
+
|
144
|
+
Or, for added security, store the private key file else where, leaving only the public key.
|
145
|
+
|
146
|
+
h2. Table Creation
|
147
|
+
|
148
|
+
In it's default configuration Strongbox requires three columns, one the encrypted
|
149
|
+
data, one for the encrypted symmetric key, and one for the encrypted symmetric IV. If
|
150
|
+
symmetric encryption is disabled then only the columns for the data being encrypted
|
151
|
+
is needed.
|
152
|
+
|
153
|
+
If your underlying database allows, use the *binary* column type. If you must store
|
154
|
+
your data in text format be sure to enable Base64 encoding and to use the *text*
|
155
|
+
column type. The _string_ column type is likely to be too small to hold the encrypted
|
156
|
+
string.
|
157
|
+
|
158
|
+
h2. Security Caveats
|
159
|
+
|
160
|
+
If you don't encrypt your data, then an attacker only needs to steal that data to get
|
161
|
+
your secrets.
|
162
|
+
|
163
|
+
If encrypt your data using symmetric encrypts and a stored key, then the attacker
|
164
|
+
needs the data and the key stored on the server.
|
165
|
+
|
166
|
+
If you use public key encryption, the attacker needs the data, the private key, and
|
167
|
+
the password. This means the attacker has to sniff the password somehow, so that's
|
168
|
+
what you need to protect against.
|
169
|
+
|
170
|
+
h2. Authors
|
171
|
+
|
172
|
+
Spike Ilacqua
|
173
|
+
|
174
|
+
h2. Thanks
|
175
|
+
|
176
|
+
Strongbox's implementation drew inspiration from Thoughtbot's Paperclip gem
|
177
|
+
http://www.thoughtbot.com/projects/paperclip
|
178
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
6
|
+
require 'strongbox'
|
7
|
+
|
8
|
+
desc 'Default: run tests.'
|
9
|
+
task :default => :test
|
10
|
+
|
11
|
+
desc 'Test the strongbox gem.'
|
12
|
+
Rake::TestTask.new(:test) do |t|
|
13
|
+
t.libs << 'lib' << 'profile'
|
14
|
+
t.pattern = 'test/**/*_test.rb'
|
15
|
+
t.verbose = true
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Generate documentation for the strongbox gem.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'doc'
|
21
|
+
rdoc.title = 'Strongbox'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README*')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
spec = Gem::Specification.new do |s|
|
28
|
+
s.name = "strongbox"
|
29
|
+
s.version = Strongbox::VERSION
|
30
|
+
s.summary = "Secures ActiveRecord fields with public key encryption."
|
31
|
+
s.authors = ["Spike Ilacqua"]
|
32
|
+
s.email = "spike@stuff-things.net"
|
33
|
+
s.homepage = "http://stuff-things.net/strongbox"
|
34
|
+
s.files = FileList["[A-Z]*", "init.rb", "{lib,rails}/**/*"]
|
35
|
+
s.add_development_dependency 'thoughtbot-shoulda'
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Generate a gemspec file for GitHub"
|
39
|
+
task :gemspec do
|
40
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
41
|
+
f.write spec.to_yaml
|
42
|
+
end
|
43
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'strongbox'
|
data/lib/strongbox.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
require 'strongbox/lock'
|
5
|
+
|
6
|
+
module Strongbox
|
7
|
+
|
8
|
+
VERSION = "0.2.0"
|
9
|
+
|
10
|
+
RSA_PKCS1_PADDING = OpenSSL::PKey::RSA::PKCS1_PADDING
|
11
|
+
RSA_SSLV23_PADDING = OpenSSL::PKey::RSA::SSLV23_PADDING
|
12
|
+
RSA_NO_PADDING = OpenSSL::PKey::RSA::NO_PADDING
|
13
|
+
RSA_PKCS1_OAEP_PADDING = OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Provides for setting the default options for Strongbox
|
17
|
+
def options
|
18
|
+
@options ||= {
|
19
|
+
:base64 => false,
|
20
|
+
:symmetric => :always,
|
21
|
+
:padding => RSA_PKCS1_PADDING,
|
22
|
+
:symmetric_cipher => 'aes-256-cbc'
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def included base #:nodoc:
|
27
|
+
base.extend ClassMethods
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class StrongboxError < StandardError #:nodoc:
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
# +encrypt_with_public_key+ gives the class it is called on an attribute that
|
36
|
+
# when assigned is automatically encrypted using a public key. This allows the
|
37
|
+
# unattended encryption of data, without exposing the information need to decrypt
|
38
|
+
# it (as would be the case when using symmetric key encryption alone). Small
|
39
|
+
# amounts of data may be encrypted directly with the public key. Larger data is
|
40
|
+
# encrypted using symmetric encryption. The encrypted data is stored in the
|
41
|
+
# database column of the same name as the attibute. If symmetric encryption is
|
42
|
+
# used (the default) additional column are need to store the generated password
|
43
|
+
# and IV.
|
44
|
+
def encrypt_with_public_key(name, options = {})
|
45
|
+
include InstanceMethods
|
46
|
+
|
47
|
+
class_inheritable_reader :lock_options
|
48
|
+
write_inheritable_attribute(:lock_options, {}) if lock_options.nil?
|
49
|
+
|
50
|
+
|
51
|
+
lock_options[name] = options.symbolize_keys.reverse_merge Strongbox.options
|
52
|
+
|
53
|
+
define_method name do
|
54
|
+
lock_for(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
define_method "#{name}=" do | plaintext |
|
58
|
+
lock_for(name).encrypt plaintext
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods
|
65
|
+
def lock_for name
|
66
|
+
@_locks ||= {}
|
67
|
+
@_locks[name] ||= Lock.new(name, self, self.class.lock_options[name])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if Object.const_defined?("ActiveRecord")
|
73
|
+
ActiveRecord::Base.send(:include, Strongbox)
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Strongbox
|
2
|
+
# The Lock class encrypts and decrypts the protected attribute. It
|
3
|
+
# automatically encrypts the data when set and decrypts it when the private
|
4
|
+
# key password is provided.
|
5
|
+
class Lock
|
6
|
+
|
7
|
+
def initialize name, instance, options = {}
|
8
|
+
@name = name
|
9
|
+
@instance = instance
|
10
|
+
|
11
|
+
@size = nil
|
12
|
+
|
13
|
+
options = Strongbox.options.merge(options)
|
14
|
+
|
15
|
+
@base64 = options[:base64]
|
16
|
+
@public_key = options[:public_key] || options[:key_pair]
|
17
|
+
@private_key = options[:private_key] || options[:key_pair]
|
18
|
+
@padding = options[:padding]
|
19
|
+
@symmetric = options[:symmetric]
|
20
|
+
@symmetric_cipher = options[:symmetric_cipher]
|
21
|
+
@symmetric_key = options[:symmetric_key] || "#{name}_key"
|
22
|
+
@symmetric_iv = options[:symmetric_iv] || "#{name}_iv"
|
23
|
+
end
|
24
|
+
|
25
|
+
def encrypt plaintext
|
26
|
+
unless @public_key
|
27
|
+
raise StrongboxError.new("#{@instance.class} model does not have public key_file")
|
28
|
+
end
|
29
|
+
if !plaintext.blank?
|
30
|
+
@size = plaintext.size # For validations
|
31
|
+
# Using a blank password in OpenSSL::PKey::RSA.new prevents reading
|
32
|
+
# the private key if the file is a key pair
|
33
|
+
public_key = OpenSSL::PKey::RSA.new(File.read(@public_key),"")
|
34
|
+
if @symmetric == :always
|
35
|
+
cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
|
36
|
+
cipher.encrypt
|
37
|
+
cipher.key = random_key = cipher.random_key
|
38
|
+
cipher.iv = random_iv = cipher.random_iv
|
39
|
+
|
40
|
+
ciphertext = cipher.update(plaintext)
|
41
|
+
ciphertext << cipher.final
|
42
|
+
encrypted_key = public_key.public_encrypt(random_key,@padding)
|
43
|
+
encrypted_iv = public_key.public_encrypt(random_iv,@padding)
|
44
|
+
if @base64
|
45
|
+
encrypted_key = Base64.encode64(encrypted_key)
|
46
|
+
encrypted_iv = Base64.encode64(encrypted_iv)
|
47
|
+
end
|
48
|
+
@instance.write_attribute(@symmetric_key,encrypted_key)
|
49
|
+
@instance.write_attribute(@symmetric_iv,encrypted_iv)
|
50
|
+
else
|
51
|
+
ciphertext = public_key.public_encrypt(plaintext,@padding)
|
52
|
+
end
|
53
|
+
ciphertext = Base64.encode64(ciphertext) if @base64
|
54
|
+
@instance.write_attribute(@name,ciphertext)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Given the private key password decrypts the attribute. Will raise
|
59
|
+
# OpenSSL::PKey::RSAError if the password is wrong.
|
60
|
+
|
61
|
+
def decrypt password = ""
|
62
|
+
# Given a private key and a nil password OpenSSL::PKey::RSA.new() will
|
63
|
+
# *prompt* for a password, we default to an empty string to avoid that.
|
64
|
+
ciphertext = @instance.read_attribute(@name)
|
65
|
+
return nil if ciphertext.nil?
|
66
|
+
return "" if ciphertext.empty?
|
67
|
+
|
68
|
+
return "*encrypted*" if password.blank?
|
69
|
+
|
70
|
+
unless @private_key
|
71
|
+
raise StrongboxError.new("#{@instance.class} model does not have private key_file")
|
72
|
+
end
|
73
|
+
|
74
|
+
if ciphertext
|
75
|
+
ciphertext = Base64.decode64(ciphertext) if @base64
|
76
|
+
private_key = OpenSSL::PKey::RSA.new(File.read(@private_key),password)
|
77
|
+
if @symmetric == :always
|
78
|
+
random_key = @instance.read_attribute(@symmetric_key)
|
79
|
+
random_iv = @instance.read_attribute(@symmetric_iv)
|
80
|
+
if @base64
|
81
|
+
random_key = Base64.decode64(random_key)
|
82
|
+
random_iv = Base64.decode64(random_iv)
|
83
|
+
end
|
84
|
+
cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
|
85
|
+
cipher.decrypt
|
86
|
+
cipher.key = private_key.private_decrypt(random_key,@padding)
|
87
|
+
cipher.iv = private_key.private_decrypt(random_iv,@padding)
|
88
|
+
plaintext = cipher.update(ciphertext)
|
89
|
+
plaintext << cipher.final
|
90
|
+
else
|
91
|
+
plaintext = private_key.private_decrypt(ciphertext,@padding)
|
92
|
+
end
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def to_s
|
99
|
+
decrypt
|
100
|
+
end
|
101
|
+
|
102
|
+
# Needed for validations
|
103
|
+
def blank?
|
104
|
+
@instance.read_attribute(@name).blank?
|
105
|
+
end
|
106
|
+
|
107
|
+
def nil?
|
108
|
+
@instance.read_attribute(@name).nil?
|
109
|
+
end
|
110
|
+
|
111
|
+
def size
|
112
|
+
@size
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),'../init.rb')
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strongbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Spike Ilacqua
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-28 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description:
|
26
|
+
email: spike@stuff-things.net
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files: []
|
32
|
+
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- Rakefile
|
36
|
+
- README.textile
|
37
|
+
- init.rb
|
38
|
+
- lib/strongbox/lock.rb
|
39
|
+
- lib/strongbox.rb
|
40
|
+
- rails/init.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://stuff-things.net/strongbox
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: "0"
|
61
|
+
version:
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.4
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Secures ActiveRecord fields with public key encryption.
|
69
|
+
test_files: []
|
70
|
+
|