crypt_keeper 0.15.0.pre → 0.16.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/Appraisals +5 -0
- data/README.md +26 -7
- data/bin/crypt_keeper +99 -0
- data/crypt_keeper.gemspec +4 -2
- data/gemfiles/activerecord_3_1.gemfile.lock +10 -6
- data/gemfiles/activerecord_3_2.gemfile.lock +10 -6
- data/gemfiles/activerecord_4_0.gemfile.lock +10 -6
- data/gemfiles/activerecord_4_1.gemfile +8 -0
- data/gemfiles/activerecord_4_1.gemfile.lock +117 -0
- data/lib/crypt_keeper/helper.rb +8 -0
- data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +1 -1
- data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +3 -3
- data/lib/crypt_keeper/provider/aes.rb +9 -8
- data/lib/crypt_keeper/provider/aes_new.rb +53 -0
- data/lib/crypt_keeper/provider/mysql_aes.rb +7 -2
- data/lib/crypt_keeper/provider/mysql_aes_new.rb +43 -0
- data/lib/crypt_keeper/provider/postgres_pgp.rb +1 -1
- data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +57 -0
- data/lib/crypt_keeper/version.rb +1 -1
- data/lib/crypt_keeper.rb +3 -0
- data/spec/fixtures/private.asc +60 -0
- data/spec/fixtures/public.asc +31 -0
- data/spec/log_subscriber/postgres_pgp_spec.rb +82 -14
- data/spec/provider/aes_new_spec.rb +45 -0
- data/spec/provider/aes_spec.rb +29 -7
- data/spec/provider/mysql_aes_new_spec.rb +52 -0
- data/spec/provider/mysql_aes_spec.rb +0 -16
- data/spec/provider/postgres_pgp_public_key_spec.rb +70 -0
- data/spec/provider/postgres_pgp_spec.rb +6 -4
- data/spec/spec_helper.rb +16 -1
- data/spec/support/active_record.rb +3 -2
- metadata +53 -10
- data/spec/log_subscriber/mysql_aes_spec.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 742dc47f65ba6f9c21e39dad6b34a25196e74978
|
4
|
+
data.tar.gz: 4e5eaafa1b9876217c739a4c3649fc52308dc0ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f43ec8ae9564d08e804d5539ee04fff90b072502e65128a983c4727953ad930a00cfc2f72bb50d5e6b6585c7bd43bb83199f75bd3cabffb27e317a3f56539dd6
|
7
|
+
data.tar.gz: 097e420adcb718391b14476dff3a8a1c00279bcd211df9cef3f163a424bd1267d5be85b1cfa15fe1773a77cd930cc9fea1c4c7ff0d4a406ca4c7146a570f5d61
|
data/.travis.yml
CHANGED
@@ -4,6 +4,7 @@ gemfile:
|
|
4
4
|
- gemfiles/activerecord_3_1.gemfile
|
5
5
|
- gemfiles/activerecord_3_2.gemfile
|
6
6
|
- gemfiles/activerecord_4_0.gemfile
|
7
|
+
- gemfiles/activerecord_4_1.gemfile
|
7
8
|
before_install:
|
8
9
|
- bundle install
|
9
10
|
before_script:
|
@@ -20,3 +21,6 @@ notifications:
|
|
20
21
|
rvm:
|
21
22
|
- 1.9.3
|
22
23
|
- 2.0.0
|
24
|
+
- 2.1.1
|
25
|
+
addons:
|
26
|
+
postgresql: 9.3
|
data/Appraisals
CHANGED
data/README.md
CHANGED
@@ -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, :key => 'super_good_password'
|
30
|
+
crypt_keeper :field, :other_field, :encryptor => :aes, :key => 'super_good_password', salt: 'salt'
|
31
31
|
end
|
32
32
|
|
33
33
|
model = MyModel.new(field: 'sometext')
|
@@ -51,15 +51,23 @@ update the content without going through the current encryptor.
|
|
51
51
|
|
52
52
|
There are three included encryptors.
|
53
53
|
|
54
|
-
* [AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/
|
54
|
+
* [AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes_new.rb)
|
55
55
|
* Encryption is peformed using AES-256 via OpenSSL.
|
56
|
+
* Passphrases are derived using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
|
56
57
|
|
58
|
+
* [AES Legacy](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes.rb) *DEPRECATED*
|
59
|
+
* Encryption is peformed using AES-256 via OpenSSL.
|
57
60
|
|
58
|
-
* [MySQL AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/
|
61
|
+
* [MySQL AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes_new.rb)
|
59
62
|
* Encryption is peformed MySQL's native AES functions.
|
60
63
|
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/mysql_aes.rb)
|
61
64
|
filtered for you to protect sensitive data from being logged.
|
65
|
+
* Passphrases are derived using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
|
62
66
|
|
67
|
+
* [MySQL AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes.rb) *DEPRECATED*
|
68
|
+
* Encryption is peformed MySQL's native AES functions.
|
69
|
+
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/mysql_aes.rb)
|
70
|
+
filtered for you to protect senitive data from being logged.
|
63
71
|
|
64
72
|
* [PostgreSQL PGP](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/postgres_pgp.rb).
|
65
73
|
* Encryption is performed using PostgresSQL's native [PGP functions](http://www.postgresql.org/docs/9.1/static/pgcrypto.html).
|
@@ -68,15 +76,25 @@ There are three included encryptors.
|
|
68
76
|
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/postgres_pgp.rb)
|
69
77
|
filtered for you to protect senitive data from being logged.
|
70
78
|
* Custom options can be set through the `:pgcrypto_options`. E.g. `crypt_keeper :field, encryptor: :postgres_pgp, pgcrypto_options: 'compress-level=9'
|
79
|
+
* Passphrases are hashed by PostgresSQL itself using a [String2Key (S2K)](http://www.postgresql.org/docs/9.2/static/pgcrypto.html) algorithm. This is rather similar to crypt() algorithms — purposefully slow and with random salt — but it produces a full-length binary key.
|
80
|
+
|
81
|
+
* [PostgreSQL PGP Public Key](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/postgres_pgp_public_key.rb).
|
82
|
+
* Encryption is performed using PostgresSQL's native [PGP functions](http://www.postgresql.org/docs/9.1/static/pgcrypto.html).
|
83
|
+
* It requires the `pgcrypto` PostgresSQL extension:
|
84
|
+
`CREATE EXTENSION IF NOT EXISTS pgcrypto`
|
85
|
+
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/postgres_pgp.rb)
|
86
|
+
filtered for you to protect senitive data from being logged.
|
87
|
+
* Accepts a public and private_key. The private key is optional. If the private key is not present the ciphertext value is returned instead of the plaintext. This allows you to keep the private key off certain servers. Encryption is possible with only a public key. Any server that needs access to the plaintext will need the private key.
|
88
|
+
* Passphrases are hashed by PostgresSQL itself using a [String2Key (S2K)](http://www.postgresql.org/docs/9.2/static/pgcrypto.html) algorithm. This is rather similar to crypt() algorithms — purposefully slow and with random salt — but it produces a full-length binary key.
|
71
89
|
|
72
90
|
## Searching
|
73
|
-
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.
|
91
|
+
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.
|
74
92
|
|
75
93
|
* AES
|
76
94
|
* 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.
|
77
95
|
|
78
96
|
* Mysql AES
|
79
|
-
* Surprisingly, MySQL's implementation of AES does not use a random initialization vector. The column containing the ciphertext can be indexed and searched quickly.
|
97
|
+
* Surprisingly, MySQL's implementation of AES does not use a random initialization vector. The column containing the ciphertext can be indexed and searched quickly.
|
80
98
|
|
81
99
|
* PostgresSQL PGP
|
82
100
|
* 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.
|
@@ -123,10 +141,11 @@ end
|
|
123
141
|
|
124
142
|
## Requirements
|
125
143
|
|
126
|
-
CryptKeeper has been tested against ActiveRecord 3.1, 3.2, 4.0 using ruby
|
127
|
-
1.9.3 and 2.
|
144
|
+
CryptKeeper has been tested against ActiveRecord 3.1, 3.2, 4.0, 4.1 using ruby
|
145
|
+
1.9.3, 2.0.0 and 2.1.1
|
128
146
|
|
129
147
|
ActiveRecord 4.0 is supported starting with v0.11.0.
|
148
|
+
ActiveRecord 4.1 is supported starting with v0.16.0. (unreleased, use master branch)
|
130
149
|
|
131
150
|
## Installation
|
132
151
|
|
data/bin/crypt_keeper
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), "/../lib"))
|
3
|
+
require 'crypt_keeper'
|
4
|
+
require 'yaml'
|
5
|
+
require 'active_support/core_ext'
|
6
|
+
require 'active_record/log_subscriber'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
if !File.exists?('config/database.yml')
|
10
|
+
puts "Please run this from the root of the rails project"
|
11
|
+
exit 1
|
12
|
+
else
|
13
|
+
config = YAML::load_file('./config/database.yml')
|
14
|
+
end
|
15
|
+
|
16
|
+
options = {}
|
17
|
+
OptionParser.new do |opts|
|
18
|
+
opts.banner = "Usage: crypt_keeper [options]"
|
19
|
+
|
20
|
+
opts.on("--old-key [key]", "The old encryption key") do |v|
|
21
|
+
options[:old_key] = v
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on("--new-key [key]", "The new encryption key") do |v|
|
25
|
+
options[:new_key] = v
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("--salt [salt]", "The salt for the new encryption") do |v|
|
29
|
+
options[:salt] = v
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("--table-name [table_name]", "The name of the table") do |v|
|
33
|
+
options[:table_name] = v
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--columns [field1,field2]", "A comma separated list of column names which are encrypted") do |v|
|
37
|
+
options[:columns] = v
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--old-encryptor [old encryptor]", "The old encryptor") do |v|
|
41
|
+
options[:old_encryptor] = v
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on("--query-log [file]", "ActiveRecord query log") do |v|
|
45
|
+
options[:query_log] = v
|
46
|
+
end
|
47
|
+
end.parse!
|
48
|
+
|
49
|
+
def get_required_arg(arg, options)
|
50
|
+
options.fetch(arg) { raise ArgumentError, "--#{arg.to_s.sub('_', '-')} is a required option"}
|
51
|
+
end
|
52
|
+
|
53
|
+
old_key = get_required_arg(:old_key, options)
|
54
|
+
new_key = get_required_arg(:new_key, options)
|
55
|
+
salt = get_required_arg(:salt, options)
|
56
|
+
table_name = get_required_arg(:table_name, options)
|
57
|
+
columns = get_required_arg(:columns, options).split(',')
|
58
|
+
encryptor = get_required_arg(:old_encryptor, options)
|
59
|
+
env = ENV.fetch('RAILS_ENV', 'development')
|
60
|
+
|
61
|
+
if options[:query_log].present?
|
62
|
+
ActiveRecord::Base.logger = Logger.new(options[:query_log])
|
63
|
+
end
|
64
|
+
|
65
|
+
if encryptor == "aes"
|
66
|
+
old_encryptor = CryptKeeper::Provider::Aes.new(key: old_key)
|
67
|
+
new_encryptor = CryptKeeper::Provider::AesNew.new(key: new_key, salt: salt)
|
68
|
+
elsif encryptor == "mysql_aes"
|
69
|
+
old_encryptor = CryptKeeper::Provider::MysqlAes.new(key: old_key)
|
70
|
+
new_encryptor = CryptKeeper::Provider::MysqlAesNew.new(key: new_key, salt: salt)
|
71
|
+
else
|
72
|
+
puts "#{encryptor} is not a known encryptor that can be migrated"
|
73
|
+
exit 1
|
74
|
+
end
|
75
|
+
|
76
|
+
class MigrateData < ActiveRecord::Base
|
77
|
+
def self.reencrypt_all(encrypted_fields, old_encryptor, new_encryptor)
|
78
|
+
ActiveRecord::Base.transaction do
|
79
|
+
find_each do |record|
|
80
|
+
new_hash = {}
|
81
|
+
encrypted_fields.each do |f|
|
82
|
+
plaintext = old_encryptor.decrypt(record[f])
|
83
|
+
ciphertext = new_encryptor.encrypt(plaintext)
|
84
|
+
new_hash[f] = ciphertext
|
85
|
+
end
|
86
|
+
|
87
|
+
record.update_attributes! new_hash
|
88
|
+
yield record if block_given?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
ActiveRecord::Base.establish_connection(config.fetch(env))
|
95
|
+
MigrateData.table_name = table_name
|
96
|
+
|
97
|
+
MigrateData.reencrypt_all(columns, old_encryptor, new_encryptor) do |r|
|
98
|
+
puts "#{r.id} is now re-encrypted"
|
99
|
+
end
|
data/crypt_keeper.gemspec
CHANGED
@@ -16,8 +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', '>= 3.1', '< 4.
|
20
|
-
gem.add_runtime_dependency 'activesupport', '>= 3.1', '< 4.
|
19
|
+
gem.add_runtime_dependency 'activerecord', '>= 3.1', '< 4.2'
|
20
|
+
gem.add_runtime_dependency 'activesupport', '>= 3.1', '< 4.2'
|
21
|
+
gem.add_runtime_dependency 'aes', '~> 0.5.0'
|
22
|
+
gem.add_runtime_dependency 'armor', '~> 0.0.2'
|
21
23
|
|
22
24
|
gem.add_development_dependency 'rspec', '~> 2.13.0'
|
23
25
|
gem.add_development_dependency 'guard', '~> 1.8.0'
|
@@ -1,9 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/justin/work/jmazzi/crypt_keeper
|
3
3
|
specs:
|
4
|
-
crypt_keeper (0.
|
5
|
-
activerecord (>= 3.1, < 4.
|
6
|
-
activesupport (>= 3.1, < 4.
|
4
|
+
crypt_keeper (0.16.0.pre)
|
5
|
+
activerecord (>= 3.1, < 4.2)
|
6
|
+
activesupport (>= 3.1, < 4.2)
|
7
|
+
aes (~> 0.5.0)
|
8
|
+
armor (~> 0.0.2)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
@@ -19,10 +21,12 @@ GEM
|
|
19
21
|
tzinfo (~> 0.3.29)
|
20
22
|
activesupport (3.1.12)
|
21
23
|
multi_json (~> 1.0)
|
24
|
+
aes (0.5.0)
|
22
25
|
appraisal (0.5.2)
|
23
26
|
bundler
|
24
27
|
rake
|
25
28
|
arel (2.2.3)
|
29
|
+
armor (0.0.2)
|
26
30
|
builder (3.0.4)
|
27
31
|
coderay (1.0.9)
|
28
32
|
coveralls (0.7.0)
|
@@ -51,7 +55,7 @@ GEM
|
|
51
55
|
lumberjack (1.0.4)
|
52
56
|
method_source (0.8.2)
|
53
57
|
mime-types (1.25)
|
54
|
-
multi_json (1.8.
|
58
|
+
multi_json (1.8.2)
|
55
59
|
mysql2 (0.3.13)
|
56
60
|
pg (0.15.1)
|
57
61
|
pry (0.9.12.2)
|
@@ -83,8 +87,8 @@ GEM
|
|
83
87
|
term-ansicolor (1.2.2)
|
84
88
|
tins (~> 0.8)
|
85
89
|
thor (0.18.1)
|
86
|
-
tins (0.
|
87
|
-
tzinfo (0.3.
|
90
|
+
tins (0.12.0)
|
91
|
+
tzinfo (0.3.38)
|
88
92
|
|
89
93
|
PLATFORMS
|
90
94
|
ruby
|
@@ -1,9 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/justin/work/jmazzi/crypt_keeper
|
3
3
|
specs:
|
4
|
-
crypt_keeper (0.
|
5
|
-
activerecord (>= 3.1, < 4.
|
6
|
-
activesupport (>= 3.1, < 4.
|
4
|
+
crypt_keeper (0.16.0.pre)
|
5
|
+
activerecord (>= 3.1, < 4.2)
|
6
|
+
activesupport (>= 3.1, < 4.2)
|
7
|
+
aes (~> 0.5.0)
|
8
|
+
armor (~> 0.0.2)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
@@ -19,10 +21,12 @@ GEM
|
|
19
21
|
activesupport (3.2.14)
|
20
22
|
i18n (~> 0.6, >= 0.6.4)
|
21
23
|
multi_json (~> 1.0)
|
24
|
+
aes (0.5.0)
|
22
25
|
appraisal (0.5.2)
|
23
26
|
bundler
|
24
27
|
rake
|
25
28
|
arel (3.0.2)
|
29
|
+
armor (0.0.2)
|
26
30
|
builder (3.0.4)
|
27
31
|
coderay (1.0.9)
|
28
32
|
coveralls (0.7.0)
|
@@ -51,7 +55,7 @@ GEM
|
|
51
55
|
lumberjack (1.0.4)
|
52
56
|
method_source (0.8.2)
|
53
57
|
mime-types (1.25)
|
54
|
-
multi_json (1.8.
|
58
|
+
multi_json (1.8.2)
|
55
59
|
mysql2 (0.3.13)
|
56
60
|
pg (0.15.1)
|
57
61
|
pry (0.9.12.2)
|
@@ -83,8 +87,8 @@ GEM
|
|
83
87
|
term-ansicolor (1.2.2)
|
84
88
|
tins (~> 0.8)
|
85
89
|
thor (0.18.1)
|
86
|
-
tins (0.
|
87
|
-
tzinfo (0.3.
|
90
|
+
tins (0.12.0)
|
91
|
+
tzinfo (0.3.38)
|
88
92
|
|
89
93
|
PLATFORMS
|
90
94
|
ruby
|
@@ -1,9 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: /Users/justin/work/jmazzi/crypt_keeper
|
3
3
|
specs:
|
4
|
-
crypt_keeper (0.
|
5
|
-
activerecord (>= 3.1, < 4.
|
6
|
-
activesupport (>= 3.1, < 4.
|
4
|
+
crypt_keeper (0.16.0.pre)
|
5
|
+
activerecord (>= 3.1, < 4.2)
|
6
|
+
activesupport (>= 3.1, < 4.2)
|
7
|
+
aes (~> 0.5.0)
|
8
|
+
armor (~> 0.0.2)
|
7
9
|
|
8
10
|
GEM
|
9
11
|
remote: https://rubygems.org/
|
@@ -23,10 +25,12 @@ GEM
|
|
23
25
|
multi_json (~> 1.3)
|
24
26
|
thread_safe (~> 0.1)
|
25
27
|
tzinfo (~> 0.3.37)
|
28
|
+
aes (0.5.0)
|
26
29
|
appraisal (0.5.2)
|
27
30
|
bundler
|
28
31
|
rake
|
29
32
|
arel (4.0.0)
|
33
|
+
armor (0.0.2)
|
30
34
|
atomic (1.1.14)
|
31
35
|
builder (3.1.4)
|
32
36
|
coderay (1.0.9)
|
@@ -57,7 +61,7 @@ GEM
|
|
57
61
|
method_source (0.8.2)
|
58
62
|
mime-types (1.25)
|
59
63
|
minitest (4.7.5)
|
60
|
-
multi_json (1.8.
|
64
|
+
multi_json (1.8.2)
|
61
65
|
mysql2 (0.3.13)
|
62
66
|
pg (0.15.1)
|
63
67
|
pry (0.9.12.2)
|
@@ -91,8 +95,8 @@ GEM
|
|
91
95
|
thor (0.18.1)
|
92
96
|
thread_safe (0.1.3)
|
93
97
|
atomic
|
94
|
-
tins (0.
|
95
|
-
tzinfo (0.3.
|
98
|
+
tins (0.12.0)
|
99
|
+
tzinfo (0.3.38)
|
96
100
|
|
97
101
|
PLATFORMS
|
98
102
|
ruby
|
@@ -0,0 +1,117 @@
|
|
1
|
+
PATH
|
2
|
+
remote: /Users/justin/work/jmazzi/crypt_keeper
|
3
|
+
specs:
|
4
|
+
crypt_keeper (0.16.0.pre)
|
5
|
+
activerecord (>= 3.1, < 4.2)
|
6
|
+
activesupport (>= 3.1, < 4.2)
|
7
|
+
aes (~> 0.5.0)
|
8
|
+
armor (~> 0.0.2)
|
9
|
+
|
10
|
+
GEM
|
11
|
+
remote: https://rubygems.org/
|
12
|
+
specs:
|
13
|
+
activemodel (4.1.0)
|
14
|
+
activesupport (= 4.1.0)
|
15
|
+
builder (~> 3.1)
|
16
|
+
activerecord (4.1.0)
|
17
|
+
activemodel (= 4.1.0)
|
18
|
+
activesupport (= 4.1.0)
|
19
|
+
arel (~> 5.0.0)
|
20
|
+
activesupport (4.1.0)
|
21
|
+
i18n (~> 0.6, >= 0.6.9)
|
22
|
+
json (~> 1.7, >= 1.7.7)
|
23
|
+
minitest (~> 5.1)
|
24
|
+
thread_safe (~> 0.1)
|
25
|
+
tzinfo (~> 1.1)
|
26
|
+
aes (0.5.0)
|
27
|
+
appraisal (0.5.2)
|
28
|
+
bundler
|
29
|
+
rake
|
30
|
+
arel (5.0.1.20140414130214)
|
31
|
+
armor (0.0.3)
|
32
|
+
builder (3.2.2)
|
33
|
+
coderay (1.1.0)
|
34
|
+
coveralls (0.7.0)
|
35
|
+
multi_json (~> 1.3)
|
36
|
+
rest-client
|
37
|
+
simplecov (>= 0.7)
|
38
|
+
term-ansicolor
|
39
|
+
thor
|
40
|
+
diff-lcs (1.2.5)
|
41
|
+
docile (1.1.3)
|
42
|
+
ffi (1.9.3)
|
43
|
+
formatador (0.2.4)
|
44
|
+
guard (1.8.3)
|
45
|
+
formatador (>= 0.2.4)
|
46
|
+
listen (~> 1.3)
|
47
|
+
lumberjack (>= 1.0.2)
|
48
|
+
pry (>= 0.9.10)
|
49
|
+
thor (>= 0.14.6)
|
50
|
+
guard-rspec (2.5.4)
|
51
|
+
guard (>= 1.1)
|
52
|
+
rspec (~> 2.11)
|
53
|
+
i18n (0.6.9)
|
54
|
+
json (1.8.1)
|
55
|
+
listen (1.3.1)
|
56
|
+
rb-fsevent (>= 0.9.3)
|
57
|
+
rb-inotify (>= 0.9)
|
58
|
+
rb-kqueue (>= 0.2)
|
59
|
+
lumberjack (1.0.5)
|
60
|
+
method_source (0.8.2)
|
61
|
+
mime-types (2.2)
|
62
|
+
minitest (5.3.3)
|
63
|
+
multi_json (1.9.2)
|
64
|
+
mysql2 (0.3.15)
|
65
|
+
pg (0.15.1)
|
66
|
+
pry (0.9.12.6)
|
67
|
+
coderay (~> 1.0)
|
68
|
+
method_source (~> 0.8)
|
69
|
+
slop (~> 3.4)
|
70
|
+
rake (10.0.4)
|
71
|
+
rb-fsevent (0.9.4)
|
72
|
+
rb-inotify (0.9.3)
|
73
|
+
ffi (>= 0.5.0)
|
74
|
+
rb-kqueue (0.2.2)
|
75
|
+
ffi (>= 0.5.0)
|
76
|
+
rest-client (1.6.7)
|
77
|
+
mime-types (>= 1.16)
|
78
|
+
rspec (2.13.0)
|
79
|
+
rspec-core (~> 2.13.0)
|
80
|
+
rspec-expectations (~> 2.13.0)
|
81
|
+
rspec-mocks (~> 2.13.0)
|
82
|
+
rspec-core (2.13.1)
|
83
|
+
rspec-expectations (2.13.0)
|
84
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
85
|
+
rspec-mocks (2.13.1)
|
86
|
+
simplecov (0.8.2)
|
87
|
+
docile (~> 1.1.0)
|
88
|
+
multi_json
|
89
|
+
simplecov-html (~> 0.8.0)
|
90
|
+
simplecov-html (0.8.0)
|
91
|
+
slop (3.5.0)
|
92
|
+
sqlite3 (1.3.9)
|
93
|
+
term-ansicolor (1.3.0)
|
94
|
+
tins (~> 1.0)
|
95
|
+
thor (0.19.1)
|
96
|
+
thread_safe (0.3.3)
|
97
|
+
tins (1.1.0)
|
98
|
+
tzinfo (1.1.0)
|
99
|
+
thread_safe (~> 0.1)
|
100
|
+
|
101
|
+
PLATFORMS
|
102
|
+
ruby
|
103
|
+
|
104
|
+
DEPENDENCIES
|
105
|
+
activerecord (~> 4.1.0)
|
106
|
+
activesupport (~> 4.1.0)
|
107
|
+
appraisal (~> 0.5.2)
|
108
|
+
coveralls
|
109
|
+
crypt_keeper!
|
110
|
+
guard (~> 1.8.0)
|
111
|
+
guard-rspec (~> 2.5.4)
|
112
|
+
mysql2 (~> 0.3.11)
|
113
|
+
pg (~> 0.15.1)
|
114
|
+
rake (~> 10.0.3)
|
115
|
+
rb-fsevent (~> 0.9.1)
|
116
|
+
rspec (~> 2.13.0)
|
117
|
+
sqlite3
|
data/lib/crypt_keeper/helper.rb
CHANGED
@@ -10,6 +10,14 @@ module CryptKeeper
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
module DigestPassphrase
|
14
|
+
def digest_passphrase(key, salt)
|
15
|
+
raise ArgumentError.new("Missing :key") if key.blank?
|
16
|
+
raise ArgumentError.new("Missing :salt") if salt.blank?
|
17
|
+
::Armor.digest(key, salt)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
13
21
|
module Serializer
|
14
22
|
def dump(value)
|
15
23
|
if value.blank?
|
@@ -12,7 +12,7 @@ module CryptKeeper
|
|
12
12
|
|
13
13
|
# Public: Prevents sensitive data from being logged
|
14
14
|
def sql_with_mysql_aes(event)
|
15
|
-
filter = /(aes_(encrypt|decrypt))\(
|
15
|
+
filter = /(aes_(encrypt|decrypt))\(.*\)/i
|
16
16
|
|
17
17
|
event.payload[:sql] = event.payload[:sql].gsub(filter) do |_|
|
18
18
|
"#{$1}([FILTERED])"
|
@@ -12,10 +12,10 @@ module CryptKeeper
|
|
12
12
|
|
13
13
|
# Public: Prevents sensitive data from being logged
|
14
14
|
def sql_with_postgres_pgp(event)
|
15
|
-
filter = /(
|
15
|
+
filter = /(\(*)pgp_(sym|pub)_(?<operation>decrypt|encrypt)(\(+.*\)+)/im
|
16
16
|
|
17
17
|
event.payload[:sql] = event.payload[:sql].gsub(filter) do |_|
|
18
|
-
"#{
|
18
|
+
"#{$~[:operation]}([FILTERED])"
|
19
19
|
end
|
20
20
|
|
21
21
|
sql_without_postgres_pgp(event)
|
@@ -24,6 +24,6 @@ module CryptKeeper
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
ActiveSupport.on_load :
|
27
|
+
ActiveSupport.on_load :crypt_keeper_postgres_pgp_log do
|
28
28
|
ActiveRecord::LogSubscriber.send :include, CryptKeeper::LogSubscriber::PostgresPgp
|
29
29
|
end
|
@@ -5,7 +5,6 @@ require 'base64'
|
|
5
5
|
module CryptKeeper
|
6
6
|
module Provider
|
7
7
|
class Aes
|
8
|
-
# A value to split the iv and cipher text with
|
9
8
|
SEPARATOR = ":crypt_keeper:"
|
10
9
|
|
11
10
|
# Public: The encryption key
|
@@ -18,6 +17,7 @@ module CryptKeeper
|
|
18
17
|
#
|
19
18
|
# options - A hash of options. :key is required
|
20
19
|
def initialize(options = {})
|
20
|
+
legacy
|
21
21
|
@aes = ::OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
22
22
|
@aes.padding = 1
|
23
23
|
|
@@ -34,6 +34,7 @@ module CryptKeeper
|
|
34
34
|
# When they are encountered, the orignal value is returned.
|
35
35
|
# Otherwise, returns the encrypted string
|
36
36
|
def encrypt(value)
|
37
|
+
return value if value == '' || value.nil?
|
37
38
|
aes.encrypt
|
38
39
|
aes.key = key
|
39
40
|
Base64::encode64("#{aes.random_iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
|
@@ -45,6 +46,7 @@ module CryptKeeper
|
|
45
46
|
# When they are encountered, the orignal value is returned.
|
46
47
|
# Otherwise, returns the decrypted string
|
47
48
|
def decrypt(value)
|
49
|
+
return value if value == '' || value.nil?
|
48
50
|
iv, value = Base64::decode64(value.to_s).split(SEPARATOR)
|
49
51
|
aes.decrypt
|
50
52
|
aes.key = key
|
@@ -52,13 +54,12 @@ module CryptKeeper
|
|
52
54
|
aes.update(value) + aes.final
|
53
55
|
end
|
54
56
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
records.select { |record| record[field] == criteria }
|
57
|
+
private
|
58
|
+
|
59
|
+
def legacy
|
60
|
+
unless ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION']
|
61
|
+
warn "[DEPRECATION] AES Legacy is now deprecated. Please see http://git.io/uYcp2A"
|
62
|
+
end
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'aes'
|
2
|
+
require 'armor'
|
3
|
+
|
4
|
+
module CryptKeeper
|
5
|
+
module Provider
|
6
|
+
class AesNew
|
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
|
@@ -11,6 +11,7 @@ module CryptKeeper
|
|
11
11
|
#
|
12
12
|
# options - A hash, :key is required
|
13
13
|
def initialize(options = {})
|
14
|
+
legacy
|
14
15
|
ActiveSupport.run_load_hooks(:crypt_keeper_mysql_aes_log, self)
|
15
16
|
|
16
17
|
@key = options.fetch(:key) do
|
@@ -34,8 +35,12 @@ module CryptKeeper
|
|
34
35
|
["SELECT AES_DECRYPT(?, ?)", Base64.decode64(value), key]).first
|
35
36
|
end
|
36
37
|
|
37
|
-
|
38
|
-
|
38
|
+
private
|
39
|
+
|
40
|
+
def legacy
|
41
|
+
unless ENV['CRYPT_KEEPER_IGNORE_LEGACY_DEPRECATION']
|
42
|
+
warn "[DEPRECATION] MySqlAes Legacy is now deprecated. Please see http://git.io/nXXOlg"
|
43
|
+
end
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|