strongbox 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ test/debug.log
2
+ doc
3
+ /strongbox-*.gem
data/Rakefile CHANGED
@@ -24,22 +24,13 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
24
24
  rdoc.rdoc_files.include('lib/**/*.rb')
25
25
  end
26
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_runtime_dependency 'activerecord'
36
- s.add_development_dependency 'thoughtbot-shoulda'
37
- s.add_development_dependency 'sqlite3'
38
- end
39
-
40
27
  desc "Generate a gemspec file for GitHub"
41
28
  task :gemspec do
42
- File.open("#{spec.name}.gemspec", 'w') do |f|
43
- f.write spec.to_yaml
44
- end
29
+ $spec = eval(File.read('strongbox.gemspec'))
30
+ $spec.validate
31
+ end
32
+
33
+ desc "Build the gem"
34
+ task :build => :gemspec do
35
+ Gem::Builder.new($spec).build
45
36
  end
data/lib/strongbox.rb CHANGED
@@ -5,7 +5,7 @@ require 'strongbox/lock'
5
5
 
6
6
  module Strongbox
7
7
 
8
- VERSION = "0.4.2"
8
+ VERSION = "0.4.3"
9
9
 
10
10
  RSA_PKCS1_PADDING = OpenSSL::PKey::RSA::PKCS1_PADDING
11
11
  RSA_SSLV23_PADDING = OpenSSL::PKey::RSA::SSLV23_PADDING
