strongbox 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +10 -0
- data/README.md +271 -0
- data/Rakefile +2 -2
- data/gemfiles/.gitignore +1 -0
- data/gemfiles/2.3.gemfile +6 -0
- data/gemfiles/3.0.gemfile +6 -0
- data/gemfiles/3.1.gemfile +6 -0
- data/gemfiles/3.2.gemfile +6 -0
- data/lib/strongbox.rb +1 -1
- data/lib/strongbox/lock.rb +3 -1
- data/strongbox.gemspec +4 -2
- data/test/method_key_test.rb +77 -0
- data/test/proc_key_test.rb +57 -0
- data/test/strongbox_multiply_test.rb +1 -1
- data/test/strongbox_test.rb +3 -3
- data/test/test_helper.rb +2 -4
- data/test/validations_test.rb +0 -2
- metadata +69 -12
- data/README.textile +0 -182
data/.travis.yml
ADDED
data/README.md
ADDED
@@ -0,0 +1,271 @@
|
|
1
|
+
# Strongbox
|
2
|
+
|
3
|
+
Strongbox provides [Public Key
|
4
|
+
Encryption](http://en.wikipedia.org/wiki/Public-key_cryptography) for
|
5
|
+
ActiveRecord. By using a public key, sensitive information can be
|
6
|
+
encrypted and stored automatically. Once stored a password is required
|
7
|
+
to access the information.
|
8
|
+
|
9
|
+
Because the largest amount of data that can practically be encrypted
|
10
|
+
with a public key is 245 bytes, by default Strongbox uses a two layer
|
11
|
+
approach. First it encrypts the attribute using symmetric encryption
|
12
|
+
with a randomly generated key and initialization vector (IV) (which
|
13
|
+
can just be thought of as a second key), then it encrypts those with
|
14
|
+
the public key.
|
15
|
+
|
16
|
+
Strongbox stores the encrypted attribute in a database column by the
|
17
|
+
same name, i.e. if you tell Strongbox to encrypt "secret" then it will
|
18
|
+
be store in `secret` in the database, just as the unencrypted
|
19
|
+
attribute would be. If symmetric encryption is used (the default) two
|
20
|
+
additional columns `secret_key` and `secret_iv` are needed as well.
|
21
|
+
|
22
|
+
The attribute is automatically encrypted simply by setting it:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
user.secret = "Shhhhhhh..."
|
26
|
+
```
|
27
|
+
|
28
|
+
and decrypted by calling the `decrypt` method with the private key password.
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
plain_text = user.secret.decrypt 'letmein'
|
32
|
+
```
|
33
|
+
|
34
|
+
## Environment
|
35
|
+
|
36
|
+
Strongbox is tested against Rails 2.3 and 3.x using Ruby 1.8.7, 1.9.2,
|
37
|
+
and 1.9.3.
|
38
|
+
|
39
|
+
## Installation
|
40
|
+
|
41
|
+
Include the gem in your Gemfile:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
gem "strongbox"
|
45
|
+
```
|
46
|
+
|
47
|
+
Still using 2.x without a Gemfile? Put the following in
|
48
|
+
`config/environment.rb`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
config.gem "strongbox"
|
52
|
+
```
|
53
|
+
|
54
|
+
## Quick Start
|
55
|
+
|
56
|
+
In your model:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class User < ActiveRecord::Base
|
60
|
+
encrypt_with_public_key :secret,
|
61
|
+
:key_pair => Rails.root.join('config','keypair.pem')
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
In your migrations:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class AddSecretColumnsToUser < ActiveRecord::Migration
|
69
|
+
def change
|
70
|
+
add_column :users, :secret, :binary
|
71
|
+
add_column :users, :secret_key, :binary
|
72
|
+
add_column :users, :secret_iv, :binary
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
Generate a key pair:
|
78
|
+
|
79
|
+
(Choose a strong password.)
|
80
|
+
|
81
|
+
```shell
|
82
|
+
openssl genrsa -des3 -out config/private.pem 2048
|
83
|
+
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
84
|
+
cat config/private.pem config/public.pem >> config/keypair.pem
|
85
|
+
```
|
86
|
+
|
87
|
+
In your views and forms you don't need to do anything special to
|
88
|
+
encrypt data. To decrypt call:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
user.secret.decrypt 'password'
|
92
|
+
```
|
93
|
+
|
94
|
+
## Usage
|
95
|
+
|
96
|
+
The `encrypt_with_public_key` method sets up the attribute it's called
|
97
|
+
on for automatic encryption. It's simplest form is:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
class User < ActiveRecord::Base
|
101
|
+
encrypt_with_public_key :secret,
|
102
|
+
:key_pair => Rails.root.join('config','keypair.pem')
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Which will encrypt the attribute `secret`. The attribute will be
|
107
|
+
encrypted using symmetric encryption with an automatically generated
|
108
|
+
key and IV encrypted using the public key. This requires three columns
|
109
|
+
in the database `secret`, `secret_key`, and `secret_iv` (see below).
|
110
|
+
|
111
|
+
Options to `encrypt_with_public_key` are:
|
112
|
+
|
113
|
+
* `:public_key` - Public key. Overrides :key_pair. See Key Formats below.
|
114
|
+
|
115
|
+
* `:private_key` - Private key. Overrides :key_pair.
|
116
|
+
|
117
|
+
* `:key_pair` - Key pair, containing both the public and private keys.
|
118
|
+
|
119
|
+
* `:symmetric` `:always`/`:never` - Encrypt the date using symmetric encryption. The public key is used to encrypt an automatically generated key and IV. This allows for large amounts of data to be encrypted. The size of data that can be encrypted directly with the public is limit to key size (in bytes) - 11. So a 2048 key can encrypt *245 bytes*. Defaults to `:always`.
|
120
|
+
|
121
|
+
* `:symmetric_cipher` - Cipher to use for symmetric encryption. Defaults to `aes-256-cbc`. Other ciphers support by OpenSSL may be used.
|
122
|
+
|
123
|
+
* `:base64` `true`/`false` - Use Base64 encoding to convert encrypted data to text. Use when binary save data storage is not available. Defaults to `false`.
|
124
|
+
|
125
|
+
* `:padding` - Method used to pad data encrypted with the public key. Defaults to `RSA_PKCS1_PADDING`. The default should be fine unless you are dealing with legacy data.
|
126
|
+
|
127
|
+
* `:ensure_required_columns` - Make sure the required database column(s) exist. Defaults to `true`, set to `false` if you want to encrypt/decrypt data stored outside of the database.
|
128
|
+
|
129
|
+
For example, encrypting a small attribute, providing only the public
|
130
|
+
key for extra security, and Base64 encoding the encrypted data:
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
class User < ActiveRecord::Base
|
134
|
+
validates_length_of :pin_code, :is => 4
|
135
|
+
encrypt_with_public_key :pin_code,
|
136
|
+
:symmetric => :never,
|
137
|
+
:base64 => true,
|
138
|
+
:public_key => Rails.root.join('config','public.pem')
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
Strongbox can encrypt muliple attributes. `encrypt_with_public_key`
|
143
|
+
accepts a list of attributes, assuming they will use the same options:
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
class User < ActiveRecord::Base
|
147
|
+
encrypt_with_public_key :secret, :double_secret,
|
148
|
+
:key_pair => Rails.root.join('config','keypair.pem')
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
If you need different options, call `encrypt_with_public_key` for each
|
153
|
+
attribute:
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class User < ActiveRecord::Base
|
157
|
+
encrypt_with_public_key :secret,
|
158
|
+
:key_pair => Rails.root.join('config','keypair.pem')
|
159
|
+
encrypt_with_public_key :double_secret,
|
160
|
+
:key_pair => Rails.root.join('config','another_key.pem')
|
161
|
+
end
|
162
|
+
```
|
163
|
+
|
164
|
+
## Key Formats
|
165
|
+
|
166
|
+
`:public_key`, `:private_key`, and `:key_pair` can be in one of the
|
167
|
+
following formats:
|
168
|
+
|
169
|
+
* A string containing path to a file. This is the default interpretation of a string.
|
170
|
+
* A string contanting a key in PEM format, needs to match this the regex `/^-+BEGIN .* KEY-+$/`
|
171
|
+
* A symbol naming a method to call. Can return any of the other valid key formats.
|
172
|
+
* A instance of `OpenSSL::PKey::RSA`. Must be unlocked to be used as the private key.
|
173
|
+
|
174
|
+
## Key Generation
|
175
|
+
|
176
|
+
### In the shell
|
177
|
+
|
178
|
+
Generate a key pair:
|
179
|
+
|
180
|
+
```shell
|
181
|
+
openssl genrsa -des3 -out config/private.pem 2048
|
182
|
+
Generating RSA private key, 2048 bit long modulus
|
183
|
+
......+++
|
184
|
+
.+++
|
185
|
+
e is 65537 (0x10001)
|
186
|
+
Enter pass phrase for config/private.pem:
|
187
|
+
Verifying - Enter pass phrase for config/private.pem:
|
188
|
+
```
|
189
|
+
|
190
|
+
and extract the the public key:
|
191
|
+
|
192
|
+
```shell
|
193
|
+
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
194
|
+
Enter pass phrase for config/private.pem:
|
195
|
+
writing RSA key
|
196
|
+
```
|
197
|
+
|
198
|
+
If you are going to leave the private key installed it's easiest to
|
199
|
+
create a single key pair file:
|
200
|
+
|
201
|
+
```shell
|
202
|
+
cat config/private.pem config/public.pem >> config/keypair.pem
|
203
|
+
```
|
204
|
+
|
205
|
+
Or, for added security, store the private key file else where, leaving
|
206
|
+
only the public key.
|
207
|
+
|
208
|
+
### In code
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
require 'openssl'
|
212
|
+
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
213
|
+
cipher = OpenSSL::Cipher::Cipher.new('des3')
|
214
|
+
private_key = rsa_key.to_pem(cipher,'password')
|
215
|
+
public_key = rsa_key.public_key.to_pem
|
216
|
+
key_pair = private_key + public_key
|
217
|
+
```
|
218
|
+
|
219
|
+
`private_key`, `public_key`, and `key_pair` are strings, store as you see fit.
|
220
|
+
|
221
|
+
## Table Creation
|
222
|
+
|
223
|
+
In it's default configuration Strongbox requires three columns, one
|
224
|
+
the encrypted data, one for the encrypted symmetric key, and one for
|
225
|
+
the encrypted symmetric IV. If symmetric encryption is disabled then
|
226
|
+
only the columns for the data being encrypted is needed.
|
227
|
+
|
228
|
+
If your underlying database allows, use the **binary** column type. If
|
229
|
+
you must store your data in text format be sure to enable Base64
|
230
|
+
encoding and to use the *text* column type. If you use a *string*
|
231
|
+
column and encrypt anything greater than 186 bytes (245 bytes if you
|
232
|
+
don't enable Base64 encoding) **your data will be lost**.
|
233
|
+
|
234
|
+
## Nil and Blank Attributes
|
235
|
+
|
236
|
+
By default, attributes set to nil will remain encrypted to protect all
|
237
|
+
information about the attribute. However, attributes may be set back
|
238
|
+
to true nil explicitly:
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
# Outside the model
|
242
|
+
@object[:secret] = nil # or ''
|
243
|
+
# Inside the model
|
244
|
+
self[:secret] = '' # or nil
|
245
|
+
```
|
246
|
+
|
247
|
+
A setting to allow nil and blank attributes by default will be forth
|
248
|
+
coming.
|
249
|
+
|
250
|
+
## Security Caveats
|
251
|
+
|
252
|
+
If you don't encrypt your data, then an attacker only needs to steal
|
253
|
+
that data to get your secrets.
|
254
|
+
|
255
|
+
If encrypt your data using symmetric encrypts and a stored key, then
|
256
|
+
the attacker needs the data and the key stored on the server.
|
257
|
+
|
258
|
+
If you use public key encryption, the attacker needs the data, the
|
259
|
+
private key, and the password. This means the attacker has to sniff
|
260
|
+
the password somehow, so that's what you need to protect against.
|
261
|
+
|
262
|
+
## Authors
|
263
|
+
|
264
|
+
Spike Ilacqua
|
265
|
+
|
266
|
+
## Thanks
|
267
|
+
|
268
|
+
Strongbox's implementation drew inspiration from Thoughtbot's
|
269
|
+
[Paperclip gem](https://github.com/thoughtbot/paperclip).
|
270
|
+
|
271
|
+
Thanks to everyone who's contributed!
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ Bundler::GemHelper.install_tasks
|
|
3
3
|
|
4
4
|
require 'rake'
|
5
5
|
require 'rake/testtask'
|
6
|
-
require '
|
6
|
+
require 'rdoc/task'
|
7
7
|
|
8
8
|
$LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
|
9
9
|
require 'strongbox'
|
@@ -36,4 +36,4 @@ end
|
|
36
36
|
desc "Build the gem"
|
37
37
|
task :build => :gemspec do
|
38
38
|
Gem::Builder.new($spec).build
|
39
|
-
end
|
39
|
+
end
|
data/gemfiles/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.lock
|
data/lib/strongbox.rb
CHANGED
data/lib/strongbox/lock.rb
CHANGED
@@ -143,7 +143,9 @@ private
|
|
143
143
|
|
144
144
|
return key if key.is_a?(OpenSSL::PKey::RSA)
|
145
145
|
|
146
|
-
if key
|
146
|
+
if key.respond_to?(:read)
|
147
|
+
key = key.read
|
148
|
+
elsif key !~ /^-+BEGIN .* KEY-+$/
|
147
149
|
key = File.read(key)
|
148
150
|
end
|
149
151
|
return OpenSSL::PKey::RSA.new(key,password)
|
data/strongbox.gemspec
CHANGED
@@ -18,6 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.files = `git ls-files`.split("\n")
|
19
19
|
s.test_files = `git ls-files -- test/*`.split("\n")
|
20
20
|
s.add_runtime_dependency 'activerecord'
|
21
|
-
s.add_development_dependency 'thoughtbot-shoulda'
|
22
|
-
s.add_development_dependency 'sqlite3'
|
21
|
+
s.add_development_dependency 'thoughtbot-shoulda', '>= 2.9.0'
|
22
|
+
s.add_development_dependency 'sqlite3', '~> 1.3.7'
|
23
|
+
s.add_development_dependency 'rake', '>= 10.0.0'
|
24
|
+
s.add_development_dependency 'rdoc', '>= 2.4.0'
|
23
25
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class MethodKeyTest < Test::Unit::TestCase
|
4
|
+
context 'With an attribute containing a string for the key pair' do
|
5
|
+
setup do
|
6
|
+
@password = 'boost facile'
|
7
|
+
rebuild_model :key_pair => :key_pair_attribute
|
8
|
+
Dummy.class_eval do
|
9
|
+
attr_accessor :key_pair_attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
@dummy = Dummy.new
|
13
|
+
@dummy.key_pair_attribute = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
14
|
+
@dummy.secret = 'Shhhh'
|
15
|
+
end
|
16
|
+
|
17
|
+
should_encypted_and_decrypt
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'With a methods returning the key pair' do
|
21
|
+
setup do
|
22
|
+
@password = 'boost facile'
|
23
|
+
rebuild_model :key_pair => :key_pair_method
|
24
|
+
Dummy.class_eval do
|
25
|
+
def key_pair_method
|
26
|
+
File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@dummy = Dummy.new
|
31
|
+
@dummy.secret = 'Shhhh'
|
32
|
+
end
|
33
|
+
|
34
|
+
should_encypted_and_decrypt
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'With attributes containing strings for the keys' do
|
38
|
+
setup do
|
39
|
+
@password = 'boost facile'
|
40
|
+
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
41
|
+
cipher = OpenSSL::Cipher::Cipher.new('des3')
|
42
|
+
rebuild_model :public_key => :public_key_attribute,
|
43
|
+
:private_key => :private_key_attribute
|
44
|
+
Dummy.class_eval do
|
45
|
+
attr_accessor :public_key_attribute, :private_key_attribute
|
46
|
+
end
|
47
|
+
@dummy = Dummy.new
|
48
|
+
@dummy.public_key_attribute = rsa_key.public_key.to_pem
|
49
|
+
@dummy.private_key_attribute = rsa_key.to_pem(cipher,@password)
|
50
|
+
@dummy.secret = 'Shhhh'
|
51
|
+
end
|
52
|
+
|
53
|
+
should_encypted_and_decrypt
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'With methods returning the keys' do
|
57
|
+
setup do
|
58
|
+
@password = 'boost facile'
|
59
|
+
rebuild_model :public_key => :public_key_method,
|
60
|
+
:private_key => :private_key_method
|
61
|
+
Dummy.class_eval do
|
62
|
+
def public_key_method
|
63
|
+
File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
64
|
+
end
|
65
|
+
|
66
|
+
def private_key_method
|
67
|
+
File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@dummy = Dummy.new
|
72
|
+
@dummy.secret = 'Shhhh'
|
73
|
+
end
|
74
|
+
|
75
|
+
should_encypted_and_decrypt
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'test/test_helper'
|
2
|
+
|
3
|
+
class ProcKeyTest < Test::Unit::TestCase
|
4
|
+
context 'With a Proc returning a string for a key pair' do
|
5
|
+
setup do
|
6
|
+
@password = 'boost facile'
|
7
|
+
rebuild_model :key_pair => Proc.new {
|
8
|
+
File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
9
|
+
}
|
10
|
+
@dummy = Dummy.new
|
11
|
+
@dummy.secret = 'Shhhh'
|
12
|
+
end
|
13
|
+
|
14
|
+
should_encypted_and_decrypt
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'With a Proc returning a key object' do
|
18
|
+
setup do
|
19
|
+
@password = 'boost facile'
|
20
|
+
@private_key = OpenSSL::PKey::RSA.new(2048)
|
21
|
+
rebuild_model :key_pair => Proc.new { @private_key }
|
22
|
+
@dummy = Dummy.new
|
23
|
+
@dummy.secret = 'Shhhh'
|
24
|
+
end
|
25
|
+
|
26
|
+
should_encypted_and_decrypt
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'With Procs returning public and private key strings' do
|
30
|
+
setup do
|
31
|
+
@password = 'boost facile'
|
32
|
+
@key_pair = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
|
33
|
+
|
34
|
+
rebuild_model :public_key => Proc.new { @key_pair },
|
35
|
+
:private_key => Proc.new { @key_pair }
|
36
|
+
@dummy = Dummy.new
|
37
|
+
@dummy.secret = 'Shhhh'
|
38
|
+
end
|
39
|
+
|
40
|
+
should_encypted_and_decrypt
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'With Procs returning public and private key objects' do
|
44
|
+
setup do
|
45
|
+
@password = 'boost facile'
|
46
|
+
@private_key = OpenSSL::PKey::RSA.new(2048)
|
47
|
+
@public_key = @private_key.public_key
|
48
|
+
|
49
|
+
rebuild_model :public_key => Proc.new { @public_key },
|
50
|
+
:private_key => Proc.new { @private_key }
|
51
|
+
@dummy = Dummy.new
|
52
|
+
@dummy.secret = 'Shhhh'
|
53
|
+
end
|
54
|
+
|
55
|
+
should_encypted_and_decrypt
|
56
|
+
end
|
57
|
+
end
|
data/test/strongbox_test.rb
CHANGED
@@ -46,7 +46,7 @@ class StrongboxTest < Test::Unit::TestCase
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
should '
|
49
|
+
should 'implement to_json' do
|
50
50
|
assert_nothing_raised do
|
51
51
|
@dummy.secret.to_json
|
52
52
|
end
|
@@ -156,11 +156,11 @@ class StrongboxTest < Test::Unit::TestCase
|
|
156
156
|
end
|
157
157
|
|
158
158
|
context 'when a private key is not provided' do
|
159
|
-
|
159
|
+
setup do
|
160
160
|
@password = 'boost facile'
|
161
161
|
rebuild_class(:public_key => File.join(FIXTURES_DIR,'keypair.pem'))
|
162
162
|
@dummy = Dummy.new(:secret => 'Shhhh')
|
163
|
-
|
163
|
+
end
|
164
164
|
|
165
165
|
should 'raise on decrypt with a password' do
|
166
166
|
assert_raises(Strongbox::StrongboxError) do
|
data/test/test_helper.rb
CHANGED
@@ -2,12 +2,10 @@ ROOT = File.join(File.dirname(__FILE__), '..')
|
|
2
2
|
RAILS_ROOT = ROOT
|
3
3
|
$LOAD_PATH << File.join(ROOT, 'lib')
|
4
4
|
|
5
|
-
require 'rubygems'
|
6
5
|
require 'test/unit'
|
7
6
|
require 'sqlite3'
|
8
7
|
require 'active_record'
|
9
8
|
require 'logger'
|
10
|
-
gem 'thoughtbot-shoulda', ">= 2.9.0"
|
11
9
|
require 'shoulda'
|
12
10
|
begin require 'redgreen'; rescue LoadError; end
|
13
11
|
|
@@ -55,12 +53,12 @@ end
|
|
55
53
|
|
56
54
|
def assert_has_errors_on(model,attribute)
|
57
55
|
# Rails 2.X && Rails 3.X
|
58
|
-
!model.errors[attribute].empty?
|
56
|
+
assert !model.errors[attribute].empty?
|
59
57
|
end
|
60
58
|
|
61
59
|
def assert_does_not_have_errors_on(model,attribute)
|
62
60
|
# Rails 2.X Rails 3.X
|
63
|
-
model.errors[attribute].nil? || model.errors[attribute].empty?
|
61
|
+
assert model.errors[attribute].nil? || model.errors[attribute].empty?
|
64
62
|
end
|
65
63
|
|
66
64
|
def generate_key_pair(password = nil,size = 2048)
|
data/test/validations_test.rb
CHANGED
@@ -26,7 +26,6 @@ class ValidationsTest < Test::Unit::TestCase
|
|
26
26
|
|
27
27
|
context 'using validates_length_of' do
|
28
28
|
setup do
|
29
|
-
rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'))
|
30
29
|
Dummy.send(:validates_length_of,
|
31
30
|
:secret,
|
32
31
|
:in => 5..10,
|
@@ -64,7 +63,6 @@ class ValidationsTest < Test::Unit::TestCase
|
|
64
63
|
if defined?(ActiveModel::Validations) # Rails 3
|
65
64
|
context 'using validates for length' do
|
66
65
|
setup do
|
67
|
-
rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'))
|
68
66
|
Dummy.send(:validates,
|
69
67
|
:secret,
|
70
68
|
:length => {:minimum => 4, :maximum => 16})
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strongbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,29 +21,76 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
25
30
|
- !ruby/object:Gem::Dependency
|
26
31
|
name: thoughtbot-shoulda
|
27
|
-
requirement:
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
28
33
|
none: false
|
29
34
|
requirements:
|
30
35
|
- - ! '>='
|
31
36
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
37
|
+
version: 2.9.0
|
33
38
|
type: :development
|
34
39
|
prerelease: false
|
35
|
-
version_requirements:
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.9.0
|
36
46
|
- !ruby/object:Gem::Dependency
|
37
47
|
name: sqlite3
|
38
|
-
requirement:
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.3.7
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.7
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
39
65
|
none: false
|
40
66
|
requirements:
|
41
67
|
- - ! '>='
|
42
68
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
69
|
+
version: 10.0.0
|
44
70
|
type: :development
|
45
71
|
prerelease: false
|
46
|
-
version_requirements:
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 10.0.0
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rdoc
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.4.0
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 2.4.0
|
47
94
|
description: ! " Strongbox provides Public Key Encryption for ActiveRecord. By
|
48
95
|
using a\n public key sensitive information can be encrypted and stored automatically.\n
|
49
96
|
\ Once stored a password is required to access the information. dependencies\n
|
@@ -54,10 +101,16 @@ extensions: []
|
|
54
101
|
extra_rdoc_files: []
|
55
102
|
files:
|
56
103
|
- .gitignore
|
104
|
+
- .travis.yml
|
57
105
|
- Gemfile
|
58
106
|
- LICENSE
|
59
|
-
- README.
|
107
|
+
- README.md
|
60
108
|
- Rakefile
|
109
|
+
- gemfiles/.gitignore
|
110
|
+
- gemfiles/2.3.gemfile
|
111
|
+
- gemfiles/3.0.gemfile
|
112
|
+
- gemfiles/3.1.gemfile
|
113
|
+
- gemfiles/3.2.gemfile
|
61
114
|
- init.rb
|
62
115
|
- lib/strongbox.rb
|
63
116
|
- lib/strongbox/lock.rb
|
@@ -66,7 +119,9 @@ files:
|
|
66
119
|
- test/database.yml
|
67
120
|
- test/fixtures/encrypted
|
68
121
|
- test/fixtures/keypair.pem
|
122
|
+
- test/method_key_test.rb
|
69
123
|
- test/missing_attributes_test.rb
|
124
|
+
- test/proc_key_test.rb
|
70
125
|
- test/strongbox_multiply_test.rb
|
71
126
|
- test/strongbox_test.rb
|
72
127
|
- test/test_helper.rb
|
@@ -91,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
146
|
version: '0'
|
92
147
|
requirements: []
|
93
148
|
rubyforge_project:
|
94
|
-
rubygems_version: 1.8.
|
149
|
+
rubygems_version: 1.8.24
|
95
150
|
signing_key:
|
96
151
|
specification_version: 3
|
97
152
|
summary: Secures ActiveRecord fields with public key encryption.
|
@@ -99,7 +154,9 @@ test_files:
|
|
99
154
|
- test/database.yml
|
100
155
|
- test/fixtures/encrypted
|
101
156
|
- test/fixtures/keypair.pem
|
157
|
+
- test/method_key_test.rb
|
102
158
|
- test/missing_attributes_test.rb
|
159
|
+
- test/proc_key_test.rb
|
103
160
|
- test/strongbox_multiply_test.rb
|
104
161
|
- test/strongbox_test.rb
|
105
162
|
- test/test_helper.rb
|
data/README.textile
DELETED
@@ -1,182 +0,0 @@
|
|
1
|
-
h1. Strongbox
|
2
|
-
|
3
|
-
Strongbox provides Public Key Encryption for ActiveRecord. By using a public key sensitive information can be encrypted and stored automatically. Once stored a password is required to access the information.
|
4
|
-
|
5
|
-
Because the largest amount of data that can practically be encrypted with a public key is 245 byte, by default Strongbox uses a two layer approach. First it encrypts the attribute using symmetric encryption with a randomly generated key and initialization vector (IV) (which can just be through to as a second key), then it encrypts those with the public key.
|
6
|
-
|
7
|
-
Strongbox stores the encrypted attribute in a database column by the same name, i.e. if you tell Strongbox to encrypt "secret" then it will be store in "secret" in the database, just as the unencrypted attribute would be. If symmetric encryption is used (the default) two additional columns "secret_key" and "secret_iv" are needed as well.
|
8
|
-
|
9
|
-
The attribute is automatically encrypted simply by setting it:
|
10
|
-
|
11
|
-
user.secret = "Shhhhhhh..."
|
12
|
-
|
13
|
-
and decrypted by calling the "decrypt" method with the private key password.
|
14
|
-
|
15
|
-
plain_text = user.secret.decrypt 'letmein'
|
16
|
-
|
17
|
-
h2. Quick Start
|
18
|
-
|
19
|
-
In your model:
|
20
|
-
|
21
|
-
bc. class User < ActiveRecord::Base
|
22
|
-
encrypt_with_public_key :secret,
|
23
|
-
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
24
|
-
end
|
25
|
-
|
26
|
-
In your migrations:
|
27
|
-
|
28
|
-
bc. class AddSecretColumnsToUser < ActiveRecord::Migration
|
29
|
-
def self.up
|
30
|
-
add_column :users, :secret, :binary
|
31
|
-
add_column :users, :secret_key, :binary
|
32
|
-
add_column :users, :secret_iv, :binary
|
33
|
-
end
|
34
|
-
def self.down
|
35
|
-
remove_column :users, :secret
|
36
|
-
remove_column :users, :secret_key
|
37
|
-
remove_column :users, :secret_iv
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
Generate a key pair:
|
42
|
-
|
43
|
-
(Choose a strong password.)
|
44
|
-
|
45
|
-
bc. openssl genrsa -des3 -out config/private.pem 2048
|
46
|
-
openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
47
|
-
cat config/private.pem config/public.pem >> config/keypair.pem
|
48
|
-
|
49
|
-
In your views and forms you don't need to do anything special to encrypt data. To decrypt call:
|
50
|
-
|
51
|
-
bc. user.secret.decrypt 'password'
|
52
|
-
|
53
|
-
h2. Gem installation (Rails 2.1+)
|
54
|
-
|
55
|
-
In config/environment.rb:
|
56
|
-
|
57
|
-
bc. config.gem "strongbox"
|
58
|
-
|
59
|
-
h2. Usage
|
60
|
-
|
61
|
-
_encrypt_with_public_key_ sets up the attribute it's called on for automatic encryption. It's simplest form is:
|
62
|
-
|
63
|
-
bc. class User < ActiveRecord::Base
|
64
|
-
encrypt_with_public_key :secret,
|
65
|
-
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
66
|
-
end
|
67
|
-
|
68
|
-
Which will encrypt the attribute "secret". The attribute will be encrypted using symmetric encryption with an automatically generated key and IV encrypted using the public key. This requires three columns in the database "secret", "secret_key", and "secret_iv" (see below).
|
69
|
-
|
70
|
-
Options to encrypt_with_public_key are:
|
71
|
-
|
72
|
-
:public_key - Public key. Overrides :key_pair. See Key Formats below.
|
73
|
-
|
74
|
-
:private_key - Private key. Overrides :key_pair.
|
75
|
-
|
76
|
-
:key_pair - Key pair, containing both the public and private keys.
|
77
|
-
|
78
|
-
:symmetric :always/:never - Encrypt the date using symmetric encryption. The public key is used to encrypt an automatically generated key and IV. This allows for large amounts of data to be encrypted. The size of data that can be encrypted directly with the public is limit to key size (in bytes) - 11. So a 2048 key can encrypt *245 bytes*. Defaults to *:always*.
|
79
|
-
|
80
|
-
:symmetric_cipher - Cipher to use for symmetric encryption. Defaults to *'aes-256-cbc'*. Other ciphers support by OpenSSL may be used.
|
81
|
-
|
82
|
-
:base64 true/false - Use Base64 encoding to convert encrypted data to text. Use when binary save data storage is not available. Defaults to *false*.
|
83
|
-
|
84
|
-
:padding - Method used to pad data encrypted with the public key. Defaults to *RSA_PKCS1_PADDING*. The default should be fine unless you are dealing with legacy data.
|
85
|
-
|
86
|
-
:ensure_required_columns - Make sure the required database column(s) exist. Defaults to *true*, set to false if you want to encrypt/decrypt data stored outside of the database.
|
87
|
-
|
88
|
-
For example, encrypting a small attribute, providing only the public key for extra security, and Base64 encoding the encrypted data:
|
89
|
-
|
90
|
-
bc. class User < ActiveRecord::Base
|
91
|
-
validates_length_of :pin_code, :is => 4
|
92
|
-
encrypt_with_public_key :pin_code,
|
93
|
-
:symmetric => :never,
|
94
|
-
:base64 => true,
|
95
|
-
:public_key => File.join(RAILS_ROOT,'config','public.pem')
|
96
|
-
end
|
97
|
-
|
98
|
-
Strongbox can encrypt muliple attributes. _encrypt_with_public_key_ accepts a list of attributes, assuming they will use the same options:
|
99
|
-
|
100
|
-
bc. class User < ActiveRecord::Base
|
101
|
-
encrypt_with_public_key :secret, :double_secret,
|
102
|
-
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
103
|
-
end
|
104
|
-
|
105
|
-
If you need different options, call _encrypt_with_public_key_ for each attribute:
|
106
|
-
|
107
|
-
bc. class User < ActiveRecord::Base
|
108
|
-
encrypt_with_public_key :secret,
|
109
|
-
:key_pair => File.join(RAILS_ROOT,'config','keypair.pem')
|
110
|
-
encrypt_with_public_key :double_secret,
|
111
|
-
:key_pair => File.join(RAILS_ROOT,'config','another_key.pem')
|
112
|
-
end
|
113
|
-
|
114
|
-
h2 Key Formats
|
115
|
-
|
116
|
-
_:public_key_, _:private_key_, and _:key_pair_ can be in one of the following formats:
|
117
|
-
|
118
|
-
* A string containing path to a file. This is the default interpretation of a string.
|
119
|
-
* A string contanting a key in PEM format, needs to match this the regex /^-+BEGIN .* KEY-+$/
|
120
|
-
* A symbol naming a method to call. Can return any of the other valid key formats.
|
121
|
-
* A instance of OpenSSL::PKey::RSA. Must be unlocked to be used as the private key.
|
122
|
-
|
123
|
-
h2. Key Generation
|
124
|
-
|
125
|
-
h3. In the shell
|
126
|
-
|
127
|
-
Generate a key pair:
|
128
|
-
|
129
|
-
bc. openssl genrsa -des3 -out config/private.pem 2048
|
130
|
-
Generating RSA private key, 2048 bit long modulus
|
131
|
-
......+++
|
132
|
-
.+++
|
133
|
-
e is 65537 (0x10001)
|
134
|
-
Enter pass phrase for config/private.pem:
|
135
|
-
Verifying - Enter pass phrase for config/private.pem:
|
136
|
-
|
137
|
-
and extract the the public key:
|
138
|
-
|
139
|
-
bc. openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
|
140
|
-
Enter pass phrase for config/private.pem:
|
141
|
-
writing RSA key
|
142
|
-
|
143
|
-
If you are going to leave the private key installed it's easiest to create a single key pair file:
|
144
|
-
|
145
|
-
bc. cat config/private.pem config/public.pem >> config/keypair.pem
|
146
|
-
|
147
|
-
Or, for added security, store the private key file else where, leaving only the public key.
|
148
|
-
|
149
|
-
h3. In code
|
150
|
-
|
151
|
-
bc. require 'openssl'
|
152
|
-
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
153
|
-
cipher = OpenSSL::Cipher::Cipher.new('des3')
|
154
|
-
private_key = rsa_key.to_pem(cipher,'password')
|
155
|
-
public_key = rsa_key.public_key.to_pem
|
156
|
-
key_pair = private_key + public_key
|
157
|
-
|
158
|
-
_private_key_, _public_key_, and _key_pair_ are strings, store as you see fit.
|
159
|
-
|
160
|
-
h2. Table Creation
|
161
|
-
|
162
|
-
In it's default configuration Strongbox requires three columns, one the encrypted data, one for the encrypted symmetric key, and one for the encrypted symmetric IV. If symmetric encryption is disabled then only the columns for the data being encrypted is needed.
|
163
|
-
|
164
|
-
If your underlying database allows, use the *binary* column type. If you must store your data in text format be sure to enable Base64 encoding and to use the *text* column type. If you use a _string_ column and encrypt anything greater than 186 bytes (245 bytes if you don't enable Base64 encoding) *your data will be lost*.
|
165
|
-
|
166
|
-
|
167
|
-
h2. Security Caveats
|
168
|
-
|
169
|
-
If you don't encrypt your data, then an attacker only needs to steal that data to get your secrets.
|
170
|
-
|
171
|
-
If encrypt your data using symmetric encrypts and a stored key, then the attacker needs the data and the key stored on the server.
|
172
|
-
|
173
|
-
If you use public key encryption, the attacker needs the data, the private key, and the password. This means the attacker has to sniff the password somehow, so that's what you need to protect against.
|
174
|
-
|
175
|
-
h2. Authors
|
176
|
-
|
177
|
-
Spike Ilacqua
|
178
|
-
|
179
|
-
h2. Thanks
|
180
|
-
|
181
|
-
Strongbox's implementation drew inspiration from Thoughtbot's Paperclip gem http://www.thoughtbot.com/projects/paperclip
|
182
|
-
|