symmetric-encryption 0.9.1 → 1.0.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.
- checksums.yaml +7 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +50 -0
- data/README.md +227 -130
- data/Rakefile +3 -1
- data/lib/symmetric_encryption/cipher.rb +62 -18
- data/lib/symmetric_encryption/symmetric_encryption.rb +3 -19
- data/lib/symmetric_encryption/version.rb +1 -1
- data/nbproject/private/private.properties +1 -1
- data/nbproject/private/private.xml +0 -3
- data/test/cipher_test.rb +13 -9
- data/test/config/database.yml +4 -6
- data/test/reader_test.rb +1 -1
- data/test/symmetric_encryption_test.rb +44 -54
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +1 -1
- metadata +17 -15
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ce8b40ac71f3b255ea4947fe3a41ba51145b5188
|
4
|
+
data.tar.gz: de4ceb7712388671b92054ed66409e78ddcf21bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 83166719c8132a10e4108c6d8e3822b61dc02be4bcd0159a47d44a6f23ebc914b8ab5cf2b979cd7a4fffc413f6d0a1aa0b6e7f211afa2e4031173f72cf2c5ef4
|
7
|
+
data.tar.gz: 1c0917a0a784610aaa517b526374fed613883203bb0d9ff04f56c6182581af415f4b2b73adb81c15948e8a5e2c2ed17d83d5820f9ed47b75498c4863d79c8668
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.2.9)
|
5
|
+
activesupport (= 3.2.9)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
activerecord (3.2.9)
|
8
|
+
activemodel (= 3.2.9)
|
9
|
+
activesupport (= 3.2.9)
|
10
|
+
arel (~> 3.0.2)
|
11
|
+
tzinfo (~> 0.3.29)
|
12
|
+
activesupport (3.2.9)
|
13
|
+
i18n (~> 0.6)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
arel (3.0.2)
|
16
|
+
bourne (1.1.2)
|
17
|
+
mocha (= 0.10.5)
|
18
|
+
builder (3.0.4)
|
19
|
+
i18n (0.6.1)
|
20
|
+
metaclass (0.0.1)
|
21
|
+
mocha (0.10.5)
|
22
|
+
metaclass (~> 0.0.1)
|
23
|
+
mongoid (3.0.6)
|
24
|
+
activemodel (~> 3.1)
|
25
|
+
moped (~> 1.1)
|
26
|
+
origin (~> 1.0)
|
27
|
+
tzinfo (~> 0.3.22)
|
28
|
+
moped (1.2.5)
|
29
|
+
multi_json (1.5.0)
|
30
|
+
origin (1.0.9)
|
31
|
+
shoulda (3.3.2)
|
32
|
+
shoulda-context (~> 1.0.1)
|
33
|
+
shoulda-matchers (~> 1.4.1)
|
34
|
+
shoulda-context (1.0.1)
|
35
|
+
shoulda-matchers (1.4.2)
|
36
|
+
activesupport (>= 3.0.0)
|
37
|
+
bourne (~> 1.1.2)
|
38
|
+
sqlite3 (1.3.6)
|
39
|
+
tzinfo (0.3.35)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
activerecord
|
46
|
+
activerecord-jdbcsqlite3-adapter
|
47
|
+
jdbc-sqlite3
|
48
|
+
mongoid
|
49
|
+
shoulda
|
50
|
+
sqlite3
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ created, managed and further secured by System Administrators. This prevents
|
|
27
27
|
developers having or needing to have access to the symmetric encryption keys
|
28
28
|
* The Operating System security features limit access to the Symmetric Encryption
|
29
29
|
key files to System Administrators and the userid under which the Rails application runs.
|
30
|
-
* The files in which the Symmetric Encryption keys are stored are
|
30
|
+
* The files in which the Symmetric Encryption keys are stored are further
|
31
31
|
encrypted using RSA 2048 bit encryption
|
32
32
|
|
33
33
|
In order for anyone to decrypt the data being encrypted in the database, they
|
@@ -38,6 +38,15 @@ by the Operating System
|
|
38
38
|
* The userid and password for the database to copy the encrypted data itself,
|
39
39
|
or an unsecured copy or export of the database contents
|
40
40
|
|
41
|
+
A major feature of symmetric encryption is that it makes the encryption and decryption
|
42
|
+
automatically available when the Rails application is started. This includes all
|
43
|
+
rake tasks and the Rails console. In this way data can be encrypted or decrypted as
|
44
|
+
part of any rake task.
|
45
|
+
|
46
|
+
From a security perspective it is important then to properly secure the system so that
|
47
|
+
no hacker can switch to and run as the rails user and thereby gain access to the
|
48
|
+
encryption and decryption capabilities
|
49
|
+
|
41
50
|
## Features
|
42
51
|
|
43
52
|
* Encryption of passwords in configuration files
|
@@ -59,37 +68,96 @@ are extended with encrypted_ behavior, rather than every object in the system
|
|
59
68
|
|
60
69
|
### Encryption Example
|
61
70
|
|
62
|
-
|
71
|
+
```ruby
|
72
|
+
SymmetricEncryption.encrypt "Sensitive data"
|
73
|
+
```
|
63
74
|
|
64
75
|
### Decryption Example
|
65
76
|
|
66
|
-
|
77
|
+
```ruby
|
78
|
+
SymmetricEncryption.decrypt "JqLJOi6dNjWI9kX9lSL1XQ==\n"
|
79
|
+
```
|
67
80
|
|
68
|
-
###
|
81
|
+
### ActiveRecord Example
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class User < ActiveRecord::Base
|
85
|
+
# Requires table users to have a column called encrypted_bank_account_number
|
86
|
+
attr_encrypted :bank_account_number
|
87
|
+
|
88
|
+
# Requires table users to have a column called encrypted_social_security_number
|
89
|
+
attr_encrypted :social_security_number
|
90
|
+
|
91
|
+
validates :encrypted_bank_account_number, :symmetric_encryption => true
|
92
|
+
validates :encrypted_social_security_number, :symmetric_encryption => true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Create a new user instance assigning a bank account number
|
96
|
+
user = User.new
|
97
|
+
user.bank_account_number = '12345'
|
98
|
+
|
99
|
+
# Saves the bank_account_number in the column encrypted_bank_account_number in
|
100
|
+
# encrypted form
|
101
|
+
user.save!
|
102
|
+
|
103
|
+
# Short example using create
|
104
|
+
User.create(:bank_account_number => '12345')
|
105
|
+
```
|
106
|
+
|
107
|
+
### Mongoid Example
|
69
108
|
|
70
|
-
|
71
|
-
|
72
|
-
end
|
109
|
+
To encrypt a field in a Mongoid document, just add ":encrypted => true" at the end
|
110
|
+
of the field specifier. The field name must currently begin with "encrypted_"
|
73
111
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
m.valid?
|
79
|
-
# => true
|
112
|
+
```ruby
|
113
|
+
# User model in Mongoid
|
114
|
+
class User
|
115
|
+
include Mongoid::Document
|
80
116
|
|
117
|
+
field :name, :type => String
|
118
|
+
field :encrypted_bank_account_number, :type => String, :encrypted => true
|
119
|
+
field :encrypted_social_security_number, :type => String, :encrypted => true
|
120
|
+
end
|
121
|
+
|
122
|
+
# Create a new user document
|
123
|
+
User.create(:bank_account_number => '12345')
|
124
|
+
|
125
|
+
# When finding a document, always use the encrypted form of the field name
|
126
|
+
user = User.where(:encrypted_bank_account_number => SymmetricEncryption.encrypt('12345')).first
|
127
|
+
|
128
|
+
# Fields can be accessed using their unencrypted names
|
129
|
+
puts user.bank_account_number
|
130
|
+
```
|
131
|
+
|
132
|
+
### Validation Example
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class MyModel < ActiveRecord::Base
|
136
|
+
validates :encrypted_ssn, :symmetric_encryption => true
|
137
|
+
end
|
138
|
+
|
139
|
+
m = MyModel.new
|
140
|
+
m.valid?
|
141
|
+
# => false
|
142
|
+
m.encrypted_ssn = SymmetricEncryption.encrypt('123456789')
|
143
|
+
m.valid?
|
144
|
+
# => true
|
145
|
+
```
|
81
146
|
### Encrypting Passwords in configuration files
|
82
147
|
|
83
148
|
Passwords can be encrypted in any YAML configuration file.
|
84
149
|
|
85
150
|
For example config/database.yml
|
86
151
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
152
|
+
```yaml
|
153
|
+
---
|
154
|
+
production:
|
155
|
+
adapter: mysql
|
156
|
+
host: db1w
|
157
|
+
database: myapp_production
|
158
|
+
username: admin
|
159
|
+
password: <%= SymmetricEncryption.try_decrypt "JqLJOi6dNjWI9kX9lSL1XQ==\n" %>
|
160
|
+
```
|
93
161
|
|
94
162
|
Note: Use SymmetricEncryption.try_decrypt method which will return nil if it
|
95
163
|
fails to decrypt the value, which is essential when the encryption keys differ
|
@@ -98,54 +166,75 @@ Note: Use SymmetricEncryption.try_decrypt method which will return nil if it
|
|
98
166
|
Note: In order for the above technique to work in other YAML configuration files
|
99
167
|
the YAML file must be processed using ERB prior to passing to YAML. For example
|
100
168
|
|
169
|
+
```ruby
|
101
170
|
config_file = Rails.root.join('config', 'redis.yml')
|
102
171
|
raise "redis config not found. Create a config file at: config/redis.yml" unless config_file.file?
|
103
172
|
|
104
173
|
cfg = YAML.load(ERB.new(File.new(config_file).read).result)[Rails.env]
|
105
174
|
raise("Environment #{Rails.env} not defined in redis.yml") unless cfg
|
175
|
+
```
|
106
176
|
|
107
177
|
### Large File Encryption
|
108
178
|
|
109
179
|
Example: Read and decrypt a line at a time from a file
|
110
180
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
181
|
+
```ruby
|
182
|
+
SymmetricEncryption::Reader.open('encrypted_file') do |file|
|
183
|
+
file.each_line do |line|
|
184
|
+
puts line
|
185
|
+
end
|
186
|
+
end
|
187
|
+
```
|
116
188
|
|
117
189
|
Example: Encrypt and write data to a file
|
118
190
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
191
|
+
```ruby
|
192
|
+
SymmetricEncryption::Writer.open('encrypted_file') do |file|
|
193
|
+
file.write "Hello World\n"
|
194
|
+
file.write "Keep this secret"
|
195
|
+
end
|
196
|
+
```
|
123
197
|
|
124
198
|
Example: Compress, Encrypt and write data to a file
|
125
199
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
200
|
+
```ruby
|
201
|
+
SymmetricEncryption::Writer.open('encrypted_compressed.zip', :compress => true) do |file|
|
202
|
+
file.write "Hello World\n"
|
203
|
+
file.write "Compress this\n"
|
204
|
+
file.write "Keep this safe and secure\n"
|
205
|
+
end
|
206
|
+
```
|
131
207
|
|
132
208
|
### Standalone test
|
133
209
|
|
134
210
|
Before generating keys we can use SymmetricEncryption in a standalone test environment:
|
135
211
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
212
|
+
```ruby
|
213
|
+
# Use test encryption keys
|
214
|
+
SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
|
215
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
216
|
+
:iv => '1234567890ABCDEF',
|
217
|
+
:cipher => 'aes-128-cbc'
|
218
|
+
)
|
219
|
+
encrypted = SymmetricEncryption.encrypt('hello world')
|
220
|
+
puts SymmetricEncryption.decrypt(encrypted)
|
221
|
+
```
|
222
|
+
|
223
|
+
### Rake Tasks
|
224
|
+
|
225
|
+
For PCI compliance developers should not be the ones creating or encrypting
|
226
|
+
passwords. The following rake tasks can be used by system administrators to
|
227
|
+
generate and encrypt passwords for databases, or external web calls.
|
228
|
+
It is safe to pass the encrypted password for say MySQL to the developers
|
229
|
+
who can then put it in the config files which are kept in source control.
|
230
|
+
|
231
|
+
Generate a random password and display its encrypted form:
|
232
|
+
|
233
|
+
rake symmetric_encryption:random_password
|
144
234
|
|
145
|
-
|
235
|
+
Encrypt a known value, such as a password:
|
146
236
|
|
147
|
-
|
148
|
-
specified environment
|
237
|
+
rake symmetric_encryption:encrypt
|
149
238
|
|
150
239
|
Note: Passwords must be encrypted in the environment in which they will be used.
|
151
240
|
Since each environment should have its own symmetric encryption keys
|
@@ -155,7 +244,9 @@ Note: Passwords must be encrypted in the environment in which they will be used.
|
|
155
244
|
### Add to an existing Rails project
|
156
245
|
Add the following line to Gemfile
|
157
246
|
|
158
|
-
|
247
|
+
```ruby
|
248
|
+
gem 'symmetric-encryption'
|
249
|
+
```
|
159
250
|
|
160
251
|
Install the Gem with bundler
|
161
252
|
|
@@ -244,8 +335,10 @@ one supplied in examples/symmetric-encryption.yml.
|
|
244
335
|
At application startup, run the code below to initialize symmetric-encryption prior to
|
245
336
|
attempting to encrypt or decrypt any data
|
246
337
|
|
247
|
-
|
248
|
-
|
338
|
+
```ruby
|
339
|
+
require 'symmetric-encryption'
|
340
|
+
SymmetricEncryption.load!('config/symmetric-encryption.yml', 'production')
|
341
|
+
```
|
249
342
|
|
250
343
|
Parameters:
|
251
344
|
|
@@ -254,8 +347,10 @@ Parameters:
|
|
254
347
|
|
255
348
|
To manually generate the symmetric encryption keys, run the code below
|
256
349
|
|
257
|
-
|
258
|
-
|
350
|
+
```ruby
|
351
|
+
require 'symmetric-encryption'
|
352
|
+
SymmetricEncryption.generate_symmetric_key_files('config/symmetric-encryption.yml', 'production')
|
353
|
+
```
|
259
354
|
|
260
355
|
Parameters:
|
261
356
|
|
@@ -280,87 +375,89 @@ use the same RSA Private key for gaining access to the Symmetric Encryption Keys
|
|
280
375
|
|
281
376
|
Create a configuration file in config/symmetric-encryption.yml per the following example:
|
282
377
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
378
|
+
```yaml
|
379
|
+
#
|
380
|
+
# Symmetric Encryption for Ruby
|
381
|
+
#
|
382
|
+
---
|
383
|
+
# For the development and test environments the test symmetric encryption keys
|
384
|
+
# can be placed directly in the source code.
|
385
|
+
# And therefore no RSA private key is required
|
386
|
+
development: &development_defaults
|
387
|
+
key: 1234567890ABCDEF1234567890ABCDEF
|
388
|
+
iv: 1234567890ABCDEF
|
389
|
+
cipher: aes-128-cbc
|
390
|
+
|
391
|
+
test:
|
392
|
+
<<: *development_defaults
|
393
|
+
|
394
|
+
production:
|
395
|
+
# Since the key to encrypt and decrypt with must NOT be stored along with the
|
396
|
+
# source code, we only hold a RSA key that is used to unlock the file
|
397
|
+
# containing the actual symmetric encryption key
|
398
|
+
#
|
399
|
+
# Sample RSA Key, DO NOT use this RSA key, generate a new one using
|
400
|
+
# openssl genrsa 2048
|
401
|
+
private_rsa_key: |
|
402
|
+
-----BEGIN RSA PRIVATE KEY-----
|
403
|
+
MIIEpAIBAAKCAQEAxIL9H/jYUGpA38v6PowRSRJEo3aNVXULNM/QNRpx2DTf++KH
|
404
|
+
6DcuFTFcNSSSxG9n4y7tKi755be8N0uwCCuOzvXqfWmXYjbLwK3Ib2vm0btpHyvA
|
405
|
+
qxgqeJOOCxKdW/cUFLWn0tACUcEjVCNfWEGaFyvkOUuR7Ub9KfhbW9cZO3BxZMUf
|
406
|
+
IPGlHl/gWyf484sXygd+S7cpDTRRzo9RjG74DwfE0MFGf9a1fTkxnSgeOJ6asTOy
|
407
|
+
fp9tEToUlbglKaYGpOGHYQ9TV5ZsyJ9jRUyb4SP5wK2eK6dHTxTcHvT03kD90Hv4
|
408
|
+
WeKIXv3WOjkwNEyMdpnJJfSDb5oquQvCNi7ZSQIDAQABAoIBAQCbzR7TUoBugU+e
|
409
|
+
ICLvpC2wOYOh9kRoFLwlyv3QnH7WZFWRZzFJszYeJ1xr5etXQtyjCnmOkGAg+WOI
|
410
|
+
k8GlOKOpAuA/PpB/leJFiYL4lBwU/PmDdTT0cdx6bMKZlNCeMW8CXGQKiFDOcMqJ
|
411
|
+
0uGtH5YD+RChPIEeFsJxnC8SyZ9/t2ra7XnMGiCZvRXIUDSEIIsRx/mOymJ7bL+h
|
412
|
+
Lbp46IfXf6ZuIzwzoIk0JReV/r+wdmkAVDkrrMkCmVS4/X1wN/Tiik9/yvbsh/CL
|
413
|
+
ztC55eSIEjATkWxnXfPASZN6oUfQPEveGH3HzNjdncjH/Ho8FaNMIAfFpBhhLPi9
|
414
|
+
nG5sbH+BAoGBAOdoUyVoAA/QUa3/FkQaa7Ajjehe5MR5k6VtaGtcxrLiBjrNR7x+
|
415
|
+
nqlZlGvWDMiCz49dgj+G1Qk1bbYrZLRX/Hjeqy5dZOGLMfgf9eKUmS1rDwAzBMcj
|
416
|
+
M9jnnJEBx8HIlNzaR6wzp3GMd0rrccs660A8URvzkgo9qNbvMLq9vyUtAoGBANll
|
417
|
+
SY1Iv9uaIz8klTXU9YzYtsfUmgXzw7K8StPdbEbo8F1J3JPJB4D7QHF0ObIaSWuf
|
418
|
+
suZqLsvWlYGuJeyX2ntlBN82ORfvUdOrdrbDlmPyj4PfFVl0AK3U3Ai374DNrjKR
|
419
|
+
hF6YFm4TLDaJhUjeV5C43kbE1N2FAMS9LYtPJ44NAoGAFDGHZ/E+aCLerddfwwun
|
420
|
+
MBS6MnftcLPHTZ1RimTrNfsBXipBw1ItWEvn5s0kCm9X24PmdNK4TnhqHYaF4DL5
|
421
|
+
ZjbQK1idEA2Mi8GGPIKJJ2x7P6I0HYiV4qy7fe/w1ZlCXE90B7PuPbtrQY9wO7Ll
|
422
|
+
ipJ45X6I1PnyfOcckn8yafUCgYACtPAlgjJhWZn2v03cTbqA9nHQKyV/zXkyUIXd
|
423
|
+
/XPLrjrP7ouAi5A8WuSChR/yx8ECRgrEM65Be3qBEtoGCB4AS1G0NcigM6qhKBFi
|
424
|
+
VS0aMXr3+V8argcUIwJaWW/x+p2go48yXlJpLHPweeXe8mXEt4iM+QZte6p2yKQ4
|
425
|
+
h9PGQQKBgQCqSydmXBnXGIVTp2sH/2GnpxLYnDBpcJE0tM8bJ42HEQQgRThIChsn
|
426
|
+
PnGA91G9MVikYapgI0VYBHQOTsz8rTIUzsKwXG+TIaK+W84nxH5y6jUkjqwxZmAz
|
427
|
+
r1URaMAun2PfAB4g2N/kEZTExgeOGqXjFhvvjdzl97ux2cTyZhaTXg==
|
428
|
+
-----END RSA PRIVATE KEY-----
|
429
|
+
|
430
|
+
# List Symmetric Key files in the order of current / latest first
|
431
|
+
ciphers:
|
432
|
+
-
|
433
|
+
# Filename containing Symmetric Encryption Key encrypted using the
|
434
|
+
# RSA public key derived from the private key above
|
435
|
+
key_filename: /etc/rails/.rails.key
|
436
|
+
iv_filename: /etc/rails/.rails.iv
|
437
|
+
|
438
|
+
# Encryption cipher
|
439
|
+
# Recommended values:
|
440
|
+
# aes-256-cbc
|
441
|
+
# 256 AES CBC Algorithm. Very strong
|
442
|
+
# Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
|
443
|
+
# JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
|
444
|
+
# aes-128-cbc
|
445
|
+
# 128 AES CBC Algorithm. Less strong.
|
446
|
+
# Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
|
447
|
+
# JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
|
448
|
+
cipher: aes-256-cbc
|
449
|
+
|
450
|
+
-
|
451
|
+
# OPTIONAL:
|
452
|
+
#
|
453
|
+
# Any previous Symmetric Encryption Keys
|
454
|
+
#
|
455
|
+
# Only used when old data still exists that requires old decryption keys
|
456
|
+
# to be used
|
457
|
+
key_filename: /etc/rails/.rails_old.key
|
458
|
+
iv_filename: /etc/rails/.rails_old.iv
|
459
|
+
cipher: aes-256-cbc
|
460
|
+
```
|
364
461
|
|
365
462
|
## Future Enhancements
|
366
463
|
|
data/Rakefile
CHANGED
@@ -2,6 +2,7 @@ lib = File.expand_path('../lib/', __FILE__)
|
|
2
2
|
$:.unshift lib unless $:.include?(lib)
|
3
3
|
|
4
4
|
require 'rubygems'
|
5
|
+
require 'rubygems/package'
|
5
6
|
require 'rake/clean'
|
6
7
|
require 'rake/testtask'
|
7
8
|
require 'date'
|
@@ -20,9 +21,10 @@ task :gem do |t|
|
|
20
21
|
s.summary = "Symmetric Encryption for Ruby, and Ruby on Rails"
|
21
22
|
s.description = "SymmetricEncryption supports encrypting ActiveRecord data, Mongoid data, passwords in configuration files, encrypting and decrypting of large files through streaming"
|
22
23
|
s.files = FileList["./**/*"].exclude(/.gem$/, /.log$/,/^nbproject/).map{|f| f.sub(/^\.\//, '')}
|
24
|
+
s.license = "Apache License V2.0"
|
23
25
|
s.has_rdoc = true
|
24
26
|
end
|
25
|
-
Gem::
|
27
|
+
Gem::Package.build gemspec
|
26
28
|
end
|
27
29
|
|
28
30
|
desc "Run Test Suite"
|
@@ -11,7 +11,7 @@ module SymmetricEncryption
|
|
11
11
|
attr_accessor :encoding
|
12
12
|
|
13
13
|
# Available encodings
|
14
|
-
ENCODINGS = [:none, :base64, :base64strict]
|
14
|
+
ENCODINGS = [:none, :base64, :base64strict, :base16]
|
15
15
|
|
16
16
|
# Generate a new Symmetric Key pair
|
17
17
|
#
|
@@ -51,7 +51,9 @@ module SymmetricEncryption
|
|
51
51
|
# It is not the default for backward compatibility
|
52
52
|
# :base64
|
53
53
|
# Return as a base64 encoded string
|
54
|
-
# :
|
54
|
+
# :base16
|
55
|
+
# Return as a Hex encoded string
|
56
|
+
# :none
|
55
57
|
# Return as raw binary data string. Note: String can contain embedded nulls
|
56
58
|
# Default: :base64
|
57
59
|
# Recommended: :base64strict
|
@@ -69,45 +71,53 @@ module SymmetricEncryption
|
|
69
71
|
raise("Invalid Encoding: #{@encoding}") unless ENCODINGS.include?(@encoding)
|
70
72
|
end
|
71
73
|
|
72
|
-
#
|
74
|
+
# Encryption of supplied string
|
73
75
|
# The String is encoded to UTF-8 prior to encryption
|
74
76
|
#
|
75
|
-
# Returns result as
|
77
|
+
# Returns result as an encoded string if encode is true
|
76
78
|
# Returns nil if the supplied str is nil
|
77
79
|
# Returns "" if it is a string and it is empty
|
78
80
|
if defined?(Encoding)
|
79
|
-
def encrypt(str)
|
81
|
+
def encrypt(str, encode = true)
|
80
82
|
return if str.nil?
|
81
83
|
buf = str.to_s.encode(SymmetricEncryption::UTF8_ENCODING)
|
82
84
|
return str if buf.empty?
|
83
|
-
crypt(:encrypt, buf)
|
85
|
+
encrypted = crypt(:encrypt, buf)
|
86
|
+
encode ? self.encode(encrypted) : encrypted
|
84
87
|
end
|
85
88
|
else
|
86
|
-
def encrypt(str)
|
89
|
+
def encrypt(str, encode = true)
|
87
90
|
return if str.nil?
|
88
91
|
buf = str.to_s
|
89
92
|
return str if buf.empty?
|
90
|
-
crypt(:encrypt, buf)
|
93
|
+
encrypted = crypt(:encrypt, buf)
|
94
|
+
encode ? self.encode(encrypted) : encrypted
|
91
95
|
end
|
92
96
|
end
|
93
97
|
|
94
|
-
#
|
95
|
-
#
|
98
|
+
# Decryption of supplied string
|
99
|
+
#
|
100
|
+
# Decodes string first if decode is true
|
101
|
+
#
|
96
102
|
# Returns a UTF-8 encoded, decrypted string
|
97
103
|
# Returns nil if the supplied str is nil
|
98
104
|
# Returns "" if it is a string and it is empty
|
99
105
|
if defined?(Encoding)
|
100
|
-
def decrypt(str)
|
101
|
-
|
102
|
-
|
103
|
-
|
106
|
+
def decrypt(str, decode = true)
|
107
|
+
decoded = self.decode(str) if decode
|
108
|
+
return unless decoded
|
109
|
+
|
110
|
+
buf = decoded.to_s.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
111
|
+
return decoded if buf.empty?
|
104
112
|
crypt(:decrypt, buf).force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
105
113
|
end
|
106
114
|
else
|
107
|
-
def decrypt(str)
|
108
|
-
|
109
|
-
|
110
|
-
|
115
|
+
def decrypt(str, decode = true)
|
116
|
+
decoded = self.decode(str) if decode
|
117
|
+
return unless decoded
|
118
|
+
|
119
|
+
buf = decoded.to_s
|
120
|
+
return decoded if buf.empty?
|
111
121
|
crypt(:decrypt, buf)
|
112
122
|
end
|
113
123
|
end
|
@@ -123,6 +133,40 @@ module SymmetricEncryption
|
|
123
133
|
::OpenSSL::Cipher::Cipher.new(@cipher).block_size
|
124
134
|
end
|
125
135
|
|
136
|
+
# Encode the supplied string using the encoding in this cipher instance
|
137
|
+
# Returns nil if the supplied string is nil
|
138
|
+
# Note: No encryption or decryption is performed
|
139
|
+
def encode(binary_string)
|
140
|
+
return unless binary_string
|
141
|
+
|
142
|
+
# Now encode data based on encoding setting
|
143
|
+
case encoding
|
144
|
+
when :base64
|
145
|
+
::Base64.encode64(binary_string)
|
146
|
+
when :base64strict
|
147
|
+
::Base64.encode64(binary_string).gsub(/\n/, '')
|
148
|
+
when :base16
|
149
|
+
binary_string.to_s.unpack('H*').first
|
150
|
+
else
|
151
|
+
binary_string
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Decode the supplied string using the encoding in this cipher instance
|
156
|
+
# Note: No encryption or decryption is performed
|
157
|
+
def decode(encoded_string)
|
158
|
+
return unless encoded_string
|
159
|
+
|
160
|
+
case encoding
|
161
|
+
when :base64, :base64strict
|
162
|
+
::Base64.decode64(encoded_string)
|
163
|
+
when :base16
|
164
|
+
[encoded_string].pack('H*')
|
165
|
+
else
|
166
|
+
encoded_string
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
126
170
|
protected
|
127
171
|
|
128
172
|
# Only for use by Symmetric::EncryptedStream
|
@@ -66,12 +66,7 @@ module SymmetricEncryption
|
|
66
66
|
def self.decrypt(str)
|
67
67
|
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
68
68
|
|
69
|
-
# Decode
|
70
|
-
case @@cipher.encoding
|
71
|
-
when :base64, :base64strict
|
72
|
-
str = ::Base64.decode64(str) if str
|
73
|
-
end
|
74
|
-
|
69
|
+
# Decode and then decrypt supplied string
|
75
70
|
begin
|
76
71
|
@@cipher.decrypt(str)
|
77
72
|
rescue OpenSSL::Cipher::CipherError => exc
|
@@ -92,19 +87,8 @@ module SymmetricEncryption
|
|
92
87
|
def self.encrypt(str)
|
93
88
|
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
94
89
|
|
95
|
-
# Encrypt
|
96
|
-
|
97
|
-
# Now encode data based on encoding setting
|
98
|
-
case @@cipher.encoding
|
99
|
-
when :base64
|
100
|
-
# Base 64 Encoding of binary data
|
101
|
-
::Base64.encode64(result)
|
102
|
-
when :base64strict
|
103
|
-
::Base64.encode64(result).gsub(/\n/, '')
|
104
|
-
else
|
105
|
-
result
|
106
|
-
end
|
107
|
-
end
|
90
|
+
# Encrypt and then encode the supplied string
|
91
|
+
@@cipher.encrypt(str)
|
108
92
|
end
|
109
93
|
|
110
94
|
# Invokes decrypt
|
@@ -14,7 +14,4 @@
|
|
14
14
|
<line>60</line>
|
15
15
|
</file>
|
16
16
|
</editor-bookmarks>
|
17
|
-
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
|
18
|
-
<file>file:/Users/rmorrison/Sandbox/symmetric-encryption/lib/symmetric_encryption/extensions/active_record/base.rb</file>
|
19
|
-
</open-files>
|
20
17
|
</project-private>
|
data/test/cipher_test.rb
CHANGED
@@ -13,16 +13,18 @@ class CipherTest < Test::Unit::TestCase
|
|
13
13
|
|
14
14
|
should "allow setting the cipher" do
|
15
15
|
cipher = SymmetricEncryption::Cipher.new(
|
16
|
-
:cipher
|
17
|
-
:key
|
18
|
-
:iv
|
16
|
+
:cipher => 'aes-128-cbc',
|
17
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
18
|
+
:iv => '1234567890ABCDEF',
|
19
|
+
:encoding => :none
|
19
20
|
)
|
20
21
|
assert_equal 'aes-128-cbc', cipher.cipher
|
21
22
|
end
|
22
23
|
|
23
24
|
should "not require an iv" do
|
24
25
|
cipher = SymmetricEncryption::Cipher.new(
|
25
|
-
:key
|
26
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
27
|
+
:encoding => :none
|
26
28
|
)
|
27
29
|
result = "\302<\351\227oj\372\3331\310\260V\001\v'\346"
|
28
30
|
# Note: This test fails on JRuby 1.7 RC1 since it's OpenSSL
|
@@ -35,9 +37,10 @@ class CipherTest < Test::Unit::TestCase
|
|
35
37
|
|
36
38
|
should "throw an exception on bad data" do
|
37
39
|
cipher = SymmetricEncryption::Cipher.new(
|
38
|
-
:cipher
|
39
|
-
:key
|
40
|
-
:iv
|
40
|
+
:cipher => 'aes-128-cbc',
|
41
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
42
|
+
:iv => '1234567890ABCDEF',
|
43
|
+
:encoding => :none
|
41
44
|
)
|
42
45
|
assert_raise OpenSSL::Cipher::CipherError do
|
43
46
|
cipher.decrypt('bad data')
|
@@ -49,8 +52,9 @@ class CipherTest < Test::Unit::TestCase
|
|
49
52
|
context 'with configuration' do
|
50
53
|
setup do
|
51
54
|
@cipher = SymmetricEncryption::Cipher.new(
|
52
|
-
:key
|
53
|
-
:iv
|
55
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
56
|
+
:iv => '1234567890ABCDEF',
|
57
|
+
:encoding => :none
|
54
58
|
)
|
55
59
|
@social_security_number = "987654321"
|
56
60
|
|
data/test/config/database.yml
CHANGED
data/test/reader_test.rb
CHANGED
@@ -22,7 +22,7 @@ class ReaderTest < Test::Unit::TestCase
|
|
22
22
|
]
|
23
23
|
@data_str = @data.inject('') {|sum,str| sum << str}
|
24
24
|
@data_len = @data_str.length
|
25
|
-
@data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str)
|
25
|
+
@data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str, false)
|
26
26
|
end
|
27
27
|
|
28
28
|
should "decrypt from string stream as a single read" do
|
@@ -12,9 +12,9 @@ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric
|
|
12
12
|
# Unit Test for SymmetricEncryption
|
13
13
|
#
|
14
14
|
class SymmetricEncryptionTest < Test::Unit::TestCase
|
15
|
-
context '
|
15
|
+
context 'SymmetricEncryption' do
|
16
16
|
|
17
|
-
context '
|
17
|
+
context 'configuration' do
|
18
18
|
setup do
|
19
19
|
@config = SymmetricEncryption.send(:read_config, File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
|
20
20
|
end
|
@@ -24,61 +24,51 @@ class SymmetricEncryptionTest < Test::Unit::TestCase
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
27
|
+
SymmetricEncryption::Cipher::ENCODINGS.each do |encoding|
|
28
|
+
context "encoding: #{encoding}" do
|
29
|
+
setup do
|
30
|
+
@social_security_number = "987654321"
|
31
|
+
@social_security_number_encrypted =
|
32
|
+
case encoding
|
33
|
+
when :base64
|
34
|
+
"S+8X1NRrqdfEIQyFHVPuVA==\n"
|
35
|
+
when :base64strict
|
36
|
+
"S+8X1NRrqdfEIQyFHVPuVA=="
|
37
|
+
when :base16
|
38
|
+
"4bef17d4d46ba9d7c4210c851d53ee54"
|
39
|
+
when :none
|
40
|
+
"K\xEF\x17\xD4\xD4k\xA9\xD7\xC4!\f\x85\x1DS\xEET".force_encoding(Encoding.find("binary"))
|
41
|
+
else
|
42
|
+
raise "Add test for encoding: #{encoding}"
|
43
|
+
end
|
44
|
+
@social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow==\n"
|
45
|
+
@encoding = SymmetricEncryption.cipher.encoding
|
46
|
+
SymmetricEncryption.cipher.encoding = encoding
|
47
|
+
end
|
48
|
+
|
49
|
+
teardown do
|
50
|
+
SymmetricEncryption.cipher.encoding = @encoding
|
51
|
+
end
|
52
|
+
|
53
|
+
should "encrypt simple string" do
|
54
|
+
assert_equal @social_security_number_encrypted, SymmetricEncryption.encrypt(@social_security_number)
|
55
|
+
end
|
56
|
+
|
57
|
+
should "decrypt string" do
|
58
|
+
assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
|
59
|
+
end
|
60
|
+
|
61
|
+
should "determine if string is encrypted" do
|
62
|
+
assert_equal true, SymmetricEncryption.encrypted?(@social_security_number_encrypted)
|
63
|
+
assert_equal false, SymmetricEncryption.encrypted?(@social_security_number)
|
64
|
+
end
|
65
|
+
|
66
|
+
should "decrypt with secondary key when first one fails" do
|
67
|
+
assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted_with_secondary_1)
|
68
|
+
end
|
55
69
|
end
|
56
70
|
end
|
57
71
|
|
58
|
-
context 'Base64Strict tests' do
|
59
|
-
setup do
|
60
|
-
@social_security_number = "987654321"
|
61
|
-
@social_security_number_encrypted = "S+8X1NRrqdfEIQyFHVPuVA=="
|
62
|
-
@social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow=="
|
63
|
-
end
|
64
|
-
|
65
|
-
should "encrypt simple string" do
|
66
|
-
assert_equal @social_security_number_encrypted, SymmetricEncryption.encrypt(@social_security_number)
|
67
|
-
end
|
68
|
-
|
69
|
-
should "decrypt string" do
|
70
|
-
assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
|
71
|
-
end
|
72
|
-
|
73
|
-
should "determine if string is encrypted" do
|
74
|
-
assert_equal true, SymmetricEncryption.encrypted?(@social_security_number_encrypted)
|
75
|
-
assert_equal false, SymmetricEncryption.encrypted?(@social_security_number)
|
76
|
-
end
|
77
|
-
|
78
|
-
should "decrypt with secondary key when first one fails" do
|
79
|
-
assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
|
80
|
-
end
|
81
|
-
end
|
82
72
|
end
|
83
73
|
|
84
74
|
end
|
Binary file
|
data/test/writer_test.rb
CHANGED
@@ -22,7 +22,7 @@ class EncryptionWriterTest < Test::Unit::TestCase
|
|
22
22
|
]
|
23
23
|
@data_str = @data.inject('') {|sum,str| sum << str}
|
24
24
|
@data_len = @data_str.length
|
25
|
-
@data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str)
|
25
|
+
@data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str, false)
|
26
26
|
@filename = '._test'
|
27
27
|
end
|
28
28
|
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: symmetric-encryption
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Reid Morrison
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2013-03-07 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: SymmetricEncryption supports encrypting ActiveRecord data, Mongoid data,
|
15
14
|
passwords in configuration files, encrypting and decrypting of large files through
|
@@ -20,11 +19,17 @@ executables: []
|
|
20
19
|
extensions: []
|
21
20
|
extra_rdoc_files: []
|
22
21
|
files:
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- LICENSE.txt
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
23
27
|
- examples/symmetric-encryption.yml
|
24
28
|
- lib/rails/generators/symmetric_encryption/config/config_generator.rb
|
25
29
|
- lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml
|
26
30
|
- lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb
|
27
31
|
- lib/symmetric-encryption.rb
|
32
|
+
- lib/symmetric_encryption.rb
|
28
33
|
- lib/symmetric_encryption/cipher.rb
|
29
34
|
- lib/symmetric_encryption/extensions/active_record/base.rb
|
30
35
|
- lib/symmetric_encryption/extensions/mongoid/fields.rb
|
@@ -35,16 +40,13 @@ files:
|
|
35
40
|
- lib/symmetric_encryption/symmetric_encryption.rb
|
36
41
|
- lib/symmetric_encryption/version.rb
|
37
42
|
- lib/symmetric_encryption/writer.rb
|
38
|
-
- lib/symmetric_encryption.rb
|
39
|
-
- LICENSE.txt
|
40
43
|
- nbproject/private/config.properties
|
41
44
|
- nbproject/private/private.properties
|
42
45
|
- nbproject/private/private.xml
|
43
46
|
- nbproject/private/rake-d.txt
|
44
47
|
- nbproject/project.properties
|
45
48
|
- nbproject/project.xml
|
46
|
-
-
|
47
|
-
- README.md
|
49
|
+
- test.rb
|
48
50
|
- test/attr_encrypted_test.rb
|
49
51
|
- test/cipher_test.rb
|
50
52
|
- test/config/database.yml
|
@@ -58,30 +60,30 @@ files:
|
|
58
60
|
- test/field_encrypted_test.rb
|
59
61
|
- test/reader_test.rb
|
60
62
|
- test/symmetric_encryption_test.rb
|
63
|
+
- test/test_db.sqlite3
|
61
64
|
- test/writer_test.rb
|
62
|
-
- test.rb
|
63
65
|
homepage: https://github.com/ClarityServices/symmetric-encryption
|
64
|
-
licenses:
|
66
|
+
licenses:
|
67
|
+
- Apache License V2.0
|
68
|
+
metadata: {}
|
65
69
|
post_install_message:
|
66
70
|
rdoc_options: []
|
67
71
|
require_paths:
|
68
72
|
- lib
|
69
73
|
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
-
none: false
|
71
74
|
requirements:
|
72
|
-
- -
|
75
|
+
- - '>='
|
73
76
|
- !ruby/object:Gem::Version
|
74
77
|
version: '0'
|
75
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
-
none: false
|
77
79
|
requirements:
|
78
|
-
- -
|
80
|
+
- - '>='
|
79
81
|
- !ruby/object:Gem::Version
|
80
82
|
version: '0'
|
81
83
|
requirements: []
|
82
84
|
rubyforge_project:
|
83
|
-
rubygems_version:
|
85
|
+
rubygems_version: 2.0.2
|
84
86
|
signing_key:
|
85
|
-
specification_version:
|
87
|
+
specification_version: 4
|
86
88
|
summary: Symmetric Encryption for Ruby, and Ruby on Rails
|
87
89
|
test_files: []
|