rjharmon-strongbox 0.3.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 +271 -0
- data/Rakefile +43 -0
- data/init.rb +1 -0
- data/lib/strongbox/lock.rb +190 -0
- data/lib/strongbox.rb +85 -0
- data/rails/init.rb +1 -0
- metadata +71 -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,271 @@
|
|
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, the
|
5
|
+
private key and password are required to access the information.
|
6
|
+
|
7
|
+
Because the largest amount of data that can practically be encrypted with a 2048-bit
|
8
|
+
public key is 245 bytes, by default Strongbox uses a two layer approach. First it
|
9
|
+
encrypts the attribute using symmetric encryption with a randomly generated key and
|
10
|
+
initialization vector (IV) (which can just be thought of 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 have been. If symmetric encryption
|
16
|
+
is used (the default) two additional columns "secret_key" and "secret_iv" are needed
|
17
|
+
as well.
|
18
|
+
|
19
|
+
The attribute is automatically and immediately encrypted simply by setting it:
|
20
|
+
|
21
|
+
user.secret = "Shhhhhhh..."
|
22
|
+
|
23
|
+
and decrypted by calling the "decrypt" method with the private key password.
|
24
|
+
|
25
|
+
plain_text = user.secret.decrypt 'letmein'
|
26
|
+
|
27
|
+
The password for the private key can be empty if you are protecting the key through a
|
28
|
+
different method. In that case, provide the empty string '' to decrypt() in order to
|
29
|
+
decrypt the data.
|
30
|
+
|
31
|
+
This fork of Strongbox is also able to perform symmetric-only encryption. If you
|
32
|
+
do this, be sure that you're protecting the symmetric key. One application of this is
|
33
|
+
for encrypting data to a private password known by the user. See below for more options.
|
34
|
+
|
35
|
+
h2. Quick Start
|
36
|
+
|
37
|
+
In your model:
|
38
|
+
|
39
|
+
bc. class User < ActiveRecord::Base
|
40
|
+
encrypt_with_public_key :secret,
|
41
|
+
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
42
|
+
end
|
43
|
+
|
44
|
+
In your migrations:
|
45
|
+
|
46
|
+
bc. class AddSecretColumnsToUser < ActiveRecord::Migration
|
47
|
+
def self.up
|
48
|
+
add_column :users, :secret, :binary
|
49
|
+
add_column :users, :secret_key, :binary
|
50
|
+
add_column :users, :secret_iv, :binary
|
51
|
+
end
|
52
|
+
def self.down
|
53
|
+
remove_column :users, :secret
|
54
|
+
remove_column :users, :secret_key
|
55
|
+
remove_column :users, :secret_iv
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Generate a key pair:
|
60
|
+
|
61
|
+
(Choose a strong password.)
|
62
|
+
|
63
|
+
bc. openssl genrsa -des3 -out config/private.pem 2048
|
64
|
+
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
65
|
+
cat config/private.pem config/public.pem >> config/keypair.pem
|
66
|
+
|
67
|
+
In your views and forms you don't need to do anything special to encrypt data. To
|
68
|
+
decrypt call:
|
69
|
+
|
70
|
+
bc. user.secret.decrypt 'password'
|
71
|
+
|
72
|
+
h2. Gem installation (Rails 2.1+)
|
73
|
+
|
74
|
+
In config/environment.rb:
|
75
|
+
|
76
|
+
bc. config.gem "strongbox"
|
77
|
+
|
78
|
+
h2. Usage
|
79
|
+
|
80
|
+
_encrypt_with_public_key_ sets up the attribute it's called on for automatic
|
81
|
+
encryption. It's simplest form is:
|
82
|
+
|
83
|
+
bc. class User < ActiveRecord::Base
|
84
|
+
encrypt_with_public_key :secret,
|
85
|
+
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
86
|
+
end
|
87
|
+
|
88
|
+
Which will encrypt the attribute "secret". The attribute will be encrypted using
|
89
|
+
symmetric encryption with an automatically generated key and IV encrypted using the
|
90
|
+
public key. This requires three columns in the database "secret", "secret_key", and
|
91
|
+
"secret_iv" (see below).
|
92
|
+
|
93
|
+
Options to encrypt_with_public_key are:
|
94
|
+
|
95
|
+
:public_key - Path to the public key file. Overrides :keypair.
|
96
|
+
|
97
|
+
:private_key - Path to the private key file. Overrides :keypair.
|
98
|
+
|
99
|
+
:keypair - Path to a file containing both the public and private keys.
|
100
|
+
|
101
|
+
:symmetric :always/:never - Encrypt the date using symmetric encryption. The public
|
102
|
+
key is used to encrypt an automatically generated key and IV. This allows for large
|
103
|
+
amounts of data to be encrypted. The size of data that can be encrypted directly with
|
104
|
+
the public is limit to key size (in bytes) - 11. So a 2048 key can encrypt *245 bytes*. Defaults to :always
|
105
|
+
|
106
|
+
:symmetric_cipher - Cipher to use for symmetric encryption. Defaults to *'aes-256-cbc'*. Other ciphers support by OpenSSL may be used.
|
107
|
+
|
108
|
+
:base64 true/false - Use Base64 encoding to convert encrypted data to text. Use when
|
109
|
+
binary save data storage is not available. Defaults to *false*
|
110
|
+
|
111
|
+
:padding - Method used to pad data encrypted with the public key. Defaults to
|
112
|
+
RSA_PKCS1_PADDING. The default should be fine unless you are dealing with legacy
|
113
|
+
data.
|
114
|
+
|
115
|
+
:encrypt_iv true/false - Default is true for backward compatibility, but it is not
|
116
|
+
necessary to encrypt the initialization vector to maintain security. For first-time
|
117
|
+
installations, you might choose to set this to false, which cuts the encryption and
|
118
|
+
decryption overhead approximately in half. There is currently no method for
|
119
|
+
migrating encrypted iv's to clear-text iv's, but you could add a second set of columns
|
120
|
+
with a different configuration and write a script that decrypts values from one encrypted
|
121
|
+
column and stores them into to the second decrypted column.
|
122
|
+
|
123
|
+
|
124
|
+
For example, encrypting a small attribute, providing only the public key for extra
|
125
|
+
security, and Base64 encoding the encrypted data:
|
126
|
+
|
127
|
+
bc. class User < ActiveRecord::Base
|
128
|
+
validates_length_of :pin_code, :is => 4
|
129
|
+
encrypt_with_public_key :pin_code,
|
130
|
+
:symmetric => :never,
|
131
|
+
:base64 => true,
|
132
|
+
:public_key => File.join(RAILS_ROOT,'config','public.pem')
|
133
|
+
end
|
134
|
+
|
135
|
+
h2. Key Generation
|
136
|
+
|
137
|
+
Generate a key pair:
|
138
|
+
|
139
|
+
bc. openssl genrsa -des3 -out config/private.pem 2048
|
140
|
+
Generating RSA private key, 2048 bit long modulus
|
141
|
+
......+++
|
142
|
+
.+++
|
143
|
+
e is 65537 (0x10001)
|
144
|
+
Enter pass phrase for config/private.pem:
|
145
|
+
Verifying - Enter pass phrase for config/private.pem:
|
146
|
+
|
147
|
+
and extract the the public key:
|
148
|
+
|
149
|
+
bc. openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
150
|
+
Enter pass phrase for config/private.pem:
|
151
|
+
writing RSA key
|
152
|
+
|
153
|
+
If you are going to leave the private key installed it's easiest to create a single
|
154
|
+
key pair file:
|
155
|
+
|
156
|
+
bc. cat config/private.pem config/public.pem >> config/keypair.pem
|
157
|
+
|
158
|
+
Or, for added security, store the private key file else where, leaving only the public key.
|
159
|
+
|
160
|
+
h2. Table Creation
|
161
|
+
|
162
|
+
In it's default configuration Strongbox requires three columns, one the encrypted
|
163
|
+
data, one for the encrypted symmetric key, and one for the encrypted symmetric IV. If
|
164
|
+
symmetric encryption is disabled then only the columns for the data being encrypted
|
165
|
+
is needed.
|
166
|
+
|
167
|
+
If your underlying database allows, use the *binary* column type. If you must store
|
168
|
+
your data in text format be sure to enable Base64 encoding and to use the *text*
|
169
|
+
column type. If you use a _string_ column and encrypt anything greater than 186
|
170
|
+
bytes (245 bytes if you don't enable Base64 encoding) *your data will be lost*.
|
171
|
+
|
172
|
+
h2. Validation
|
173
|
+
|
174
|
+
Because Strongbox immediately encrypts the data as you assign it into the model,
|
175
|
+
the amount of validation that can be done is minimal, being limited to
|
176
|
+
validates_size_of and validates_presence_of.
|
177
|
+
|
178
|
+
If you require additional validation for your encrypted columns, this should be done
|
179
|
+
before assigning into encrypted attributes. That, or you might want to contribute a
|
180
|
+
patch that delays the encryption step until right before save.
|
181
|
+
|
182
|
+
h2. Symmetric Encryption
|
183
|
+
|
184
|
+
h3. Background
|
185
|
+
|
186
|
+
Asymmetric encryption is generally far preferred over symmetric-only encryption.
|
187
|
+
Being able to physically separate and protect the private decryption key from the
|
188
|
+
public encryption key creates a level of potential security unmatchable with
|
189
|
+
symmetric encryption, which is reversible using the single encryption key.
|
190
|
+
|
191
|
+
However, symmetric keys can be useful in certain circumstances - for example the
|
192
|
+
combined symmetric/asymmetric encryption provided by default with
|
193
|
+
_encrypt_with_public_key_. Other advanced examples include:
|
194
|
+
|
195
|
+
* Encrypt data to a PIN code known only to the user (retrieve it only when they re-type their PIN)
|
196
|
+
|
197
|
+
* Encrypt a PIN code with public key, then use the PIN to symmetrically encrypt other data, so
|
198
|
+
that it can be retrieved directly by the user but not by an attacker. Combined with pubkey
|
199
|
+
encryption for the same data, much flexibility is gained with a minimum of risk exposure - though
|
200
|
+
it comes with a tradeoff: increased code complexity.
|
201
|
+
|
202
|
+
h3. Implementing Symmetric Encryption
|
203
|
+
|
204
|
+
If you don't understand the caveats above, please re-read them. Then, if you are
|
205
|
+
prepared to do symmetric encryption, use _encrypt_with_symmetric_key_:
|
206
|
+
|
207
|
+
bc. class User < ActiveRecord::Base
|
208
|
+
validates_length_of :pin_code, :is => 4
|
209
|
+
encrypt_with_symmetric_key :some_data, :encrypt_iv => false, :key_proc => :pin_key
|
210
|
+
attr_accessor :pin_key
|
211
|
+
end
|
212
|
+
|
213
|
+
All options are the same as for pubkey encryption, except that no keys may be specified.
|
214
|
+
Additionally, :encrypt_iv must be set to false, and the :key_proc must be specified as a
|
215
|
+
symbol referring to a function which will return the symmetric key.
|
216
|
+
|
217
|
+
h3. Implementing the symmetric-key function
|
218
|
+
|
219
|
+
Two examples should demonstrate the use of a function that returns symmetric keys. They
|
220
|
+
correspond to the two use cases mentioned above.
|
221
|
+
|
222
|
+
Encrypting with a key not stored on the server: This is easily implemented by creating
|
223
|
+
an attr_accessor on the model having the encrypted field.
|
224
|
+
|
225
|
+
bc. class User < ActiveRecord::Base
|
226
|
+
encrypt_with_symmetric_key :some_data, :encrypt_iv => false, :key_proc => :pin_key
|
227
|
+
attr_accessor :pin_key
|
228
|
+
end
|
229
|
+
|
230
|
+
In your controller, put the key into the model prior to storing or retrieving encrypted data.
|
231
|
+
|
232
|
+
In the second example, we store the user's PIN, encrypted asymmetrically. To encrypt
|
233
|
+
data to the PIN code, we go inside the security perimeter where we have the private key;
|
234
|
+
decrypt the PIN, then set it, possibly using the attr_accessor method, prior to encrypting
|
235
|
+
the symmetric data.
|
236
|
+
|
237
|
+
Decryption for user-facing content is done the same way as in the first example.
|
238
|
+
|
239
|
+
Another method of implementing the :key_proc is as follows:
|
240
|
+
|
241
|
+
bc. attr_writer :pin_key
|
242
|
+
def pin_key
|
243
|
+
@pin_key || self.pin.decrypt('password')
|
244
|
+
end
|
245
|
+
|
246
|
+
h2. Security Caveats
|
247
|
+
|
248
|
+
If you don't encrypt your data, then an attacker only needs to steal that data to get
|
249
|
+
your secrets.
|
250
|
+
|
251
|
+
If encrypt your data using symmetric encrypts and a stored key, then the attacker
|
252
|
+
needs the data and the key stored on the server.
|
253
|
+
|
254
|
+
If you use public key encryption, the attacker needs the data, the private key, and
|
255
|
+
the password. This means the attacker has to sniff the password somehow, so that's
|
256
|
+
what you need to protect against.
|
257
|
+
|
258
|
+
h2. Authors
|
259
|
+
|
260
|
+
Spike Ilacqua
|
261
|
+
|
262
|
+
h2. Contributors
|
263
|
+
|
264
|
+
Randy Harmon
|
265
|
+
|
266
|
+
h2. Thanks
|
267
|
+
|
268
|
+
Strongbox's implementation drew inspiration from Thoughtbot's Paperclip gem
|
269
|
+
http://www.thoughtbot.com/projects/paperclip
|
270
|
+
|
271
|
+
|
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'
|
@@ -0,0 +1,190 @@
|
|
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
|
+
@is_empty = true if @instance[@name].blank?
|
16
|
+
|
17
|
+
@base64 = options[:base64]
|
18
|
+
@public_key = options[:public_key] || options[:key_pair]
|
19
|
+
@private_key = options[:private_key] || options[:key_pair]
|
20
|
+
@padding = options[:padding]
|
21
|
+
@symmetric = options[:symmetric]
|
22
|
+
@symmetric_cipher = options[:symmetric_cipher]
|
23
|
+
@symmetric_key = options[:symmetric_key] || "#{name}_key"
|
24
|
+
@symmetric_iv = options[:symmetric_iv] || "#{name}_iv"
|
25
|
+
@key_proc = options[:key_proc]
|
26
|
+
@encrypt_iv = options[:encrypt_iv]
|
27
|
+
if @symmetric == :only
|
28
|
+
if @encrypt_iv
|
29
|
+
raise ArgumentError, ":encrypt_iv should be set to false for :symmetric => :only encryption, since encrypting the iv requires a pubkey"
|
30
|
+
end
|
31
|
+
if @public_key
|
32
|
+
raise ArgumentError, ":public_key, :private_key and :key_pair are not used with :symmetric => :only"
|
33
|
+
end
|
34
|
+
unless @key_proc
|
35
|
+
raise ArgumentError, ":key_proc option is required. This option specifies a proc or a symbol of a method on the instance, which will return a key used for the symmetric cypher."
|
36
|
+
end
|
37
|
+
else
|
38
|
+
if @key_proc
|
39
|
+
raise ArgumentError, ":key_proc is valid only when :symmetric => :only is specified, or when using encrypt_with_symmetric_key()"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def encrypt plaintext
|
45
|
+
|
46
|
+
unless @public_key or @symmetric == :only
|
47
|
+
raise StrongboxError.new("#{@instance.class} model does not have public key_file")
|
48
|
+
end
|
49
|
+
if !plaintext.blank?
|
50
|
+
@is_empty = false
|
51
|
+
@size = plaintext.size # For validations
|
52
|
+
# Using a blank password in OpenSSL::PKey::RSA.new prevents reading
|
53
|
+
# the private key if the file is a key pair
|
54
|
+
public_key = get_rsa_key(@public_key,"")
|
55
|
+
if @symmetric == :always or @symmetric == :only
|
56
|
+
cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
|
57
|
+
cipher.encrypt
|
58
|
+
|
59
|
+
cipher.key = symmetric_key = case @key_proc
|
60
|
+
when Proc
|
61
|
+
@key_proc.call( @instance )
|
62
|
+
when Symbol
|
63
|
+
@instance.send( @key_proc )
|
64
|
+
else
|
65
|
+
cipher.random_key
|
66
|
+
end
|
67
|
+
cipher.iv = symmetric_iv = cipher.random_iv
|
68
|
+
|
69
|
+
ciphertext = cipher.update(plaintext)
|
70
|
+
ciphertext << cipher.final
|
71
|
+
unless @symmetric == :only
|
72
|
+
encrypted_key = public_key.public_encrypt(symmetric_key,@padding)
|
73
|
+
end
|
74
|
+
if @encrypt_iv
|
75
|
+
encrypted_iv = public_key.public_encrypt(symmetric_iv,@padding)
|
76
|
+
end
|
77
|
+
if @base64
|
78
|
+
unless @symmetric == :only
|
79
|
+
encrypted_key = Base64.encode64(encrypted_key)
|
80
|
+
end
|
81
|
+
encrypted_iv = Base64.encode64(encrypted_iv)
|
82
|
+
end
|
83
|
+
unless @symmetric == :only
|
84
|
+
@instance[@symmetric_key] = encrypted_key
|
85
|
+
end
|
86
|
+
if @encrypt_iv
|
87
|
+
@instance[@symmetric_iv] = encrypted_iv
|
88
|
+
else
|
89
|
+
@instance[@symmetric_iv] = symmetric_iv
|
90
|
+
end
|
91
|
+
else
|
92
|
+
ciphertext = public_key.public_encrypt(plaintext,@padding)
|
93
|
+
end
|
94
|
+
ciphertext = Base64.encode64(ciphertext) if @base64
|
95
|
+
@instance[@name] = ciphertext
|
96
|
+
else
|
97
|
+
@size = 0
|
98
|
+
@instance[@name] = ""
|
99
|
+
@is_empty = true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Given the private key password decrypts the attribute. Will raise
|
104
|
+
# OpenSSL::PKey::RSAError if the password is wrong.
|
105
|
+
|
106
|
+
def decrypt password = nil
|
107
|
+
return "" if @is_empty
|
108
|
+
# Given a private key and a nil password OpenSSL::PKey::RSA.new() will
|
109
|
+
# *prompt* for a password, we default to an empty string to avoid that.
|
110
|
+
ciphertext = @instance[@name]
|
111
|
+
return nil if ciphertext.nil?
|
112
|
+
return "" if ciphertext.empty?
|
113
|
+
|
114
|
+
return "*encrypted*" if password.nil? and ! @key_proc
|
115
|
+
unless @private_key or @symmetric == :only
|
116
|
+
raise StrongboxError.new("#{@instance.class} model does not have private key_file")
|
117
|
+
end
|
118
|
+
|
119
|
+
if ciphertext
|
120
|
+
ciphertext = Base64.decode64(ciphertext) if @base64
|
121
|
+
private_key = get_rsa_key(@private_key,password)
|
122
|
+
|
123
|
+
if @symmetric == :always || @symmetric == :only
|
124
|
+
symmetric_key = case @key_proc
|
125
|
+
when Proc
|
126
|
+
@key_proc.call( @instance )
|
127
|
+
when Symbol
|
128
|
+
@instance.send( @key_proc )
|
129
|
+
else
|
130
|
+
@instance[@symmetric_key]
|
131
|
+
end
|
132
|
+
symmetric_iv = @instance[@symmetric_iv]
|
133
|
+
|
134
|
+
if @base64
|
135
|
+
if @symmetric == :always
|
136
|
+
symmetric_key = Base64.decode64(symmetric_key)
|
137
|
+
end
|
138
|
+
symmetric_iv = Base64.decode64(symmetric_iv)
|
139
|
+
end
|
140
|
+
cipher = OpenSSL::Cipher::Cipher.new(@symmetric_cipher)
|
141
|
+
cipher.decrypt
|
142
|
+
cipher.key = if @symmetric == :only
|
143
|
+
symmetric_key
|
144
|
+
else
|
145
|
+
private_key.private_decrypt(symmetric_key,@padding)
|
146
|
+
end
|
147
|
+
if @encrypt_iv
|
148
|
+
cipher.iv = private_key.private_decrypt(symmetric_iv,@padding)
|
149
|
+
else
|
150
|
+
cipher.iv = symmetric_iv
|
151
|
+
end
|
152
|
+
|
153
|
+
plaintext = cipher.update(ciphertext)
|
154
|
+
plaintext << cipher.final
|
155
|
+
else
|
156
|
+
plaintext = private_key.private_decrypt(ciphertext,@padding)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_s
|
164
|
+
decrypt
|
165
|
+
end
|
166
|
+
|
167
|
+
# Needed for validations
|
168
|
+
def blank?
|
169
|
+
@instance[@name].blank?
|
170
|
+
end
|
171
|
+
|
172
|
+
def nil?
|
173
|
+
@instance[@name].nil?
|
174
|
+
end
|
175
|
+
|
176
|
+
def size
|
177
|
+
@size
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
def get_rsa_key(key,password = '')
|
182
|
+
return nil unless key
|
183
|
+
return key if key.is_a?(OpenSSL::PKey::RSA)
|
184
|
+
if key !~ /^-----BEGIN RSA/
|
185
|
+
key = File.read(key)
|
186
|
+
end
|
187
|
+
return OpenSSL::PKey::RSA.new(key,password)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
data/lib/strongbox.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
require 'strongbox/lock'
|
5
|
+
|
6
|
+
module Strongbox
|
7
|
+
|
8
|
+
VERSION = "0.3.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
|
+
:encrypt_iv => true,
|
23
|
+
:symmetric_cipher => 'aes-256-cbc'
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def included base #:nodoc:
|
28
|
+
base.extend ClassMethods
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class StrongboxError < StandardError #:nodoc:
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# +encrypt_with_public_key+ gives the class it is called on an attribute that
|
37
|
+
# when assigned is automatically encrypted using a public key. This allows the
|
38
|
+
# unattended encryption of data, without exposing the information need to decrypt
|
39
|
+
# it (as would be the case when using symmetric key encryption alone). Small
|
40
|
+
# amounts of data may be encrypted directly with the public key. Larger data is
|
41
|
+
# encrypted using symmetric encryption. The encrypted data is stored in the
|
42
|
+
# database column of the same name as the attibute. If symmetric encryption is
|
43
|
+
# used (the default) additional column are need to store the generated password
|
44
|
+
# and IV.
|
45
|
+
def encrypt_with_public_key(name, options = {})
|
46
|
+
strongbox_encryption( name, options )
|
47
|
+
end
|
48
|
+
|
49
|
+
def encrypt_with_symmetric_key( name, options = {})
|
50
|
+
options.merge!( :symmetric => :only )
|
51
|
+
strongbox_encryption( name, options )
|
52
|
+
end
|
53
|
+
|
54
|
+
def strongbox_encryption( name, options )
|
55
|
+
include InstanceMethods
|
56
|
+
|
57
|
+
class_inheritable_reader :lock_options
|
58
|
+
write_inheritable_attribute(:lock_options, {}) if lock_options.nil?
|
59
|
+
|
60
|
+
|
61
|
+
lock_options[name] = options.symbolize_keys.reverse_merge Strongbox.options
|
62
|
+
|
63
|
+
define_method name do
|
64
|
+
lock_for(name)
|
65
|
+
end
|
66
|
+
|
67
|
+
define_method "#{name}=" do | plaintext |
|
68
|
+
lock_for(name).encrypt plaintext
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods
|
75
|
+
def lock_for name
|
76
|
+
@_locks ||= {}
|
77
|
+
@_locks[name] ||= Lock.new(name, self, self.class.lock_options[name])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if Object.const_defined?("ActiveRecord")
|
83
|
+
ActiveRecord::Base.send(:include, Strongbox)
|
84
|
+
end
|
85
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__),'../init.rb')
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rjharmon-strongbox
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Spike Ilacqua
|
8
|
+
- Randy Harmon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2009-12-13 23:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: thoughtbot-shoulda
|
18
|
+
type: :development
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
version:
|
26
|
+
description:
|
27
|
+
email: r_j_h_box-sf@yahoo.com
|
28
|
+
executables: []
|
29
|
+
|
30
|
+
extensions: []
|
31
|
+
|
32
|
+
extra_rdoc_files: []
|
33
|
+
|
34
|
+
files:
|
35
|
+
- LICENSE
|
36
|
+
- Rakefile
|
37
|
+
- README.textile
|
38
|
+
- init.rb
|
39
|
+
- lib/strongbox/lock.rb
|
40
|
+
- lib/strongbox.rb
|
41
|
+
- rails/init.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://stuff-things.net/strongbox
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Secures ActiveRecord fields with public key encryption.
|
70
|
+
test_files: []
|
71
|
+
|