rjharmon-strongbox 0.3.0
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/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
|
+
|