Skip to content

Commit d06a23a

Browse files
author
S. Brent Faulkner
committed
Merge branch 'master' into feature/coverage
Conflicts: README.rdoc Rakefile encryptor.gemspec
2 parents 9391d84 + 535e4a5 commit d06a23a

10 files changed

+365
-120
lines changed

.travis.yml

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
language: ruby
2+
rvm:
3+
- 1.8.7
4+
- 1.9.3
5+
- 2.0.0
6+

README.md

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
## Encryptor [![Build Status](https://travis-ci.org/attr-encrypted/encryptor.png?branch=master)](https://travis-ci.org/attr-encrypted/encryptor)
2+
3+
A simple wrapper for the standard Ruby OpenSSL library
4+
5+
Intended to be used by a future version of `http://github.com/shuber/attr_encrypted` to easily encrypt/decrypt attributes in any Ruby class or model.
6+
7+
### Installation
8+
9+
```bash
10+
gem install encryptor
11+
```
12+
13+
### Usage
14+
15+
#### Basic
16+
17+
Encryptor uses the AES-256-CBC algorithm by default to encrypt strings securely. You are strongly advised to use both an initialization vector (via the `:iv` option) and a salt (via the `:salt` option) to perform this encryption as securely as possible. Specifying only an `:iv` option without `:salt` is not recommended but is supported as part of a "compatibility mode" to support clients built using older versions of this gem.
18+
19+
The best example is:
20+
21+
```ruby
22+
salt = Time.now.to_i.to_s
23+
secret_key = 'secret'
24+
iv = OpenSSL::Cipher::Cipher.new('aes-256-cbc').random_iv
25+
encrypted_value = Encryptor.encrypt('some string to encrypt', :key => secret_key, :iv => iv, :salt => salt)
26+
decrypted_value = Encryptor.decrypt(encrypted_value, :key => secret_key, :iv => iv, :salt => salt)
27+
```
28+
29+
The value to encrypt or decrypt may also be passed as the :value option if you'd prefer.
30+
31+
```ruby
32+
encrypted_value = Encryptor.encrypt(:value => 'some string to encrypt', :key => secret_key, :iv => iv, :salt => salt)
33+
decrypted_value = Encryptor.decrypt(:value => encrypted_value, :key => secret_key, :iv => iv, :salt => salt)
34+
```
35+
36+
**You may also skip the salt and the IV if you like. Do so at your own risk!**
37+
38+
```ruby
39+
encrypted_value = Encryptor.encrypt(:value => 'some string to encrypt', :key => 'secret')
40+
decrypted_value = Encryptor.decrypt(:value => encrypted_value, :key => 'secret')
41+
```
42+
43+
You may also pass an `:algorithm` option, though this is not required.
44+
45+
```ruby
46+
Encryptor.default_options.merge!(:algorithm => 'aes-128-cbc', :key => 'some default secret key', :iv => iv, :salt => salt)
47+
```
48+
49+
#### Strings
50+
51+
Encryptor adds `encrypt` and `decrypt` methods to `String` objects for your convenience. These two methods accept the same arguments as the associated ones in the `Encryptor` module. They're nice when you set the default options in the `Encryptor.default_options attribute.` For example:
52+
53+
```ruby
54+
Encryptor.default_options.merge!(:key => 'some default secret key', :iv => iv, :salt => salt)
55+
credit_card = 'xxxx xxxx xxxx 1234'
56+
encrypted_credit_card = credit_card.encrypt
57+
```
58+
59+
There's also `encrypt!` and `decrypt!` methods that replace the contents of a string with the encrypted or decrypted version of itself.
60+
61+
### Algorithms
62+
63+
Run `openssl list-cipher-commands` in your terminal to view a list of all cipher algorithms that are supported on your platform. Typically, this will include the following:
64+
65+
aes-128-cbc
66+
aes-128-ecb
67+
aes-192-cbc
68+
aes-192-ecb
69+
aes-256-cbc
70+
aes-256-ecb
71+
bf
72+
bf-cbc
73+
bf-cfb
74+
bf-ecb
75+
bf-ofb
76+
cast
77+
cast-cbc
78+
cast5-cbc
79+
cast5-cfb
80+
cast5-ecb
81+
cast5-ofb
82+
des
83+
des-cbc
84+
des-cfb
85+
des-ecb
86+
des-ede
87+
des-ede-cbc
88+
des-ede-cfb
89+
des-ede-ofb
90+
des-ede3
91+
des-ede3-cbc
92+
des-ede3-cfb
93+
des-ede3-ofb
94+
des-ofb
95+
des3
96+
desx
97+
idea
98+
idea-cbc
99+
idea-cfb
100+
idea-ecb
101+
idea-ofb
102+
rc2
103+
rc2-40-cbc
104+
rc2-64-cbc
105+
rc2-cbc
106+
rc2-cfb
107+
rc2-ecb
108+
rc2-ofb
109+
rc4
110+
rc4-40
111+
112+
Note that some ciphers may not be supported by Ruby.
113+
114+
### Notes on patches/pull requests
115+
116+
* Fork the project.
117+
* Make your feature addition or bug fix.
118+
* Add tests for it: this is important so I don't break it in a future version unintentionally.
119+
* Commit, do not mess with Rakefile, version, or history: if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull).
120+
* Send me a pull request: bonus points for topic branches.
121+

README.rdoc

-103
This file was deleted.

encryptor.gemspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
2121

2222
s.require_paths = ['lib']
2323

