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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4dac9dced35c1976a89c1cb778a1d7e596a63f87
4
- data.tar.gz: c6eeae2387c3a6489131763f5fef77c030af1cd0
3
+ metadata.gz: ed50ff6fcf3ddaf3a95aed933e81fc266a90828f
4
+ data.tar.gz: 384a709f62320d39702b3957385443f57f923483
5
5
  SHA512:
6
- metadata.gz: 708efe2ec46234f57701d129edcc380b1d69108ab433a33e364285a278e22d9fe46c23647f70553864575bd214e2bf5253448a295403b021de99f18cdc3bcc9c
7
- data.tar.gz: fdc02dc6897557a4e4bedca75594e6d689db844de8c162bb9e6a41217b01e465c3d468cb1f5e5f9660f44111879b7804f5c74663df0d54fc81054c9e24a94450
6
+ metadata.gz: 7f255fe078fdbffa927b9b4fea76c1e92c29709418e7a4ee8fdfe603329241f1688a02b1eacce5285fb6faee15947b9a413f6169e932f4d1c231c4eabb0e73d0
7
+ data.tar.gz: a0ee50b3bb3e4dd1e4d32cd04ce63c2257b286877c94623a4686c4e88c479c8dd3eca77c996d3a115a12fb2a620d9acc94d720fbf8c06c3dac37f036be20b5d9
data/.travis.yml CHANGED
@@ -1,3 +1,7 @@
1
1
  language: ruby
2
+ cache:
3
+ - bundler
2
4
  rvm:
3
- - 2.1.0
5
+ - 2.1.4
6
+ - 2.0.0
7
+ - 1.9.3
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 overriding `read_attribute` and `write_attribute`
31
- methods, intercepting with the encryption / decryption of the attribute value.
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. Currently, those settings are set only in the
36
- environment, using the variable names `PUBLIC_KEY_FILE`, `PRIVATE_KEY_FILE` and
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
- If an application does not need to retrieve the encrypted information it is not
40
- required for the private key settings to be defined. However, please note that
41
- during development the `inspect` method does call the `read_attribute` method
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
- Moreover, please note that ActiveRecord methods that operate massively on
45
- records do not use the `read_attribute` and `write_attribute` methods and so
46
- encryption / decryption does not take place there. This is by design.
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", ">= 3.1", "< 4.2"
26
- spec.add_dependency "activesupport", ">= 3.1", "< 4.2"
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
- if self.class.encrypted_attributes.include?(attr_name)
41
- value = encrypt(value)
42
- end unless value.nil?
43
-
44
- super(attr_name, value)
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
- # Wrap read_attribute to encrypt value.
48
- def read_attribute(attr_name)
49
- attr_name = attr_name.to_s
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
- raise ActiveRecord::ActiveRecordError.new("Undefined public key for encrypted attribute") if PUBLIC_KEY.nil?
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
- raise ActiveRecord::ActiveRecordError.new("Undefined private key for encrypted attribute") if PRIVATE_KEY.nil?
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
@@ -1,3 +1,3 @@
1
1
  module Cryptonite
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -23,12 +23,12 @@ describe Cryptonite do
23
23
  def self.table_name
24
24
  "sensitive_data"
25
25
  end
26
- end.tap { |obj| obj.attr_encrypted :secret }
26
+ end
27
27
  }
28
28
 
29
- context "with public key only" do
29
+ context "with both keys" do
30
30
  before do
31
- stub_const('Cryptonite::PRIVATE_KEY', nil)
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 private key only" do
67
+ context "with public key only" do
46
68
  before do
47
- stub_const('Cryptonite::PUBLIC_KEY', nil)
69
+ subject.tap { |obj| obj.attr_encrypted :secret, public_key: PUBLIC_FIXTURE_KEY }
48
70
  end
49
71
 
50
- it 'decrypts field in database' do
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(:store, 'secret', Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret)))
86
+ instance.instance_variable_get(:@attributes).send(:fetch, 'secret').value = Base64.encode64(PUBLIC_FIXTURE_KEY.public_encrypt(secret))
55
87
 
56
- expect(instance.secret).to eq(secret)
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 EncryptedAttributes concern.
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.2
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-10-17 00:00:00.000000000 Z
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: '3.1'
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: '3.1'
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: '3.1'
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: '3.1'
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.4.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: