cryptonite 0.0.2 → 0.0.3
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 +4 -4
- data/.travis.yml +5 -1
- data/README.md +35 -12
- data/cryptonite.gemspec +2 -2
- data/lib/cryptonite.rb +45 -30
- data/lib/cryptonite/version.rb +1 -1
- data/spec/cryptonite_spec.rb +41 -9
- data/spec/spec_helper.rb +1 -5
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed50ff6fcf3ddaf3a95aed933e81fc266a90828f
|
4
|
+
data.tar.gz: 384a709f62320d39702b3957385443f57f923483
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f255fe078fdbffa927b9b4fea76c1e92c29709418e7a4ee8fdfe603329241f1688a02b1eacce5285fb6faee15947b9a413f6169e932f4d1c231c4eabb0e73d0
|
7
|
+
data.tar.gz: a0ee50b3bb3e4dd1e4d32cd04ce63c2257b286877c94623a4686c4e88c479c8dd3eca77c996d3a115a12fb2a620d9acc94d720fbf8c06c3dac37f036be20b5d9
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -27,23 +27,46 @@ the attributes that will be transparently encrypted, e.g.
|
|
27
27
|
|
28
28
|
attr_encrypted :secret, :another_secret
|
29
29
|
|
30
|
-
The library operates by
|
31
|
-
|
30
|
+
The library operates by serializing the fields with a custom encoder that will
|
31
|
+
do encryption / decryption of the attribute value.
|
32
32
|
|
33
33
|
In order to encrypt the data the library should be provided with the public key
|
34
34
|
path, and respectively in order to decrypt them it requires the private key
|
35
|
-
path along with its password.
|
36
|
-
environment, using the variable names `
|
37
|
-
`PRIVATE_KEY_PASSWORD
|
35
|
+
path along with its password. Those settings can be set either in the
|
36
|
+
environment, using the variable names `PUBLIC_KEY`, `PRIVATE_KEY` and
|
37
|
+
`PRIVATE_KEY_PASSWORD`, or be passed as options to the `attr_encrypted` method:
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
and hence it will fail if a private key is not provided.
|
39
|
+
attr_encrypted :secret, public_key: File.read('public_key.pem')
|
40
|
+
attr_encrypted :another_secret, private_key: 'private_key.pem', private_key_password: 'test'
|
41
|
+
attr_encrypted :yet_another_secret, key_pair: :get_key_method
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
43
|
+
If an application does not need to retrieve the encrypted information it is not
|
44
|
+
required for the private key settings to be defined. Moreover, please note that
|
45
|
+
ActiveRecord methods that operate massively on records do not use the
|
46
|
+
serialization features and so encryption / decryption does not take place
|
47
|
+
there. This is by design.
|
48
|
+
|
49
|
+
## Key Generation
|
50
|
+
|
51
|
+
Generate a key pair:
|
52
|
+
|
53
|
+
```shell
|
54
|
+
openssl genrsa -des3 -out private.pem 2048
|
55
|
+
Generating RSA private key, 2048 bit long modulus
|
56
|
+
......+++
|
57
|
+
.+++
|
58
|
+
e is 65537 (0x10001)
|
59
|
+
Enter pass phrase for private.pem:
|
60
|
+
Verifying - Enter pass phrase for private.pem:
|
61
|
+
```
|
62
|
+
|
63
|
+
and extract the the public key:
|
64
|
+
|
65
|
+
```shell
|
66
|
+
openssl rsa -in private.pem -out public.pem -outform PEM -pubout
|
67
|
+
Enter pass phrase for private.pem:
|
68
|
+
writing RSA key
|
69
|
+
```
|
47
70
|
|
48
71
|
## Contributing
|
49
72
|
|
data/cryptonite.gemspec
CHANGED
@@ -22,6 +22,6 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency "rspec", "~> 3.1"
|
24
24
|
spec.add_development_dependency "sqlite3"
|
25
|
-
spec.add_dependency "activerecord", ">=
|
26
|
-
spec.add_dependency "activesupport", ">=
|
25
|
+
spec.add_dependency "activerecord", ">= 4.0", "< 4.2"
|
26
|
+
spec.add_dependency "activesupport", ">= 4.0", "< 4.2"
|
27
27
|
end
|
data/lib/cryptonite.rb
CHANGED
@@ -12,9 +12,6 @@ require 'active_support/lazy_load_hooks'
|
|
12
12
|
module Cryptonite
|
13
13
|
extend ActiveSupport::Concern
|
14
14
|
|
15
|
-
PUBLIC_KEY = OpenSSL::PKey::RSA.new(File.read(ENV['PUBLIC_KEY_FILE'])) rescue nil
|
16
|
-
PRIVATE_KEY = OpenSSL::PKey::RSA.new(File.read(ENV['PRIVATE_KEY_FILE']), ENV['PRIVATE_KEY_PASSWORD']) rescue nil
|
17
|
-
|
18
15
|
included do
|
19
16
|
class_attribute :_attr_encrypted, instance_accessor: false
|
20
17
|
self._attr_encrypted = []
|
@@ -24,6 +21,15 @@ module Cryptonite
|
|
24
21
|
# Attributes listed as encrypted will be transparently encrypted and
|
25
22
|
# decrypted in database operations.
|
26
23
|
def attr_encrypted(*attributes)
|
24
|
+
options = attributes.extract_options!
|
25
|
+
|
26
|
+
@public_key = get_rsa_key(options[:public_key] || options[:key_pair] || ENV['PUBLIC_KEY'])
|
27
|
+
@private_key = get_rsa_key(options[:private_key] || options[:key_pair] || ENV['PRIVATE_KEY'], options[:private_key_password] || ENV['PRIVATE_KEY_PASSWORD'])
|
28
|
+
|
29
|
+
for attribute in attributes do
|
30
|
+
serialize attribute, Coder.new(@private_key || @public_key)
|
31
|
+
end
|
32
|
+
|
27
33
|
self._attr_encrypted = Set.new(attributes.map { |a| a.to_s }) + (self._attr_encrypted || [])
|
28
34
|
end
|
29
35
|
|
@@ -31,47 +37,56 @@ module Cryptonite
|
|
31
37
|
def encrypted_attributes
|
32
38
|
self._attr_encrypted
|
33
39
|
end
|
34
|
-
end
|
35
|
-
|
36
|
-
# Wrap write_attribute to encrypt value.
|
37
|
-
def write_attribute(attr_name, value)
|
38
|
-
attr_name = attr_name.to_s
|
39
40
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
private
|
42
|
+
# Retrives an RSA key with multiple ways.
|
43
|
+
def get_rsa_key(key, password = nil)
|
44
|
+
return nil unless key
|
45
|
+
|
46
|
+
if key.is_a?(Proc)
|
47
|
+
key = key.call
|
48
|
+
end
|
49
|
+
|
50
|
+
if key.is_a?(Symbol)
|
51
|
+
key = @instance.send(key)
|
52
|
+
end
|
53
|
+
|
54
|
+
return key if key.is_a?(::OpenSSL::PKey::RSA)
|
55
|
+
|
56
|
+
if key.respond_to?(:read)
|
57
|
+
key = key.read
|
58
|
+
elsif key !~ /^-+BEGIN .* KEY-+$/
|
59
|
+
key = File.read(key)
|
60
|
+
end
|
61
|
+
|
62
|
+
if password.nil?
|
63
|
+
::OpenSSL::PKey::RSA.new(key)
|
64
|
+
else
|
65
|
+
::OpenSSL::PKey::RSA.new(key, password.to_s)
|
66
|
+
end
|
67
|
+
end
|
45
68
|
end
|
46
69
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
if self.class.encrypted_attributes.include?(attr_name)
|
52
|
-
value = super(attr_name)
|
53
|
-
decrypt(value) unless value.nil?
|
54
|
-
else
|
55
|
-
super(attr_name)
|
70
|
+
class Coder # :nodoc:
|
71
|
+
def initialize(key)
|
72
|
+
raise ArgumentError unless key.is_a?(::OpenSSL::PKey::RSA)
|
73
|
+
@key = key
|
56
74
|
end
|
57
|
-
end
|
58
75
|
|
59
|
-
private
|
60
76
|
# Encrypts a value with public key encryption. Keys should be defined in
|
61
77
|
# environment.
|
62
78
|
def encrypt(value)
|
63
|
-
|
64
|
-
|
65
|
-
Base64.encode64(PUBLIC_KEY.public_encrypt(value))
|
79
|
+
Base64.encode64(@key.public_encrypt(value))
|
66
80
|
end
|
81
|
+
alias :dump :encrypt
|
67
82
|
|
68
83
|
# Decrypts a value with public key encryption. Keys should be defined in
|
69
84
|
# environment.
|
70
85
|
def decrypt(value)
|
71
|
-
|
72
|
-
|
73
|
-
PRIVATE_KEY.private_decrypt(Base64.decode64(value))
|
86
|
+
@key.private_decrypt(Base64.decode64(value))
|
74
87
|
end
|
88
|
+
alias :load :decrypt
|
89
|
+
end
|
75
90
|
end
|
76
91
|
|
77
92
|
ActiveSupport.on_load :active_record do
|
data/lib/cryptonite/version.rb
CHANGED
data/spec/cryptonite_spec.rb
CHANGED
@@ -23,12 +23,12 @@ describe Cryptonite do
|
|
23
23
|
def self.table_name
|
24
24
|
"sensitive_data"
|
25
25
|
end
|
26
|
-
end
|
26
|
+
end
|
27
27
|
}
|
28
28
|
|
29
|
-
context "with
|
29
|
+
context "with both keys" do
|
30
30
|
before do
|
31
|
-
|
31
|
+
subject.tap { |obj| obj.attr_encrypted :secret, key_pair: PRIVATE_FIXTURE_KEY }
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'encrypts field in database' do
|
@@ -36,24 +36,56 @@ describe Cryptonite do
|
|
36
36
|
|
37
37
|
subject.new(secret: secret).tap do |instance|
|
38
38
|
expect(
|
39
|
-
instance.instance_variable_get(:@attributes).send(:fetch, 'secret')
|
39
|
+
instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
|
40
|
+
).not_to eq(secret)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'decrypts field in database' do
|
45
|
+
secret = SecureRandom.hex(16)
|
46
|
+
|
47
|
+
subject.new.tap do |instance|
|
48
|
+
instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret))
|
49
|
+
|
50
|
+
expect(instance.secret).to eq(secret)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'encrypts and decrypts field in database' do
|
55
|
+
secret = SecureRandom.hex(16)
|
56
|
+
|
57
|
+
subject.create(secret: secret).reload.tap do |instance|
|
58
|
+
expect(
|
59
|
+
instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
|
40
60
|
).not_to eq(secret)
|
61
|
+
|
62
|
+
expect(instance.secret).to eq(secret)
|
41
63
|
end
|
42
64
|
end
|
43
65
|
end
|
44
66
|
|
45
|
-
context "with
|
67
|
+
context "with public key only" do
|
46
68
|
before do
|
47
|
-
|
69
|
+
subject.tap { |obj| obj.attr_encrypted :secret, public_key: PUBLIC_FIXTURE_KEY }
|
48
70
|
end
|
49
71
|
|
50
|
-
it '
|
72
|
+
it 'encrypts field in database' do
|
73
|
+
secret = SecureRandom.hex(16)
|
74
|
+
|
75
|
+
subject.new(secret: secret).tap do |instance|
|
76
|
+
expect(
|
77
|
+
instance.instance_variable_get(:@attributes).send(:fetch, 'secret').serialized_value
|
78
|
+
).not_to eq(secret)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'cannot decrypt field in database' do
|
51
83
|
secret = SecureRandom.hex(16)
|
52
84
|
|
53
85
|
subject.new.tap do |instance|
|
54
|
-
instance.instance_variable_get(:@attributes).send(:
|
86
|
+
instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret))
|
55
87
|
|
56
|
-
expect
|
88
|
+
expect{ instance.secret }.to raise_error OpenSSL::PKey::RSAError
|
57
89
|
end
|
58
90
|
end
|
59
91
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -87,11 +87,7 @@ RSpec.configure do |config|
|
|
87
87
|
Kernel.srand config.seed
|
88
88
|
=end
|
89
89
|
|
90
|
-
# Configure public key encryption for the
|
90
|
+
# Configure public key encryption for the Cryptonite concern.
|
91
91
|
::PUBLIC_FIXTURE_KEY = OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/public.pem', __FILE__)))
|
92
92
|
::PRIVATE_FIXTURE_KEY = OpenSSL::PKey::RSA.new(File.read(File.expand_path('../fixtures/keys/private.pem', __FILE__)), 'test')
|
93
|
-
config.before do
|
94
|
-
stub_const('Cryptonite::PUBLIC_KEY', PUBLIC_FIXTURE_KEY)
|
95
|
-
stub_const('Cryptonite::PRIVATE_KEY', PRIVATE_FIXTURE_KEY)
|
96
|
-
end
|
97
93
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cryptonite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GaggleAMP
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -72,7 +72,7 @@ dependencies:
|
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '4.0'
|
76
76
|
- - "<"
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: '4.2'
|
@@ -82,7 +82,7 @@ dependencies:
|
|
82
82
|
requirements:
|
83
83
|
- - ">="
|
84
84
|
- !ruby/object:Gem::Version
|
85
|
-
version: '
|
85
|
+
version: '4.0'
|
86
86
|
- - "<"
|
87
87
|
- !ruby/object:Gem::Version
|
88
88
|
version: '4.2'
|
@@ -92,7 +92,7 @@ dependencies:
|
|
92
92
|
requirements:
|
93
93
|
- - ">="
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
95
|
+
version: '4.0'
|
96
96
|
- - "<"
|
97
97
|
- !ruby/object:Gem::Version
|
98
98
|
version: '4.2'
|
@@ -102,7 +102,7 @@ dependencies:
|
|
102
102
|
requirements:
|
103
103
|
- - ">="
|
104
104
|
- !ruby/object:Gem::Version
|
105
|
-
version: '
|
105
|
+
version: '4.0'
|
106
106
|
- - "<"
|
107
107
|
- !ruby/object:Gem::Version
|
108
108
|
version: '4.2'
|
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
version: '0'
|
148
148
|
requirements: []
|
149
149
|
rubyforge_project:
|
150
|
-
rubygems_version: 2.
|
150
|
+
rubygems_version: 2.2.2
|
151
151
|
signing_key:
|
152
152
|
specification_version: 4
|
153
153
|
summary: Enables the encryption of specific ActiveRecord attributes.
|
@@ -156,3 +156,4 @@ test_files:
|
|
156
156
|
- spec/fixtures/keys/private.pem
|
157
157
|
- spec/fixtures/keys/public.pem
|
158
158
|
- spec/spec_helper.rb
|
159
|
+
has_rdoc:
|