crypt_keeper 1.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 78d7a7f1ea679f9a1542a65b537a446065c9cd27
4
- data.tar.gz: 518d78e7d4a80e07faa80b9eac9c3b268e2db2c8
2
+ SHA256:
3
+ metadata.gz: 9607ecd6a9297094077cafed8d0f5a29624d4d68f1309630423b53442bdfba5e
4
+ data.tar.gz: 7f7f07056ba8990311944d6b9bc4c9977354113e7e489b6fd5a2c27b3bb39b56
5
5
  SHA512:
6
- metadata.gz: 30b9ccbea99c9ac89ee4bf4c9df73fc0b95e2ae3f65aca3e9936a95bd406d271e672e73cefa2c717a2f0c057c638ca876a238c7058da2c3ad6e3f2d2d12720cb
7
- data.tar.gz: 07f33f2caca79c5be2c45f2365601e8939b5c64200fba53c00de3e70a9d6b221a68efe47aad00fc7b42003bce2c964ce68701656966149afe4b65dfc45a4e0a7
6
+ metadata.gz: 211a89c08366409e4800fb000e7779fbc4162ce6abd43bc8fb3023984b381db2c5e5061d60668d863d42be75ea7cf3e83f751a1aa5dedf0e96891f019fe4dd04
7
+ data.tar.gz: 2fab3a0eed0e1fbffc65831a469d83a2e5d1ffa748c26c4e7882696de670d5c18bed078323b5e08066e1fda345094b55bde4f85ec782c3017635709eb9984464
data/.travis.yml CHANGED
@@ -1,23 +1,60 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.1.10
5
- - 2.2.5
6
- - 2.3.1
4
+ - 2.2.10
5
+ - 2.3.8
6
+ - 2.4.5
7
+ - 2.5.3
8
+ - 2.6.4
9
+ - 2.7.2
7
10
 
8
11
  gemfile:
9
12
  - gemfiles/activerecord_4_2.gemfile
13
+ - gemfiles/activerecord_5_0.gemfile
10
14
  - gemfiles/activerecord_5_1.gemfile
15
+ - gemfiles/activerecord_5_2.gemfile
16
+ - gemfiles/activerecord_6_0.gemfile
17
+ - gemfiles/activerecord_6_1.gemfile
18
+
11
19
 
12
20
  matrix:
13
21
  exclude:
14
- - gemfile: gemfiles/activerecord_5_1.gemfile
15
- rvm: 2.1.10
22
+ - rvm: 2.2.10
23
+ gemfile: gemfiles/activerecord_5_0.gemfile
24
+ - rvm: 2.2.10
25
+ gemfile: gemfiles/activerecord_5_1.gemfile
26
+ - rvm: 2.2.10
27
+ gemfile: gemfiles/activerecord_6_0.gemfile
28
+ - rvm: 2.2.10
29
+ gemfile: gemfiles/activerecord_6_1.gemfile
30
+ - rvm: 2.3.8
31
+ gemfile: gemfiles/activerecord_5_0.gemfile
32
+ - rvm: 2.3.8
33
+ gemfile: gemfiles/activerecord_5_1.gemfile
34
+ - rvm: 2.3.8
35
+ gemfile: gemfiles/activerecord_6_0.gemfile
36
+ - rvm: 2.3.8
37
+ gemfile: gemfiles/activerecord_6_1.gemfile
38
+ - rvm: 2.4.5
39
+ gemfile: gemfiles/activerecord_5_0.gemfile
40
+ - rvm: 2.4.5
41
+ gemfile: gemfiles/activerecord_5_1.gemfile
42
+ - rvm: 2.4.5
43
+ gemfile: gemfiles/activerecord_6_0.gemfile
44
+ - rvm: 2.4.5
45
+ gemfile: gemfiles/activerecord_6_1.gemfile
46
+ - rvm: 2.5.3
47
+ gemfile: gemfiles/activerecord_5_0.gemfile
48
+ - rvm: 2.5.3
49
+ gemfile: gemfiles/activerecord_5_1.gemfile
50
+ - rvm: 2.6.4
51
+ gemfile: gemfiles/activerecord_5_0.gemfile
52
+ - rvm: 2.6.4
53
+ gemfile: gemfiles/activerecord_5_1.gemfile
16
54
 
17
- addons:
18
- postgresql: 9.3
19
-
20
- sudo: false
55
+ services:
56
+ - postgresql
57
+ - mysql
21
58
 
22
59
  before_script:
23
60
  - cp spec/default.database.yml spec/database.yml
@@ -25,7 +62,8 @@ before_script:
25
62
  - psql crypt_keeper_providers -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' -U postgres
26
63
  - mysql -e 'CREATE DATABASE crypt_keeper_providers'
27
64
 
28
- branches: master
65
+ branches:
66
+ - master
29
67
 
30
68
  notifications:
31
69
  email:
data/Appraisals CHANGED
@@ -1,9 +1,36 @@
1
1
  appraise "activerecord_4_2" do
2
2
  gem "activerecord", "~> 4.2.0"
3
3
  gem "activesupport", "~> 4.2.0"
4
+ gem "sqlite3", "~> 1.3.0"
5
+
6
+ # otherwise you get "undefined method `new' for BigDecimal:Class" in Ruby 2.7
7
+ gem "bigdecimal", "1.3.5"
4
8
  end
5
9
 
6
10
  appraise "activerecord_5_0" do
7
11
  gem "activerecord", "~> 5.0.0"
8
12
  gem "activesupport", "~> 5.0.0"
13
+
14
+ gem "sqlite3", "~> 1.3.6"
15
+ end
16
+
17
+ appraise "activerecord_5_1" do
18
+ gem "activerecord", "~> 5.1.0"
19
+ gem "activesupport", "~> 5.1.0"
20
+ end
21
+
22
+ appraise "activerecord_5_2" do
23
+ gem "activerecord", "~> 5.2.0"
24
+ gem "activesupport", "~> 5.2.0"
25
+ end
26
+
27
+ appraise "activerecord_6_0" do
28
+ gem "activerecord", "~> 6.0.0"
29
+ gem "activesupport", "~> 6.0.0"
30
+ end
31
+
32
+ appraise "activerecord_6_1" do
33
+ gem "activerecord", "~> 6.1.0"
34
+ gem "activesupport", "~> 6.1.0"
35
+ gem "pg", "~> 1.1"
9
36
  end
data/README.md CHANGED
@@ -15,7 +15,7 @@ is a simple class that does 3 things.
15
15
  Note: Any options defined using `crypt_keeper` will be passed to `new` as a
16
16
  hash.
17
17
 