24-
s.files = Dir['{bin,lib}/**/*'] + %w(MIT-LICENSE Rakefile README.rdoc)
24+
s.files = Dir['{bin,lib}/**/*'] + %w(MIT-LICENSE Rakefile README.md)
2525
s.test_files = Dir['test/**/*']
2626

2727
s.add_development_dependency('rake', '0.9.2.2')
@@ -32,4 +32,4 @@ Gem::Specification.new do |s|
3232
s.add_development_dependency('simplecov')
3333
s.add_development_dependency('simplecov-rcov')
3434
end
35-
end
35+
end

lib/encryptor.rb

+13-2
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,24 @@ def crypt(cipher_method, *args) #:nodoc:
5252
cipher = OpenSSL::Cipher::Cipher.new(options[:algorithm])
5353
cipher.send(cipher_method)
5454
if options[:iv]
55-
cipher.key = options[:key]
5655
cipher.iv = options[:iv]
56+
if options[:salt].nil?
57+
# Use a non-salted cipher.
58+
# This behaviour is retained for backwards compatibility. This mode
59+
# is not secure and new deployments should use the :salt options
60+
# wherever possible.
61+
cipher.key = options[:key]
62+
else
63+
# Use an explicit salt (which can be persisted into a database on a
64+
# per-column basis, for example). This is the preferred (and more
65+
# secure) mode of operation.
66+
cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(options[:key], options[:salt], 2000, cipher.key_len)
67+
end
5768
else
5869
cipher.pkcs5_keyivgen(options[:key])
5970
end
6071
yield cipher, options if block_given?
6172
result = cipher.update(options[:value])
6273
result << cipher.final
6374
end
64-
end
75+
end

lib/encryptor/version.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ def self.to_s
1414
[MAJOR, MINOR, PATCH].join('.')
1515
end
1616
end
17-
end
17+
end

test/compatibility_test.rb

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
require File.expand_path('../test_helper', __FILE__)
2+
3+
# Test ensures that values stored by previous versions of the gem will
4+
# roundtrip and decrypt correctly in this and future versions. This is important
5+
# for data stored in databases and allows consumers of the gem to upgrade with
6+
# confidence in the future.
7+
#
8+
class CompatibilityTest < Test::Unit::TestCase
9+
ALGORITHM = 'aes-256-cbc'
10+
11+
def self.base64_encode(value)
12+
[value].pack('m').strip
13+
end
14+
15+
def self.base64_decode(value)
16+
value.unpack('m').first
17+
end
18+
19+
if OpenSSL::Cipher.ciphers.include?(ALGORITHM)
20+
def test_encrypt_with_iv
21+
key = Digest::SHA256.hexdigest('my-fixed-key')
22+
iv = Digest::SHA256.hexdigest('my-fixed-iv')
23+
result = Encryptor.encrypt(
24+
:algorithm => ALGORITHM,
25+
:value => 'my-fixed-input',
26+
:key => key,
27+
:iv => iv
28+
)
29+
assert_equal 'nGuyGniksFXnMYj/eCxXKQ==', self.class.base64_encode(result)
30+
end
31+
32+
def test_encrypt_without_iv
33+
key = Digest::SHA256.hexdigest('my-fixed-key')
34+
result = Encryptor.encrypt(
35+
:algorithm => ALGORITHM,
36+
:value => 'my-fixed-input',
37+
:key => key
38+
)
39+
assert_equal 'XbwHRMFWqR5M80kgwRcEEg==', self.class.base64_encode(result)
40+
end
41+
42+
def test_decrypt_with_iv
43+
key = Digest::SHA256.hexdigest('my-fixed-key')
44+
iv = Digest::SHA256.hexdigest('my-fixed-iv')
45+
result = Encryptor.decrypt(
46+
:algorithm => ALGORITHM,
47+
:value => self.class.base64_decode('nGuyGniksFXnMYj/eCxXKQ=='),
48+
:key => key,
49+
:iv => iv
50+
)
51+
assert_equal 'my-fixed-input', result
52+
end
53+
54+
def test_decrypt_without_iv
55+
key = Digest::SHA256.hexdigest('my-fixed-key')
56+
result = Encryptor.decrypt(
57+
:algorithm => ALGORITHM,
58+
:value => self.class.base64_decode('XbwHRMFWqR5M80kgwRcEEg=='),
59+
:key => key
60+
)
61+
assert_equal 'my-fixed-input', result
62+
end
63+
64+
def test_encrypt_with_iv_and_salt
65+
key = Digest::SHA256.hexdigest('my-fixed-key')
66+
iv = Digest::SHA256.hexdigest('my-fixed-iv')
67+
salt = 'my-fixed-salt'
68+
result = Encryptor.encrypt(
69+
:algorithm => ALGORITHM,
70+
:value => 'my-fixed-input',
71+
:key => key,
72+
:iv => iv,
73+
:salt => salt
74+
)
75+
assert_equal 'DENuQSh9b0eW8GN3YLzLGw==', self.class.base64_encode(result)
76+
end
77+
78+
def test_decrypt_with_iv_and_salt
79+
key = Digest::SHA256.hexdigest('my-fixed-key')
80+
iv = Digest::SHA256.hexdigest('my-fixed-iv')
81+
salt = 'my-fixed-salt'
82+
result = Encryptor.decrypt(
83+
:algorithm => ALGORITHM,
84+
:value => self.class.base64_decode('DENuQSh9b0eW8GN3YLzLGw=='),
85+
:key => key,
86+
:iv => iv,
87+
:salt => salt
88+
)
89+
assert_equal 'my-fixed-input', result
90+
end
91+
end
92+
end
93+

0 commit comments

Comments
 (0)