data/strongbox.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+ require 'strongbox'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "strongbox"
7
+ s.version = Strongbox::VERSION
8
+ s.summary = "Secures ActiveRecord fields with public key encryption."
9
+ s.authors = ["Spike Ilacqua"]
10
+ s.email = "spike@stuff-things.net"
11
+ s.description = <<-EOF
12
+ Strongbox provides Public Key Encryption for ActiveRecord. By using a
13
+ public key sensitive information can be encrypted and stored automatically.
14
+ Once stored a password is required to access the information. dependencies
15
+ are specified in standard Ruby syntax.
16
+ EOF
17
+ s.homepage = "http://stuff-things.net/strongbox"
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- test/*`.split("\n")
20
+ s.add_runtime_dependency 'activerecord'
21
+ s.add_development_dependency 'thoughtbot-shoulda'
22
+ s.add_development_dependency 'sqlite3'
23
+ end
data/test/database.yml ADDED
@@ -0,0 +1,4 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: ":memory:"
4
+
Binary file
@@ -0,0 +1,24 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ Proc-Type: 4,ENCRYPTED
3
+ DEK-Info: DES-EDE3-CBC,317921A00FB0882F
4
+
5
+ f+GWBkcLJLsBUElOEKhqrtYgT1X4nixaZHD5x0VhmW2FrREz4vcqXrxwLTaRQJK/
6
+ vHFJ/7IVmEHScwEognSfw/wX2HMIHczoQT3ugsa29Nt7t1VLGy9jvN1+1f+g90xe
7
+ 02jC7CYEKUJ3agZPox49i0/UN9OCIgdtKfecdDHYWyziob8yYTsUdDGyAXlPv0Kx
8
+ 0MPSCRDtEh4UJ2PIFyw2HowkYeNss6uIte9rxJGINI11D9vmXR0pH0XyCwHQn+2T
9
+ ScHWg8BJ1rkBKydbKQ4vnfhGMjG+bZyrJXrJSoazXroseuhHu8QRUONm5Kl/zW1f
10
+ GP1CjIfTCQQZECYIa2tXTFdL9y2ZOCn8xit57SwEpmJMvZC58PkQX5+/aHPcOXhl
11
+ YrF+6FEfNpdBz9PUmv4Af2kTa88xZqm1Q3GtTOk7wsJpfeTMhU71KjA1pL9xNPrT
12
+ DnKhtfLGvcgo8Z9BGOiLFe9uQvhhprX7isc1XdysbMigsVIWLvZp9RxRp/zAn7fy
13
+ y56C6mc3tUwcq89RcxAn+bC75gwZO/hyVrnkhManOMfHTEiZXVybU9Ril3SZ+ry6
14
+ 8AxMid0ZWbbtCHdDc5rHfXsGeFhJZxBbg/WtMxBPGHNByqs8sWUM9Z8YoK8WMYxV
15
+ GvC9RB4m0jgA4S3MEOMmKOXDuJxa7IgTgApVmLPl+sDOHGK3xAItYJJawJqOZQ1f
16
+ r+x/8g19CuehuflCxDo+D4/RJMqkOEq+0FGUqI8lHv6vR6+YpkGdrQQXUohBy67f
17
+ 3Qym1ztZ8ygsttgJwnhwAfMh8FdIrVJc7NZ8pDiBZbg=
18
+ -----END RSA PRIVATE KEY-----
19
+ -----BEGIN PUBLIC KEY-----
20
+ MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9F1ipsLL+V68bGSJFqFLQKgXq
21
+ Glyyplx0s9KxgLbmbDICXpV7DceKaIBUkPZDx2DrlvjZmG+rG5ehdWNI7q/hupao
22
+ NF0WzEiOp+30gISeyl81Z/NAmhcwcOnZpbS9nl4JLaWrN7iGC1geNBNDo+lVbsm1
23
+ O2+Tlt8rjHsNjzgIzQIDAQAB
24
+ -----END PUBLIC KEY-----
@@ -0,0 +1,77 @@
1
+ require 'test/test_helper'
2
+
3
+ class MissingAttribuesTest < Test::Unit::TestCase
4
+ context 'A Class with a secured field without a matching database column' do
5
+ setup do
6
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
7
+ table.string :in_the_clear
8
+ end
9
+ rebuild_class {}
10
+ end
11
+
12
+ should 'raise' do
13
+ assert_raise(Strongbox::StrongboxError) do
14
+ Dummy.class_eval do
15
+ encrypt_with_public_key :secret, :key_pair =>
16
+ File.join(FIXTURES_DIR,'keypair.pem')
17
+ end
18
+ @dummy = Dummy.new
19
+ @dummy.secret = 'Shhhh'
20
+ end
21
+ end
22
+
23
+ teardown do
24
+ rebuild_model
25
+ end
26
+ end
27
+
28
+ context 'A Class with a secured field missing symmetric database columns' do
29
+ setup do
30
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
31
+ table.string :in_the_clear
32
+ table.string :secret
33
+ end
34
+ rebuild_class {}
35
+ end
36
+
37
+ should 'raise' do
38
+ assert_raise(Strongbox::StrongboxError) do
39
+ Dummy.class_eval do
40
+ encrypt_with_public_key :secret, :key_pair =>
41
+ File.join(FIXTURES_DIR,'keypair.pem')
42
+ end
43
+ @dummy = Dummy.new
44
+ @dummy.secret = 'Shhhh'
45
+ end
46
+ end
47
+
48
+ teardown do
49
+ rebuild_model
50
+ end
51
+ end
52
+
53
+ context 'A Class with a secured field without a matching database column told not to check columns' do
54
+ setup do
55
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
56
+ table.string :in_the_clear
57
+ end
58
+ rebuild_class {}
59
+ end
60
+
61
+ should 'not raise' do
62
+ assert_nothing_raised do
63
+ Dummy.class_eval do
64
+ encrypt_with_public_key(:secret,
65
+ :key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
66
+ :ensure_required_columns => false)
67
+ end
68
+ @dummy = Dummy.new
69
+ @dummy.secret = 'Shhhh'
70
+ end
71
+ end
72
+
73
+ teardown do
74
+ rebuild_model
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,303 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/test_helper'
3
+
4
+ class StrongboxTest < Test::Unit::TestCase
5
+ context 'A Class with a secured field' do
6
+ setup do
7
+ @password = 'boost facile'
8
+ rebuild_model :key_pair => File.join(FIXTURES_DIR,'keypair.pem')
9
+ end
10
+
11
+ should 'not error when trying to also create a secure field' do
12
+ assert_nothing_raised do
13
+ Dummy.class_eval do
14
+ encrypt_with_public_key :secret
15
+ end
16
+ end
17
+ end
18
+
19
+ context 'that is valid' do
20
+ setup do
21
+ @dummy = Dummy.new
22
+ @dummy.secret = 'Shhhh'
23
+ @dummy.in_the_clear = 'Hey you guys!'
24
+ end
25
+
26
+ should 'not change unencrypted fields' do
27
+ assert_equal 'Hey you guys!', @dummy.in_the_clear
28
+ end
29
+
30
+ should 'return "*encrypted*" when locked' do
31
+ assert_equal '*encrypted*', @dummy.secret.decrypt
32
+ end
33
+
34
+ should 'return secret when unlocked' do
35
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
36
+ end
37
+
38
+ should 'generate and store symmetric encryption key and IV' do
39
+ assert_not_nil @dummy.attributes['secret_key']
40
+ assert_not_nil @dummy.attributes['secret_iv']
41
+ end
42
+
43
+ should 'raise on bad password' do
44
+ assert_raises(OpenSSL::PKey::RSAError) do
45
+ @dummy.secret.decrypt('letmein')
46
+ end
47
+ end
48
+
49
+ context 'updating unencrypted fields' do
50
+ setup do
51
+ @dummy.in_the_clear = 'I see you...'
52
+ @dummy.save
53
+ end
54
+
55
+ should 'not effect the secret' do
56
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
57
+ end
58
+ end
59
+
60
+ context 'updating the secret' do
61
+ setup do
62
+ @dummy.secret = @new_secret = 'Don\'t tell'
63
+ @dummy.save
64
+ end
65
+
66
+ should 'update the secret' do
67
+ assert_equal @new_secret, @dummy.secret.decrypt(@password)
68
+ end
69
+ end
70
+
71
+ context 'with symmetric encryption disabled' do
72
+ setup do
73
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
74
+ :symmetric => :never)
75
+ @dummy = Dummy.new
76
+ @dummy.secret = 'Shhhh'
77
+ end
78
+
79
+ should 'return "*encrypted*" when locked' do
80
+ assert_equal '*encrypted*', @dummy.secret.decrypt
81
+ end
82
+
83
+ should 'return secret when unlocked' do
84
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
85
+ end
86
+
87
+ should 'allow decryption of other strings encrypted with the same key' do
88
+ encrypted_text = File.read(File.join(FIXTURES_DIR,'encrypted'))
89
+ assert_equal 'Setec Astronomy', @dummy.secret.decrypt(@password, encrypted_text)
90
+ end
91
+
92
+ should 'not generate and store symmetric encryption key and IV' do
93
+ assert_nil @dummy.attributes['secret_key']
94
+ assert_nil @dummy.attributes['secret_iv']
95
+ end
96
+
97
+ end
98
+
99
+ context 'with Base64 encoding enabled' do
100
+ setup do
101
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
102
+ :base64 => true)
103
+ @dummy = Dummy.new
104
+ @dummy.secret = 'Shhhh'
105
+ end
106
+
107
+ should 'Base64 encode the ciphertext' do
108
+ # Base64 encoded text is limited to the charaters A–Z, a–z, and 0–9,
109
+ # and is padded with 0 to 2 equal-signs
110
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret']
111
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret_key']
112
+ assert_match /^[0-9A-Za-z+\/]+={0,2}$/, @dummy.attributes['secret_iv']
113
+ end
114
+
115
+ should 'encrypt the data' do
116
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
117
+ assert_equal '*encrypted*', @dummy.secret.decrypt
118
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
119
+ end
120
+ end
121
+ end
122
+
123
+ context 'using blowfish cipher instead of AES' do
124
+ setup do
125
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'),
126
+ :symmetric_cipher => 'bf-cbc')
127
+ @dummy = Dummy.new
128
+ @dummy.secret = 'Shhhh'
129
+ end
130
+
131
+ should 'encrypt the data' do
132
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
133
+ assert_equal '*encrypted*', @dummy.secret.decrypt
134
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
135
+ end
136
+ end
137
+ end
138
+
139
+ context 'when a public key is not provided' do
140
+ setup do
141
+ rebuild_class
142
+ @dummy = Dummy.new
143
+ end
144
+
145
+ should 'raise on encrypt' do
146
+ assert_raises(Strongbox::StrongboxError) do
147
+ @dummy.secret = 'Shhhh'
148
+ end
149
+ end
150
+ end
151
+
152
+ context 'when a private key is not provided' do
153
+ setup do
154
+ @password = 'boost facile'
155
+ rebuild_class(:public_key => File.join(FIXTURES_DIR,'keypair.pem'))
156
+ @dummy = Dummy.new(:secret => 'Shhhh')
157
+ end
158
+
159
+ should 'raise on decrypt with a password' do
160
+ assert_raises(Strongbox::StrongboxError) do
161
+ @dummy.secret.decrypt(@password)
162
+ end
163
+ end
164
+
165
+ should 'return "*encrypted*" when still locked' do
166
+ assert_equal '*encrypted*', @dummy.secret.decrypt
167
+ end
168
+ end
169
+
170
+ context "when an unencrypted public key is used" do
171
+ setup do
172
+ rebuild_class(:public_key => generate_key_pair.public_key)
173
+ @dummy = Dummy.new(:secret => 'Shhhh')
174
+ end
175
+
176
+ should "encrypt the data" do
177
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
178
+ assert_equal '*encrypted*', @dummy.secret.decrypt
179
+ end
180
+ end
181
+
182
+ context "when an unencrypted key pair is used" do
183
+ setup do
184
+ rebuild_class(:key_pair => generate_key_pair)
185
+ @dummy = Dummy.new(:secret => 'Shhhh')
186
+ end
187
+
188
+ should "encrypt the data" do
189
+ assert_not_equal @dummy.attributes['secret'], 'Shhhh'
190
+ assert_equal "Shhhh", @dummy.secret.decrypt('')
191
+ end
192
+ end
193
+
194
+ context 'with validations' do
195
+ context 'using validates_presence_of' do
196
+ setup do
197
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'))
198
+ Dummy.send(:validates_presence_of, :secret)
199
+ @valid = Dummy.new(:secret => 'Shhhh')
200
+ @invalid = Dummy.new(:secret => nil)
201
+ end
202
+
203
+ should 'not have an error on the secret when valid' do
204
+ assert @valid.valid?
205
+ assert_does_not_have_errors_on(@valid,:secret)
206
+ end
207
+
208
+ should 'have an error on the secret when invalid' do
209
+ assert !@invalid.valid?
210
+ assert_has_errors_on(@invalid,:secret)
211
+ end
212
+ end
213
+
214
+ context 'using validates_length_of' do
215
+ setup do
216
+ rebuild_class(:key_pair => File.join(FIXTURES_DIR,'keypair.pem'))
217
+ Dummy.send(:validates_length_of,
218
+ :secret,
219
+ :in => 5..10,
220
+ :allow_nil => true,
221
+ :allow_blank => true
222
+ )
223
+ @valid = Dummy.new(:secret => 'Shhhh')
224
+ @valid_nil = Dummy.new(:secret => nil)
225
+ @valid_blank = Dummy.new(:secret => '')
226
+ @invalid = Dummy.new(:secret => '1')
227
+ end
228
+
229
+ should 'not have an error on the secret when in range' do
230
+ assert @valid.valid?
231
+ assert_does_not_have_errors_on(@valid,:secret)
232
+ end
233
+
234
+ should 'not have an error on the secret when nil' do
235
+ assert @valid_nil.valid?
236
+ assert_does_not_have_errors_on(@valid_nil,:secret)
237
+ end
238
+
239
+ should 'not have an error on the secret when blank' do
240
+ assert @valid_blank.valid?
241
+ assert_does_not_have_errors_on(@valid_blank,:secret)
242
+ end
243
+
244
+ should 'have an error on the secret when invalid' do
245
+ assert !@invalid.valid?
246
+ assert_has_errors_on(@invalid,:secret)
247
+ end
248
+ end
249
+ end
250
+
251
+ context 'A Class with two secured fields' do
252
+ setup do
253
+ @password = 'boost facile'
254
+ key_pair = File.join(FIXTURES_DIR,'keypair.pem')
255
+ Dummy.class_eval do
256
+ encrypt_with_public_key :secret, :key_pair => key_pair
257
+ encrypt_with_public_key :segreto, :key_pair => key_pair,
258
+ :symmetric => :never
259
+ end
260
+ end
261
+
262
+ context 'that is valid' do
263
+ setup do
264
+ @dummy = Dummy.new
265
+ @dummy.secret = 'I have a secret...'
266
+ @dummy.segreto = 'Ho un segreto...'
267
+ end
268
+
269
+ should 'return "*encrypted*" when the record is locked' do
270
+ assert_equal '*encrypted*', @dummy.secret.decrypt
271
+ assert_equal '*encrypted*', @dummy.segreto.decrypt
272
+ end
273
+
274
+ should 'return the secrets when unlocked' do
275
+ assert_equal 'I have a secret...', @dummy.secret.decrypt(@password)
276
+ assert_equal 'Ho un segreto...', @dummy.segreto.decrypt(@password)
277
+ end
278
+
279
+ end
280
+ end
281
+
282
+ context 'Using strings for keys' do
283
+ setup do
284
+ @password = 'boost facile'
285
+ key_pair = File.read(File.join(FIXTURES_DIR,'keypair.pem'))
286
+ public_key = OpenSSL::PKey::RSA.new(key_pair,"")
287
+ private_key = OpenSSL::PKey::RSA.new(key_pair,@password)
288
+ Dummy.class_eval do
289
+ encrypt_with_public_key :secret, :public_key => public_key, :private_key => private_key
290
+ end
291
+ @dummy = Dummy.new
292
+ @dummy.secret = 'Shhhh'
293
+ end
294
+
295
+ should 'return "*encrypted*" when locked' do
296
+ assert_equal '*encrypted*', @dummy.secret.decrypt
297
+ end
298
+
299
+ should 'return secret when unlocked' do
300
+ assert_equal 'Shhhh', @dummy.secret.decrypt(@password)
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,73 @@
1
+ ROOT = File.join(File.dirname(__FILE__), '..')
2
+ RAILS_ROOT = ROOT
3
+ $LOAD_PATH << File.join(ROOT, 'lib')
4
+
5
+ require 'rubygems'
6
+ require 'test/unit'
7
+ require 'sqlite3'
8
+ require 'active_record'
9
+ require 'logger'
10
+ gem 'thoughtbot-shoulda', ">= 2.9.0"
11
+ require 'shoulda'
12
+ begin require 'redgreen'; rescue LoadError; end
13
+
14
+ require 'strongbox'
15
+
16
+ ENV['RAILS_ENV'] ||= 'test'
17
+
18
+ FIXTURES_DIR = File.join(File.dirname(__FILE__), "fixtures")
19
+ config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
20
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
21
+ ActiveRecord::Base.establish_connection(config['test'])
22
+
23
+
24
+ # rebuild_model and rebuild_class are borrowed directly from the Paperclip gem
25
+ #
26
+ # http://thoughtbot.com/projects/paperclip
27
+
28
+ # rebuild_model (re)creates a database table for our Dummy model.
29
+ # Call this to initial create a model, or to reset the database.
30
+
31
+ def rebuild_model options = {}
32
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
33
+ table.string :in_the_clear
34
+ table.binary :secret
35
+ table.binary :secret_key
36
+ table.binary :secret_iv
37
+ table.binary :segreto
38
+ end
39
+ rebuild_class options
40
+ end
41
+
42
+ # rebuild_class creates or replaces the Dummy ActiveRecord Model.
43
+ # Call this when changing the options to encrypt_with_public_key
44
+
45
+ def rebuild_class options = {}
46
+ ActiveRecord::Base.send(:include, Strongbox)
47
+ Object.send(:remove_const, "Dummy") rescue nil
48
+ Object.const_set("Dummy", Class.new(ActiveRecord::Base))
49
+ Dummy.class_eval do
50
+ include Strongbox
51
+ encrypt_with_public_key :secret, options
52
+ end
53
+ end
54
+
55
+ def assert_has_errors_on(model,attribute)
56
+ # Rails 2.X && Rails 3.X
57
+ !model.errors[attribute].empty?
58
+ end
59
+
60
+ def assert_does_not_have_errors_on(model,attribute)
61
+ # Rails 2.X Rails 3.X
62
+ model.errors[attribute].nil? || model.errors[attribute].empty?
63
+ end
64
+
65
+ def generate_key_pair(password = nil,size = 2048)
66
+ rsa_key = OpenSSL::PKey::RSA.new(size)
67
+ # If no password is provided, don't encrypt the key
68
+ return rsa_key if password.blank?
69
+ cipher = OpenSSL::Cipher::Cipher.new('des3')
70
+ key_pair = rsa_key.to_pem(cipher,password)
71
+ key_pair << rsa_key.public_key.to_pem
72
+ return key_pair
73
+ end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strongbox
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 3
10
+ version: 0.4.3
5
11
  platform: ruby
6
12
  authors:
7
13
  - Spike Ilacqua
@@ -9,40 +15,52 @@ autorequire:
9
15
  bindir: bin
10
16
  cert_chain: []
11
17
 
12
- date: 2011-01-17 00:00:00 -07:00
18
+ date: 2011-02-21 00:00:00 -07:00
13
19
  default_executable:
14
20
  dependencies:
15
21
  - !ruby/object:Gem::Dependency
16
22
  name: activerecord
17
- type: :runtime
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
20
26
  requirements:
21
27
  - - ">="
22
28
  - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
23
32
  version: "0"
24
- version:
33
+ type: :runtime
34
+ version_requirements: *id001
25
35
  - !ruby/object:Gem::Dependency
26
36
  name: thoughtbot-shoulda
27
- type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
30
40
  requirements:
31
41
  - - ">="
32
42
  - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
33
46
  version: "0"
34
- version:
47
+ type: :development
48
+ version_requirements: *id002
35
49
  - !ruby/object:Gem::Dependency
36
50
  name: sqlite3
37
- type: :development
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
40
54
  requirements:
41
55
  - - ">="
42
56
  - !ruby/object:Gem::Version
57
+ hash: 3
58
+ segments:
59
+ - 0
43
60
  version: "0"
44
- version:
45
- description:
61
+ type: :development
62
+ version_requirements: *id003
63
+ description: " Strongbox provides Public Key Encryption for ActiveRecord. By using a\n public key sensitive information can be encrypted and stored automatically.\n Once stored a password is required to access the information. dependencies\n are specified in standard Ruby syntax.\n"
46
64
  email: spike@stuff-things.net
47
65
  executables: []
48
66
 
@@ -51,14 +69,21 @@ extensions: []
51
69
  extra_rdoc_files: []
52
70
 
53
71
  files:
72
+ - .gitignore
54
73
  - LICENSE
55
- - Rakefile
56
- - README.html
57
74
  - README.textile
75
+ - Rakefile
58
76
  - init.rb
59
- - lib/strongbox/lock.rb
60
77
  - lib/strongbox.rb
78
+ - lib/strongbox/lock.rb
61
79
  - rails/init.rb
80
+ - strongbox.gemspec
81
+ - test/database.yml
82
+ - test/fixtures/encrypted
83
+ - test/fixtures/keypair.pem
84
+ - test/missing_attributes_test.rb
85
+ - test/strongbox_test.rb
86
+ - test/test_helper.rb
62
87
  has_rdoc: true
63
88
  homepage: http://stuff-things.net/strongbox
64
89
  licenses: []
@@ -69,23 +94,34 @@ rdoc_options: []
69
94
  require_paths:
70
95
  - lib
71
96
  required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
72
98
  requirements:
73
99
  - - ">="
74
100
  - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
75
104
  version: "0"
76
- version:
77
105
  required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
78
107
  requirements:
79
108
  - - ">="
80
109
  - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
81
113
  version: "0"
82
- version:
83
114
  requirements: []
84
115
 
85
116
  rubyforge_project:
86
- rubygems_version: 1.3.5
117
+ rubygems_version: 1.5.2
87
118
  signing_key:
88
119
  specification_version: 3
89
120
  summary: Secures ActiveRecord fields with public key encryption.
90
- test_files: []
91
-
121
+ test_files:
122
+ - test/database.yml
123
+ - test/fixtures/encrypted
124
+ - test/fixtures/keypair.pem
125
+ - test/missing_attributes_test.rb
126
+ - test/strongbox_test.rb
127
+ - test/test_helper.rb
data/README.html DELETED
@@ -1,88 +0,0 @@
1
- <h1>Strongbox</h1>
2
- <p>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.</p>
3
- <p>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.</p>
4
- <p>Strongbox stores the encrypted attribute in a database column by the same name, i.e. if you tell Strongbox to encrypt &#8220;secret&#8221; then it will be store in &#8220;secret&#8221; in the database, just as the unencrypted attribute would be. If symmetric encryption is used (the default) two additional columns &#8220;secret_key&#8221; and &#8220;secret_iv&#8221; are needed as well.</p>
5
- <p>The attribute is automatically encrypted simply by setting it:</p>
6
- user.secret = &#8220;Shhhhhhh&#8230;&#8221;
7
- <p>and decrypted by calling the &#8220;decrypt&#8221; method with the private key password.</p>
8
- plain_text = user.secret.decrypt &#8216;letmein&#8217;
9
- <h2>Quick Start</h2>
10
- <p>In your model:</p>
11
- <pre><code>class User &lt; ActiveRecord::Base
12
- encrypt_with_public_key :secret,
13
- :key_pair =&gt; File.join(RAILS_ROOT,'config','keypair.pem')
14
- end</code></pre>
15
- <p>In your migrations:</p>
16
- <pre><code>class AddSecretColumnsToUser &lt; ActiveRecord::Migration
17
- def self.up
18
- add_column :users, :secret, :binary
19
- add_column :users, :secret_key, :binary
20
- add_column :users, :secret_iv, :binary
21
- end
22
- def self.down
23
- remove_column :users, :secret
24
- remove_column :users, :secret_key
25
- remove_column :users, :secret_iv
26
- end
27
- end</code></pre>
28
- <p>Generate a key pair:</p>
29
- <p>(Choose a strong password.)</p>
30
- <pre><code>openssl genrsa -des3 -out config/private.pem 2048
31
- openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
32
- cat config/private.pem config/public.pem &gt;&gt; config/keypair.pem</code></pre>
33
- <p>In your views and forms you don&#8217;t need to do anything special to encrypt data. To decrypt call:</p>
34
- <pre><code>user.secret.decrypt 'password'</code></pre>
35
- <h2>Gem installation (Rails 2.1+)</h2>
36
- <p>In config/environment.rb:</p>
37
- <pre><code>config.gem "strongbox"</code></pre>
38
- <h2>Usage</h2>
39
- <p><em>encrypt_with_public_key</em> sets up the attribute it&#8217;s called on for automatic encryption. It&#8217;s simplest form is:</p>
40
- <pre><code>class User &lt; ActiveRecord::Base
41
- encrypt_with_public_key :secret,
42
- :key_pair =&gt; File.join(RAILS_ROOT,'config','keypair.pem')
43
- end</code></pre>
44
- <p>Which will encrypt the attribute &#8220;secret&#8221;. 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 &#8220;secret&#8221;, &#8220;secret_key&#8221;, and &#8220;secret_iv&#8221; (see below).</p>
45
- <p>Options to encrypt_with_public_key are:</p>
46
- <p>:public_key &#8211; Path to the public key file. Overrides :keypair.</p>
47
- <p>:private_key &#8211; Path to the private key file. Overrides :keypair.</p>
48
- <p>:keypair &#8211; Path to a file containing both the public and private keys.</p>
49
- <p>:symmetric :always/:never &#8211; 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) &#8211; 11. So a 2048 key can encrypt <strong>245 bytes</strong>. Defaults to <strong>:always</strong>.</p>
50
- <p>:symmetric_cipher &#8211; Cipher to use for symmetric encryption. Defaults to <strong>&#8216;aes-256-cbc&#8217;</strong>. Other ciphers support by OpenSSL may be used.</p>
51
- <p>:base64 true/false &#8211; Use Base64 encoding to convert encrypted data to text. Use when binary save data storage is not available. Defaults to <strong>false</strong>.</p>
52
- <p>:padding &#8211; Method used to pad data encrypted with the public key. Defaults to <strong>RSA_PKCS1_PADDING</strong>. The default should be fine unless you are dealing with legacy data.</p>
53
- <p>:ensure_required_columns &#8211; Make sure the required database columns exist. Defaults to <strong>true</strong>, set to false if you want to encrypt/decrypt data stored outside of the database.</p>
54
- <p>For example, encrypting a small attribute, providing only the public key for extra security, and Base64 encoding the encrypted data:</p>
55
- <pre><code>class User &lt; ActiveRecord::Base
56
- validates_length_of :pin_code, :is =&gt; 4
57
- encrypt_with_public_key :pin_code,
58
- :symmetric =&gt; :never,
59
- :base64 =&gt; true,
60
- :public_key =&gt; File.join(RAILS_ROOT,'config','public.pem')
61
- end</code></pre>
62
- <h2>Key Generation</h2>
63
- <p>Generate a key pair:</p>
64
- <pre><code>openssl genrsa -des3 -out config/private.pem 2048
65
- Generating RSA private key, 2048 bit long modulus
66
- ......+++
67
- .+++
68
- e is 65537 (0x10001)
69
- Enter pass phrase for config/private.pem:
70
- Verifying - Enter pass phrase for config/private.pem:</code></pre>
71
- <p>and extract the the public key:</p>
72
- <pre><code>openssl rsa -in config/private.pem -out config/public.pem -outform PEM -pubout
73
- Enter pass phrase for config/private.pem:
74
- writing RSA key</code></pre>
75
- <p>If you are going to leave the private key installed it&#8217;s easiest to create a single key pair file:</p>
76
- <pre><code>cat config/private.pem config/public.pem &gt;&gt; config/keypair.pem</code></pre>
77
- <p>Or, for added security, store the private key file else where, leaving only the public key.</p>
78
- <h2>Table Creation</h2>
79
- <p>In it&#8217;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.</p>
80
- <p>If your underlying database allows, use the <strong>binary</strong> column type. If you must store your data in text format be sure to enable Base64 encoding and to use the <strong>text</strong> column type. If you use a <em>string</em> column and encrypt anything greater than 186 bytes (245 bytes if you don&#8217;t enable Base64 encoding) <strong>your data will be lost</strong>.</p>
81
- <h2>Security Caveats</h2>
82
- <p>If you don&#8217;t encrypt your data, then an attacker only needs to steal that data to get your secrets.</p>
83
- <p>If encrypt your data using symmetric encrypts and a stored key, then the attacker needs the data and the key stored on the server.</p>
84
- <p>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&#8217;s what you need to protect against.</p>
85
- <h2>Authors</h2>
86
- <p>Spike Ilacqua</p>
87
- <h2>Thanks</h2>
88
- <p>Strongbox&#8217;s implementation drew inspiration from Thoughtbot&#8217;s Paperclip gem http://www.thoughtbot.com/projects/paperclip</p>