18
- You can see an AES example [here](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes_new.rb).
18
+ You can see an example [here](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/active_support.rb).
19
19
 
20
20
  ## Why?
21
21
 
@@ -27,7 +27,7 @@ simple that *just works*.
27
27
 
28
28
  ```ruby
29
29
  class MyModel < ActiveRecord::Base
30
- crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt'
30
+ crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt'
31
31
  end
32
32
 
33
33
  model = MyModel.new(field: 'sometext')
@@ -47,13 +47,49 @@ expected behavior, and has its use cases. An example would be migrating from
47
47
  one type of encryption to another. Using `update_column` would allow you to
48
48
  update the content without going through the current encryptor.
49
49
 
50
+ ## Generating Keys/Salts
51
+
52
+ For encryptors requiring secret keys/salts, you can generate them via
53
+ `rails secret`:
54
+
55
+ ```
56
+ rails secret
57
+ ef209071bd76143a75eda57b99425da63ce6c2d44581d652aa4302a90dcd7d7e99cbc22091c01a19f93ea484f40b142612f9bf76de8eb2d51ff9b3eb02a7782c
58
+ ```
59
+
60
+ Or manually (this is the same implementation that Rails uses):
61
+
62
+ ```
63
+ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
64
+ ```
65
+
66
+ These values should be stored outside of your application repository for added
67
+ security. For example, one could use [dotenv][] and reference them as `ENV`
68
+ variables.
69
+
70
+ ```
71
+ # .env
72
+ CRYPT_KEEPER_KEY=75d942f3d3b3492772e0330f717eaf5e689673ea8b983475ef8f6551f6e99d280cd89972706e46b48240cc01c4d0f7df5ffa3524566b789d147ed04cc4ea4eab
73
+ CRYPT_KEEPER_SALT=b16a153e99a5db616a861ea5a6febc64d8a758c4aef3b8c8fc6675ac9daf03f7965f16e8b4b2bdfd28ff65f5203afb8102b8f41c514c3667bb3512015b1e77e8
74
+ ```
75
+
76
+ Then in your model:
77
+
78
+ ```ruby
79
+ class MyModel < ActiveRecord::Base
80
+ crypt_keeper :field, :other_field, encryptor: :active_support, key: ENV["CRYPT_KEEPER_KEY"], salt: ENV["CRYPT_KEEPER_SALT"]
81
+ end
82
+ ```
83
+
84
+ [dotenv]: https://github.com/bkeepers/dotenv
85
+
50
86
  ## Encodings
51
87
 
52
88
  You can force an encoding on the plaintext before encryption and after decryption by using the `encoding` option. This is useful when dealing with multibyte strings:
53
89
 
54
90
  ```ruby
55
91
  class MyModel < ActiveRecord::Base
56
- crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt', :encoding => 'UTF-8'
92
+ crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt', encoding: 'UTF-8'
57
93
  end
58
94
 
59
95
  model = MyModel.new(field: 'Tromsø')
@@ -68,7 +104,7 @@ If you are working with an existing table you would like to encrypt, you must us
68
104
 
69
105
  ```ruby
70
106
  class MyExistingModel < ActiveRecord::Base
71
- crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt'
107
+ crypt_keeper :field, :other_field, encryptor: :active_support, key: 'super_good_password', salt: 'salt'
72
108
  end
73
109
 
74
110
  MyExistingModel.encrypt_table!
@@ -78,10 +114,10 @@ Running `encrypt_table!` will encrypt all rows in the database using the encrypt
78
114
 
79
115
  ## Supported Available Encryptors
80
116
 
81
- There are four supported encryptors: `aes_new`, `mysql_aes_new`, `postgres_pgp`, `postgres_pgp_public_key`.
117
+ There are four supported encryptors: `active_support`, `mysql_aes_new`, `postgres_pgp`, `postgres_pgp_public_key`.
82
118
 
83
- * [AES New](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes_new.rb)
84
- * Encryption is peformed using AES-256 via OpenSSL.
119
+ * [ActiveSupport](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/active_support.rb)
120
+ * Encryption is performed using [ActiveSupport::MessageEncryptor](http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html)
85
121
  * Passphrases are derived using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
86
122
 
87
123
  * [MySQL AES New](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes_new.rb)
@@ -111,14 +147,14 @@ There are four supported encryptors: `aes_new`, `mysql_aes_new`, `postgres_pgp`,
111
147
  ## Searching
112
148
  Searching ciphertext is a complex problem that varies depending on the encryption algorithm you choose. All of the bundled providers include search support, but they have some caveats.
113
149
 
114
- * AES
115
- * The Ruby implementation of AES uses a random initialization vector. The same plaintext encrypted multiple times will have different output each time for the ciphertext. Since this is the case, it is not possible to search leveraging the database. Database rows will need to be filtered in memory. It is suggested that you use a scope or ActiveRecord batches to narrow the results before seaching them.
150
+ * ActiveSupport::MessageEncryptor
151
+ * ActiveSupport's MessageEncryptor uses a random initialization vector when generating keys. The same plaintext encrypted multiple times will have different output each time for the ciphertext. Since this is the case, it is not possible to search leveraging the database. Database rows will need to be filtered in memory. It is suggested that you use a scope or ActiveRecord batches to narrow the results before seaching them.
116
152
 
117
153
  * Mysql AES
118
- * Surprisingly, MySQL's implementation of AES does not use a random initialization vector. The column containing the ciphertext can be indexed and searched quickly.
154
+ * Surprisingly, MySQL's implementation of AES does not use a random initialization vector. The column containing the ciphertext can be indexed and searched quickly.
119
155
 
120
156
  * PostgresSQL PGP
121
- * PGP also uses a random initialization vector which means it generates unique output each time you encrypt plaintext. Although the database can be searched by performing row level decryption and comparing the plaintext, it will not be able to use an index. A scope or batch is suggested when searching.
157
+ * PGP also uses a random initialization vector which means it generates unique output each time you encrypt plaintext. Although the database can be searched by performing row level decryption and comparing the plaintext, it will not be able to use an index. A scope or batch is suggested when searching.
122
158
 
123
159
  ## How the search interface is used
124
160
 
@@ -157,10 +193,35 @@ as a string or an underscored symbol
157
193
 
158
194
  ```ruby
159
195
  class MyModel < ActiveRecord::Base
160
- crypt_keeper :field, :other_field, :encryptor => :my_encryptor, :key => 'super_good_password'
196
+ crypt_keeper :field, :other_field, encryptor: :my_encryptor, key: 'super_good_password'
161
197
  end
162
198
  ```
163
199
 
200
+ ## Migrating from CryptKeeper 1.x to 2.0
201
+
202
+ CryptKeeper 2.0 removes the AES encryptor due to security issues in the
203
+ underlying AES gem. If you were previously using the `aes_new` encryptor, you
204
+ will need to follow these instructions to reencrypt your data.
205
+
206
+ The general migration path is as follows:
207
+
208
+ 1. Enable maintenance mode in any live apps
209
+ 2. Backup database
210
+ 3. Decrypt tables: TableName.decrypt_table!
211
+ 4. Update to 2.0.0.rc1 in your app. Update the encryptor to use :active_support
212
+ 5. Encrypt tables: `TableName.encrypt_table!`
213
+ 6. Verify data can be decrypted: `TableName.first`
214
+ 7. Disable maintenance mode if necessary
215
+
216
+ In case you experience problems, the rollback procedure is as follows:
217
+
218
+ 1. Enable maintenance mode
219
+ 2. Backup database again
220
+ 3. Restore first database dump, from before CryptKeeper 2.0.0.rc1
221
+ 4. Verify data can be decrypted
222
+ 5. Disable maintenance mode
223
+ 6. Let us know what happened :(
224
+
164
225
  ## Requirements
165
226
 
166
227
  CryptKeeper has been tested against ActiveRecord 4.2 and 5.0 using Ruby
data/crypt_keeper.gemspec CHANGED
@@ -16,10 +16,10 @@ Gem::Specification.new do |gem|
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = CryptKeeper::VERSION
18
18
 
19
- gem.add_runtime_dependency 'activerecord', '>= 4.2', '< 5.2'
20
- gem.add_runtime_dependency 'activesupport', '>= 4.2', '< 5.2'
21
- gem.add_runtime_dependency 'aes', '~> 0.5.0'
22
- gem.add_runtime_dependency 'armor', '~> 0.0.2'
19
+ gem.post_install_message = "WARNING: CryptKeeper 2.0 contains breaking changes and may require you to reencrypt your data! Please view the README at https://github.com/jmazzi/crypt_keeper for more information."
20
+
21
+ gem.add_runtime_dependency 'activerecord', '>= 4.2', '< 6.2'
22
+ gem.add_runtime_dependency 'activesupport', '>= 4.2', '< 6.2'
23
23
 
24
24
  gem.add_development_dependency 'rspec', '~> 3.5.0'
25
25
  gem.add_development_dependency 'guard', '~> 2.6.1'
@@ -35,8 +35,8 @@ Gem::Specification.new do |gem|
35
35
  gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter'
36
36
  gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
37
37
  else
38
- gem.add_development_dependency 'sqlite3'
38
+ gem.add_development_dependency 'sqlite3', '>= 1.3.3', '< 1.5'
39
39
  gem.add_development_dependency 'pg', '~> 0.18.0'
40
- gem.add_development_dependency 'mysql2', '~> 0.3.11'
40
+ gem.add_development_dependency 'mysql2', '>= 0.3.13', '< 0.6.0'
41
41
  end
42
42
  end
@@ -4,5 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "activerecord", "~> 4.2.0"
6
6
  gem "activesupport", "~> 4.2.0"
7
+ gem "sqlite3", "~> 1.3.0"
8
+ gem "bigdecimal", "1.3.5"
7
9
 
8
10
  gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.0.0"
6
+ gem "activesupport", "~> 5.0.0"
7
+ gem "sqlite3", "~> 1.3.6"
8
+
9
+ gemspec :path => "../"
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "activerecord", "~> 5.1"
6
- gem "activesupport", "~> 5.1"
5
+ gem "activerecord", "~> 5.1.0"
6
+ gem "activesupport", "~> 5.1.0"
7
7
 
8
8
  gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 5.2.0"
6
+ gem "activesupport", "~> 5.2.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.0.0"
6
+ gem "activesupport", "~> 6.0.0"
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 6.1.0"
6
+ gem "activesupport", "~> 6.1.0"
7
+ gem "pg", "~> 1.1"
8
+
9
+ gemspec :path => "../"
data/lib/crypt_keeper.rb CHANGED
@@ -4,7 +4,7 @@ require 'crypt_keeper/version'
4
4
  require 'crypt_keeper/model'
5
5
  require 'crypt_keeper/helper'
6
6
  require 'crypt_keeper/provider/base'
7
- require 'crypt_keeper/provider/aes_new'
7
+ require 'crypt_keeper/provider/active_support'
8
8
  require 'crypt_keeper/provider/mysql_aes_new'
9
9
  require 'crypt_keeper/provider/postgres_base'
10
10
  require 'crypt_keeper/provider/postgres_pgp'
@@ -12,6 +12,10 @@ module CryptKeeper
12
12
  def escape_and_execute_sql(query, new_transaction: false)
13
13
  query = ::ActiveRecord::Base.send :sanitize_sql_array, query
14
14
 
15
+ # force binary encoding to avoid "invalid byte sequence in UTF-8" errors
16
+ # when we send binary AES keys (f.ex) to the database
17
+ query = query.b if query.respond_to?(:b)
18
+
15
19
  if CryptKeeper.silence_logs?
16
20
  ::ActiveRecord::Base.logger.silence do
17
21
  execute_sql(query, new_transaction: new_transaction)
@@ -39,10 +43,84 @@ module CryptKeeper
39
43
  end
40
44
 
41
45
  module DigestPassphrase
46
+ # Private: Hash algorithm to use when generating a PBKDF2 passphrase.
47
+ #
48
+ # Returns a String.
49
+ HASH_ALGORITHM = "sha512".freeze
50
+
51
+ # Private: Iterations to use when generating a PBKDF2 passphrase.
52
+ #
53
+ # Returns a String.
54
+ ITERATIONS = 5000
55
+
56
+ private_constant :HASH_ALGORITHM, :ITERATIONS
57
+
58
+ # Public: Iterations to use to generate digest passphrase.
59
+ #
60
+ # Returns a String.
61
+ mattr_accessor :iterations do
62
+ if iter = ENV["ARMOR_ITER"]
63
+ warn :ARMOR_ITER unless iter == ITERATIONS
64
+ Integer(iter)
65
+ else
66
+ ITERATIONS
67
+ end
68
+ end
69
+
70
+ # Public: Generates a hex passphrase using the given key and salt.
71
+ #
72
+ # key - Encryption key
73
+ # salt - Encryption salt
74
+ #
75
+ # Returns a String.
42
76
  def digest_passphrase(key, salt)
43
- raise ArgumentError.new("Missing :key") if key.blank?
77
+ raise ArgumentError.new("Missing :key") if key.blank?
44
78
  raise ArgumentError.new("Missing :salt") if salt.blank?
45
- ::Armor.digest(key, salt)
79
+
80
+ require "openssl"
81
+
82
+ digest = OpenSSL::Digest.new(hash_algorithm)
83
+
84
+ hmac = OpenSSL::PKCS5.pbkdf2_hmac(
85
+ key,
86
+ salt,
87
+ iterations,
88
+ digest.digest_length,
89
+ digest
90
+ )
91
+
92
+ hmac.unpack("H*").first
93
+ end
94
+
95
+ private
96
+
97
+ # Private: Hash algorithm to use for digest passphrase.
98
+ #
99
+ # Returns a String.
100
+ def hash_algorithm
101
+ if hash = ENV["ARMOR_HASH"]
102
+ warn :ARMOR_HASH unless hash == HASH_ALGORITHM
103
+ hash
104
+ else
105
+ HASH_ALGORITHM
106
+ end
107
+ end
108
+
109
+ # Private: Warns about the deprecated ENV vars used with the Armor gem.
110
+ #
111
+ # key - The ENV variable name
112
+ #
113
+ # Returns a String.
114
+ def warn(key)
115
+ require "active_support/deprecation"
116
+
117
+ ActiveSupport::Deprecation.warn <<-MSG.squish
118
+ CryptKeeper no longer uses the Armor gem to generate passphrases for
119
+ MySQL AES encryption. Your installation is using a non-standard
120
+ value for `ENV["#{key}"]` which affects the way passphrases are
121
+ generated. You will need to re-encrypt your data with this variable
122
+ removed prior to CryptKeeper v3.0.0.
123
+ MSG
46
124
  end
47
125
  end
48
126
  end
@@ -3,7 +3,7 @@ require 'active_support/core_ext/array/extract_options'
3
3
 
4
4
  module CryptKeeper
5
5
  module Model
6
- extend ActiveSupport::Concern
6
+ extend ::ActiveSupport::Concern
7
7
 
8
8
  # Public: Ensures that each field exist and is of type text. This prevents
9
9
  # encrypted data from being truncated.
@@ -0,0 +1,51 @@
1
+ require "active_support/message_encryptor"
2
+
3
+ module CryptKeeper
4
+ module Provider
5
+ class ActiveSupport < Base
6
+ attr_reader :encryptor
7
+
8
+ # Public: Initializes the encryptor
9
+ #
10
+ # options - A hash, :key and :salt are required
11
+ #
12
+ # Returns nothing.
13
+ def initialize(options = {})
14
+ key = options.fetch(:key)
15
+ salt = options.fetch(:salt)
16
+
17
+ @encryptor = ::ActiveSupport::MessageEncryptor.new \
18
+ ::ActiveSupport::KeyGenerator.new(key).generate_key(salt, 32)
19
+ end
20
+
21
+ # Public: Encrypts a string
22
+ #
23
+ # value - Plaintext value
24
+ #
25
+ # Returns an encrypted string
26
+ def encrypt(value)
27
+ encryptor.encrypt_and_sign(value)
28
+ end
29
+
30
+ # Public: Decrypts a string
31
+ #
32
+ # value - Cipher text
33
+ #
34
+ # Returns a plaintext string
35
+ def decrypt(value)
36
+ encryptor.decrypt_and_verify(value)
37
+ end
38
+
39
+ # Public: Searches the table
40
+ #
41
+ # records - ActiveRecord::Relation
42
+ # field - Field name to match
43
+ # criteria - Value to match
44
+ #
45
+ # Returns an Enumerable
46
+ def search(records, field, criteria)
47
+ records.select { |record| record[field] == criteria }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -12,7 +12,7 @@ module CryptKeeper
12
12
  #
13
13
  # options - A hash, :key and :salt are required
14
14
  def initialize(options = {})
15
- ActiveSupport.run_load_hooks(:crypt_keeper_mysql_aes_log, self)
15
+ ::ActiveSupport.run_load_hooks(:crypt_keeper_mysql_aes_log, self)
16
16
  @key = digest_passphrase(options[:key], options[:salt])
17
17
  end
18
18
 
@@ -8,7 +8,7 @@ module CryptKeeper
8
8
  #
9
9
  # options - A hash, :key is required
10
10
  def initialize(options = {})
11
- ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
11
+ ::ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
12
12
 
13
13
  @key = options.fetch(:key) do
14
14
  raise ArgumentError, "Missing :key"
@@ -42,8 +42,14 @@ module CryptKeeper
42
42
  end
43
43
 
44
44
  def search(records, field, criteria)
45
- records.where("(pgp_sym_decrypt(cast(\"#{field}\" AS bytea), ?) = ?)",
46
- key, criteria)
45
+ if criteria.present?
46
+ records
47
+ .where.not("TRIM(BOTH FROM #{field}) = ?", "")
48
+ .where("(pgp_sym_decrypt(cast(\"#{field}\" AS bytea), ?) = ?)",
49
+ key, criteria)
50
+ else
51
+ records.where(field => criteria)
52
+ end
47
53
  end
48
54
 
49
55
  private
@@ -4,7 +4,7 @@ module CryptKeeper
4
4
  attr_accessor :key
5
5
 
6
6
  def initialize(options = {})
7
- ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
7
+ ::ActiveSupport.run_load_hooks(:crypt_keeper_postgres_pgp_log, self)
8
8
 
9
9
  @key = options.fetch(:key) do
10
10
  raise ArgumentError, "Missing :key"
@@ -1,3 +1,3 @@
1
1
  module CryptKeeper
2
- VERSION = "1.1.1"
2
+ VERSION = "2.2.0"
3
3
  end
@@ -0,0 +1,94 @@
1
+ require "spec_helper"
2
+
3
+ describe CryptKeeper::Helper::DigestPassphrase do
4
+ # gem "armor"
5
+ # require "armor"
6
+ #
7
+ # 10.times.with_index(1).map do |_, i|
8
+ # hash = { key: SecureRandom.hex(64), salt: SecureRandom.hex(64) }
9
+ # hash[:armor] = Armor.digest(h[:key], h[:salt])
10
+ # hash
11
+ # end
12
+ let :armor_passphrases do
13
+ [
14
+ {
15
+ key: "c458da1abe465b15563e0f72e567594ca760214f2d11c668c6c0b923c8b5cc972b167410da6290feef3cc652a03d46f9ead9ce6da4cd197b4f66dd40f234aabc",
16
+ salt: "978279087fe82fd33b295df2ca38ae719d93c73d154110f9cd9e6c1859cd1525af1a9f9c25768b4bd7cfe6ce6fcd473d5f702c67fb5552369aeb7b5ab9c0573c",
17
+ armor: "2493abd50731ffbd80129bd9f2b77bcbffbb42a44f0e263668f9cf5f3d1804e1f9a56a9c85856be87b21b72a8b09aa21e111a646edae4a14b651019a73074066"
18
+ },
19
+ {
20
+ key: "3d4dd8193cf4c0ecaac0f3a1502a1e21a9bad969c97ace34399a4829a3457b56aa598b60a3031b6a10961e1558c7147d3895ec522eba0283a962bd82c6f558ef",
21
+ salt: "f5e7225b5add4a81e93773d543246ba2f7052e65f5adb651f8f47ba129e71053e04839db2ffb5b491223f589b2c74d4440b2c07d41e064df940aa433591a37b1",
22
+ armor: "1e9386895b6d33fed5caf24c6db598df888354ad2605679181d7e2b7c4b59611579d2b1a4e72b834f230848dd715a0b7e37280f2304a2fc78c6f5037a0ef3e9c"
23
+ },
24
+ {
25
+ key: "2d19e58724b7bb62d6129e2af692854919daeb6cf477c1e5dd5f8c846dbae3768baaaa8ba79d39c765b88b68d510f3b2fb59389af5f99c68c2e77620fe610393",
26
+ salt: "a96bfc08af98c063e0f9b3e6498d90a33cf279d12d38d6f5aabd6b7691d5ed25346bc2e9cfee1c98b8cea243b7e79ad69e281800207ea022007dc42e2f49dce3",
27
+ armor: "e86340f12e833d8cc722249f7190184562d4b7b27755ca3ece1950cf9158629536b16609406f230902f6f15c15afa652c35c0b0239c90167efd438b1e1233fd1"
28
+ },
29
+ {
30
+ key: "e78715a772f930027b0e6353e3be578ebc8fd5631401be9e723f747e03f3bed90cb051fa25024e8179e8fc3c65072d74eee92a636e5c2fa89f61f3f1dc2d043b",
31
+ salt: "536d128ba1f2453040d31eaddb87d1b1d2ac157f2ec7f6d2ef530a5ae01f19afea43cae3ca5783440c90decc9cedf78ec6aad474b2e67637abd640dc9d8b1665",
32
+ armor: "96c617786de65bda0d287851a189d50b56a14f3fccc72886943609e0644a593c4d51221d93f10287a79becb3f8f26322413c5c4e65a1e6564b4da55d1f175402"
33
+ },
34
+ {
35
+ key: "941e93a03156121bf565d62d9fadfc5f56ec5ecfc274a5ad1298d358419b8e18b5e4c4b17426ae031676e11633f875a5e3e39e50a4b08685427a72ab6289e465",
36
+ salt: "114b89e478b19e5e0d4df10b1914892a7434fdd0857bbb236bb21d9d8fae03b5401b1a53d64390ffa18fc74bc80139a8f216eb6a0c79155bcd89955a36be0322",
37
+ armor: "ead4be935420a772cc2c2637229431ee093ebcadb1f432d394eeca45515c4b74b732eec76c6d8aff33f0aafd33cab7e2d967796a271d3e8a10be5655d1025708"
38
+ },
39
+ {
40
+ key: "c7c7dbd592beb79178cdbbd45f91654a40c44fd632de0d89398dc1f0c8b241210b8db538a3cb3699796323fc1c54877c3df959e94bf5765fafd03c05fb4d4d7f",
41
+ salt: "1cd67ceaf2c1635eda3d1bbea60f228fd95d59c59a34624b4abf6846a2ae774f359a48695e6a9df5aef34f42dcfa69ad6dbd5be1e7190b050e0c58f5a0ff0cce",
42
+ armor: "387c92c591510bf56677a4de231065d271c6553c8b75a1a1710bb4a7c1404a527e5cbd9f0b812eedc162e5bd5dae4e11e9b5f3782eb4fd4616e2725fa98093e4"
43
+ },
44
+ {
45
+ key: "6fa240131e1aaf2214dc4a5f52709721c078ef8cb4aba1ab926650b202cfeb8db90acaebb0109667613768be3e0af1decf739c108861aa5ac55248bd307e8cbe",
46
+ salt: "de1d28b96bf570bcc8c26949d1d9f4c03b30f4d2e7799df3d4f981384b731f15b5494095cb6bd8d71f374d1bde10b6f9048bb6eb6a763af74af6b70a8320e651",
47
+ armor: "e9b72ed9330477421c63d38a3dfe056fa5ee5d2097af6f024b4452df6010e2563daca906b7ec49c04071973c49cc010f41b5c4503e691d2bc2066a5ff9766be5"
48
+ },
49
+ {
50
+ key: "2accf78dc18db506512c5aa36b6d50950ec98473873fe75a2fd6322883346a02d77f014f9fd5e02d1d3ab34e4c46e93bb17b5d2436f76ec13fade56cdb97ce04",
51
+ salt: "e1ee82d540543beda0a6f8c362e2890665a5b6cdfcb115bf4af3808ac49e43e7ca286d7fc4921f6ad5344a1b082db2e47913cd4616b23e0299f45c09487c65dc",
52
+ armor: "ad84346e9bec6885902390f7a45d674e48b53481400d7008b45780c2677f3a93635c6000b2760ccee184839edb2bd2b34914dc70f322005e58216e013d73cdeb"
53
+ },
54
+ {
55
+ key: "05e3508b0f4a1116fadbbd04a6c4c0a38c29724c76d7c406313813300cefbf9dbe426c28f65ca7297927707abaa4788572b1b9835a421d8699f29096055bc156",
56
+ salt: "371380fde21244493ec04928154c6af685d16e4a7feeb79b5916b484d485f3137eb30ebffdd0bebd77f86a2f092155e798acc0dac91b1704244614b0940da88e",
57
+ armor: "d573f9bb1ed0f7dac9a4f087abe7400cb366cda2877ed8c1660bd6dd2b96533f1210883079921a35fa09339d5f84fbe8b4977a6621e783ee5c3ac85e80b3685c"
58
+ },
59
+ {
60
+ key: "9dd15dc718b7ae4a87652e51b64aa545b6b6f26726e76fb0c45ec256be9359318d68e94858b0d782ba8505201c1b7a604492ba831ad0e93185fc5ccffbc7007d",
61
+ salt: "971bc4fada6fdc5ba3484d7a2ef34a4a2c9f7cc7e8c67d00fc2c774b2db2ef3a4f81c4bc4977055248256fe68f1525fa2f8673d56b41a5e15aae45b68d7f6fc6",
62
+ armor: "3d9d44881595a083efae99e710bd1fe68321152ac61e2d225974d5e2c2d4ae24849cc7b0f7f9af325fb4aa20524d12565e9e4c36cad91f9165df3af2f4692a07"
63
+ }
64
+ ]
65
+ end
66
+
67
+ let :example_key do
68
+ "ff17f8eeeb25e8647cda72fc577b1e9f3f68f6f18997efcdb043f171ad93949b39150b886d4866230add5a0ef6285b37c20bca5194a4dc76ae8845ed88ccb2db"
69
+ end
70
+
71
+ let :example_salt do
72
+ "8f273e1ffae67cf5582dd5fcec9d7f99e996861a985e502aa1283088a27d9fdda63869007474ef689381f10ea4f8f5e9e72255a9e47304cd80fbfafd414d77a7"
73
+ end
74
+
75
+ let :example_passphrase do
76
+ "01096fb7199ac8051d24a350204df61a0ab5b7c6bc393a3325d5dc686de39df7084d7cab12065eb06f2e09f4bc5b6a3851ce459e7eca47088fd9d1bf1d9f35b5"
77
+ end
78
+
79
+ let :encryptor do
80
+ Class.new do
81
+ include CryptKeeper::Helper::DigestPassphrase
82
+ end.new
83
+ end
84
+
85
+ it "returns passphrases that are backwards compatible with Armor" do
86
+ armor_passphrases.each do |ex|
87
+ expect(encryptor.digest_passphrase(ex[:key], ex[:salt])).to eq(ex[:armor])
88
+ end
89
+ end
90
+
91
+ it "generates a passphrase from key/salt" do
92
+ expect(encryptor.digest_passphrase(example_key, example_salt)).to eq(example_passphrase)
93
+ end
94
+ end
@@ -25,8 +25,7 @@ describe CryptKeeper::Model do
25
25
  end
26
26
 
27
27
  it "allows binary as a valid type" do
28
- subject.crypt_keeper :storage, encryptor: :fake_encryptor
29
- allow(subject.columns_hash['storage']).to receive(:type).and_return(:binary)
28
+ subject.crypt_keeper :storage_binary, encryptor: :fake_encryptor
30
29
  expect(subject.new.save).to be_truthy
31
30
  end
32
31
 
@@ -55,6 +54,7 @@ describe CryptKeeper::Model do
55
54
  end
56
55
  end
57
56
 
57
+
58
58
  context "Encryption and Decryption" do
59
59
  let(:plain_text) { 'plain_text' }
60
60
  let(:cipher_text) { 'tooltxet_nialp' }
@@ -105,6 +105,21 @@ describe CryptKeeper::Model do
105
105
  expect_any_instance_of(CryptKeeper::Provider::Encryptor).to_not receive(:decrypt)
106
106
  subject.find(record.id).storage
107
107
  end
108
+
109
+ context "with a binary database field" do
110
+ subject { create_encrypted_model :storage_binary, passphrase: 'tool', encryptor: :encryptor }
111
+
112
+ it "encrypts the data" do
113
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:encrypt).with('testing')
114
+ subject.create!(storage_binary: 'testing')
115
+ end
116
+
117
+ it "decrypts the data" do
118
+ record = subject.create!(storage_binary: 'testing')
119
+ expect_any_instance_of(CryptKeeper::Provider::Encryptor).to receive(:decrypt).at_least(1).times.with('toolgnitset')
120
+ subject.find(record.id).storage_binary
121
+ end
122
+ end
108
123
  end
109
124
 
110
125
  context "Search" do
@@ -120,7 +135,7 @@ describe CryptKeeper::Model do
120
135
  end
121
136
 
122
137
  context "Encodings" do
123
- subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
138
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :active_support, encoding: 'utf-8' }
124
139
 
125
140
  it "forces the encoding on decrypt" do
126
141
  record = subject.create!(storage: 'Tromsø')
@@ -137,7 +152,7 @@ describe CryptKeeper::Model do
137
152
  end
138
153
 
139
154
  context "Initial Table Encryption" do
140
- subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
155
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :active_support }
141
156
 
142
157
  before do
143
158
  subject.delete_all
@@ -146,14 +161,14 @@ describe CryptKeeper::Model do
146
161
  end
147
162
 
148
163
  it "encrypts the table" do
149
- expect { subject.first(5).map(&:storage) }.to raise_error(OpenSSL::Cipher::CipherError)
164
+ expect { subject.first(5).map(&:storage) }.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature)
150
165
  subject.encrypt_table!
151
166
  expect { subject.first(5).map(&:storage) }.not_to raise_error
152
167
  end
153
168
  end
154
169
 
155
170
  context "Table Decryption (Reverse of Initial Table Encryption)" do
156
- subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new }
171
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :active_support }
157
172
  let!(:storage_entries) { 5.times.map { |i| "testing#{i}" } }
158
173
 
159
174
  before do
@@ -168,7 +183,7 @@ describe CryptKeeper::Model do
168
183
  end
169
184
 
170
185
  context "Missing Attributes" do
171
- subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :aes_new, encoding: 'utf-8' }
186
+ subject { create_encrypted_model :storage, key: 'tool', salt: 'salt', encryptor: :active_support, encoding: 'utf-8' }
172
187
 
173
188
  it "doesn't attempt decryption of missing attributes" do
174
189
  subject.create!(storage: 'blah')
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe CryptKeeper::Provider::ActiveSupport do
4
+ subject { described_class.new(key: 'cake', salt: 'salt') }
5
+
6
+ let :plaintext do
7
+ "string"
8
+ end
9
+
10
+ let :encrypted do
11
+ subject.encrypt plaintext
12
+ end
13
+
14
+ let :decrypted do
15
+ subject.decrypt encrypted
16
+ end
17
+
18
+ describe "#encrypt" do
19
+ specify { expect(encrypted).to_not eq(plaintext) }
20
+ specify { expect(encrypted).to_not be_blank }
21
+ end
22
+
23
+ describe "#decrypt" do
24
+ specify { expect(decrypted).to eq(plaintext) }
25
+ specify { expect(decrypted).to_not be_blank }
26
+ end
27
+
28
+ describe "#search" do
29
+ let :records do
30
+ [{ name: 'Bob' }, { name: 'Tim' }]
31
+ end
32
+
33
+ it "finds the matching record" do
34
+ expect(subject.search(records, :name, 'Bob')).to eql([records.first])
35
+ end
36
+ end
37
+ end
@@ -7,14 +7,16 @@ describe CryptKeeper::Provider::MysqlAesNew do
7
7
 
8
8
  # MySQL stores AES encrypted strings in binary which you can't paste
9
9
  # into a spec :). This is a Base64 encoded string of 'test' AES encrypted
10
- # by AES_ENCRYPT()
10
+ # by AES_ENCRYPT():
11
+ #
12
+ # SELECT TO_BASE64(AES_ENCRYPT('test', 'cd9c9275c80ccaec820f988edafd92bd0403d2055aed2b7094f569bc5314b88720c155f7f377addc35eff90c8bd11c34d5daaab5051c3435d2f5ad0c09d4b43e'));
11
13
  let(:cipher_text) do
12
- "fBN8i7bx/DGAA4NJ4EWi0A=="
14
+ "qlwlvp6+otN1qnchZ48zNQ=="
13
15
  end
14
16
 
15
17
  subject { described_class.new key: ENCRYPTION_PASSWORD, salt: 'salt' }
16
18
 
17
- specify { expect(subject.key).to eq("825e8c5e8ca394818b307b22b8cb7d3df2735e9c1e5838b476e7719135a4f499f2133022c1a0e8597c9ac1507b0f0c44328a40049f9704fab3598c5dec120724") }
19
+ specify { expect(subject.key).to eq("cd9c9275c80ccaec820f988edafd92bd0403d2055aed2b7094f569bc5314b88720c155f7f377addc35eff90c8bd11c34d5daaab5051c3435d2f5ad0c09d4b43e") }
18
20
 
19
21
  describe "#initialize" do
20
22
  specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
@@ -64,6 +64,42 @@ describe CryptKeeper::Provider::PostgresPgp do
64
64
  match = subject.create!(storage: 'blah')
65
65
  expect(subject.search_by_plaintext(:storage, 'blah').first).to eq(match)
66
66
  end
67
+
68
+ it "successfully accesses result when the data has empty strings and nil" do
69
+ subject.create!(storage: nil)
70
+ subject.create!(storage: '')
71
+ subject.create!(storage: ' ')
72
+ match = subject.create!(storage: 'blah')
73
+
74
+ results = subject.search_by_plaintext(:storage, 'blah')
75
+
76
+ expect(results).to match_array([match])
77
+ end
78
+
79
+ it "finds an empty string" do
80
+ subject.create!(storage: nil)
81
+ subject.create!(storage: 'blah')
82
+ match = subject.create!(storage: '')
83
+
84
+ expect(subject.search_by_plaintext(:storage, '')).to match_array([match])
85
+ end
86
+
87
+ it "finds the empty string with the right number of spaces" do
88
+ subject.create!(storage: '')
89
+ match = subject.create!(storage: ' ')
90
+
91
+ results = subject.search_by_plaintext(:storage, ' ')
92
+
93
+ expect(results).to match_array([match])
94
+ end
95
+
96
+ it "finds nil results" do
97
+ subject.create!(storage: '')
98
+ subject.create!(storage: 'blah')
99
+ match = subject.create!(storage: nil)
100
+
101
+ expect(subject.search_by_plaintext(:storage, nil)).to match_array([match])
102
+ end
67
103
  end
68
104
 
69
105
  describe "Custom pgcrypto options" do
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,3 @@
1
- ENV['ARMOR_ITER'] ||= "10"
2
1
  ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION'] = "true"
3
2
  require 'coveralls'
4
3
  Coveralls.wear!
@@ -15,7 +14,11 @@ RSpec.configure do |config|
15
14
  config.filter_run :focus
16
15
 
17
16
  config.after :each do
18
- ActiveRecord::Base.descendants.each do |model|
17
+ descendants = ActiveRecord::Base.descendants.delete_if do |descendant|
18
+ descendant.to_s.include?("SchemaMigration")
19
+ end
20
+
21
+ descendants.each do |model|
19
22
  model.method(:delete_all).call
20
23
  end
21
24
  end
@@ -18,6 +18,7 @@ module CryptKeeper
18
18
  create_table :sensitive_data, :force => true do |t|
19
19
  t.column :name, :string
20
20
  t.column :storage, :text
21
+ t.column :storage_binary, :binary
21
22
  t.column :secret, :text
22
23
  end
23
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crypt_keeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Mazzi
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-22 00:00:00.000000000 Z
11
+ date: 2021-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '4.2'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '5.2'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,7 +29,7 @@ dependencies:
29
29
  version: '4.2'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '5.2'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -39,7 +39,7 @@ dependencies:
39
39
  version: '4.2'
40
40
  - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '5.2'
42
+ version: '6.2'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -49,35 +49,7 @@ dependencies:
49
49
  version: '4.2'
50
50
  - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '5.2'
53
- - !ruby/object:Gem::Dependency
54
- name: aes
55
- requirement: !ruby/object:Gem::Requirement
56
- requirements:
57
- - - "~>"
58
- - !ruby/object:Gem::Version
59
- version: 0.5.0
60
- type: :runtime
61
- prerelease: false
62
- version_requirements: !ruby/object:Gem::Requirement
63
- requirements:
64
- - - "~>"
65
- - !ruby/object:Gem::Version
66
- version: 0.5.0
67
- - !ruby/object:Gem::Dependency
68
- name: armor
69
- requirement: !ruby/object:Gem::Requirement
70
- requirements:
71
- - - "~>"
72
- - !ruby/object:Gem::Version
73
- version: 0.0.2
74
- type: :runtime
75
- prerelease: false
76
- version_requirements: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - "~>"
79
- - !ruby/object:Gem::Version
80
- version: 0.0.2
52
+ version: '6.2'
81
53
  - !ruby/object:Gem::Dependency
82
54
  name: rspec
83
55
  requirement: !ruby/object:Gem::Requirement
@@ -182,14 +154,20 @@ dependencies:
182
154
  requirements:
183
155
  - - ">="
184
156
  - !ruby/object:Gem::Version
185
- version: '0'
157
+ version: 1.3.3
158
+ - - "<"
159
+ - !ruby/object:Gem::Version
160
+ version: '1.5'
186
161
  type: :development
187
162
  prerelease: false
188
163
  version_requirements: !ruby/object:Gem::Requirement
189
164
  requirements:
190
165
  - - ">="
191
166
  - !ruby/object:Gem::Version
192
- version: '0'
167
+ version: 1.3.3
168
+ - - "<"
169
+ - !ruby/object:Gem::Version
170
+ version: '1.5'
193
171
  - !ruby/object:Gem::Dependency
194
172
  name: pg
195
173
  requirement: !ruby/object:Gem::Requirement
@@ -208,16 +186,22 @@ dependencies:
208
186
  name: mysql2
209
187
  requirement: !ruby/object:Gem::Requirement
210
188
  requirements:
211
- - - "~>"
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: 0.3.13
192
+ - - "<"
212
193
  - !ruby/object:Gem::Version
213
- version: 0.3.11
194
+ version: 0.6.0
214
195
  type: :development
215
196
  prerelease: false
216
197
  version_requirements: !ruby/object:Gem::Requirement
217
198
  requirements:
218
- - - "~>"
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: 0.3.13
202
+ - - "<"
219
203
  - !ruby/object:Gem::Version
220
- version: 0.3.11
204
+ version: 0.6.0
221
205
  description: Transparent ActiveRecord encryption
222
206
  email:
223
207
  - jmazzi@gmail.com
@@ -236,23 +220,28 @@ files:
236
220
  - Rakefile
237
221
  - crypt_keeper.gemspec
238
222
  - gemfiles/activerecord_4_2.gemfile
223
+ - gemfiles/activerecord_5_0.gemfile
239
224
  - gemfiles/activerecord_5_1.gemfile
225
+ - gemfiles/activerecord_5_2.gemfile
226
+ - gemfiles/activerecord_6_0.gemfile
227
+ - gemfiles/activerecord_6_1.gemfile
240
228
  - lib/crypt_keeper.rb
241
229
  - lib/crypt_keeper/helper.rb
242
230
  - lib/crypt_keeper/log_subscriber/mysql_aes.rb
243
231
  - lib/crypt_keeper/log_subscriber/postgres_pgp.rb
244
232
  - lib/crypt_keeper/model.rb
245
- - lib/crypt_keeper/provider/aes_new.rb
233
+ - lib/crypt_keeper/provider/active_support.rb
246
234
  - lib/crypt_keeper/provider/base.rb
247
235
  - lib/crypt_keeper/provider/mysql_aes_new.rb
248
236
  - lib/crypt_keeper/provider/postgres_base.rb
249
237
  - lib/crypt_keeper/provider/postgres_pgp.rb
250
238
  - lib/crypt_keeper/provider/postgres_pgp_public_key.rb
251
239
  - lib/crypt_keeper/version.rb
240
+ - spec/crypt_keeper/helper_spec.rb
252
241
  - spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb
253
242
  - spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb
254
243
  - spec/crypt_keeper/model_spec.rb
255
- - spec/crypt_keeper/provider/aes_new_spec.rb
244
+ - spec/crypt_keeper/provider/active_support_spec.rb
256
245
  - spec/crypt_keeper/provider/mysql_aes_new_spec.rb
257
246
  - spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb
258
247
  - spec/crypt_keeper/provider/postgres_pgp_spec.rb
@@ -267,7 +256,9 @@ homepage: http://jmazzi.github.com/crypt_keeper/
267
256
  licenses:
268
257
  - MIT
269
258
  metadata: {}
270
- post_install_message:
259
+ post_install_message: 'WARNING: CryptKeeper 2.0 contains breaking changes and may
260
+ require you to reencrypt your data! Please view the README at https://github.com/jmazzi/crypt_keeper
261
+ for more information.'
271
262
  rdoc_options: []
272
263
  require_paths:
273
264
  - lib
@@ -282,16 +273,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
282
273
  - !ruby/object:Gem::Version
283
274
  version: '0'
284
275
  requirements: []
285
- rubyforge_project:
286
- rubygems_version: 2.6.11
287
- signing_key:
276
+ rubygems_version: 3.0.9
277
+ signing_key:
288
278
  specification_version: 4
289
279
  summary: Transparent ActiveRecord encryption
290
280
  test_files:
281
+ - spec/crypt_keeper/helper_spec.rb
291
282
  - spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb
292
283
  - spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb
293
284
  - spec/crypt_keeper/model_spec.rb
294
- - spec/crypt_keeper/provider/aes_new_spec.rb
285
+ - spec/crypt_keeper/provider/active_support_spec.rb
295
286
  - spec/crypt_keeper/provider/mysql_aes_new_spec.rb
296
287
  - spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb
297
288
  - spec/crypt_keeper/provider/postgres_pgp_spec.rb
@@ -1,53 +0,0 @@
1
- require 'aes'
2
- require 'armor'
3
-
4
- module CryptKeeper
5
- module Provider
6
- class AesNew < Base
7
- include CryptKeeper::Helper::DigestPassphrase
8
-
9
- # Public: The encryption key
10
- attr_accessor :key
11
-
12
- # Public: Initializes the class
13
- #
14
- # options - A hash of options. :key and :salt are required
15
- def initialize(options = {})
16
- @key = digest_passphrase(options[:key], options[:salt])
17
- end
18
-
19
- # Public: Encrypt a string
20
- #
21
- # Note: nil and empty strings are not encryptable with AES.
22
- # When they are encountered, the orignal value is returned.
23
- # Otherwise, returns the encrypted string
24
- #
25
- # Returns a String
26
- def encrypt(value)
27
- AES.encrypt(value, key)
28
- end
29
-
30
- # Public: Decrypt a string
31
- #
32
- # Note: nil and empty strings are not encryptable with AES (and thus cannot be decrypted).
33
- # When they are encountered, the orignal value is returned.
34
- # Otherwise, returns the decrypted string
35
- #
36
- # Returns a String
37
- def decrypt(value)
38
- AES.decrypt(value, key)
39
- end
40
-
41
- # Public: Search for a record
42
- #
43
- # record - An ActiveRecord collection
44
- # field - The field to search
45
- # criteria - A string to search with
46
- #
47
- # Returns an Enumerable
48
- def search(records, field, criteria)
49
- records.select { |record| record[field] == criteria }
50
- end
51
- end
52
- end
53
- end
@@ -1,41 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe CryptKeeper::Provider::AesNew do
4
- subject { described_class.new(key: 'cake', salt: 'salt') }
5
-
6
- describe "#initialize" do
7
- let(:digested_key) do
8
- ::Armor.digest('cake', 'salt')
9
- end
10
-
11
- specify { expect(subject.key).to eq(digested_key) }
12
- specify { expect { described_class.new }.to raise_error(ArgumentError, "Missing :key") }
13
- end
14
-
15
- describe "#encrypt" do
16
- let(:encrypted) do
17
- subject.encrypt 'string'
18
- end
19
-
20
- specify { expect(encrypted).to_not eq('string') }
21
- specify { expect(encrypted).to_not be_blank }
22
- end
23
-
24
- describe "#decrypt" do
25
- let(:decrypted) do
26
- subject.decrypt "V02ebRU2wLk25AizasROVg==$kE+IpRaUNdBfYqR+WjMqvA=="
27
- end
28
-
29
- specify { expect(decrypted).to eq('string') }
30
- end
31
-
32
- describe "#search" do
33
- let(:records) do
34
- [{ name: 'Bob' }, { name: 'Tim' }]
35
- end
36
-
37
- it "finds the matching record" do
38
- expect(subject.search(records, :name, 'Bob')).to eql([records.first])
39
- end
40
- end
41
- end