crypt_keeper 0.16.0.pre → 1.1.1
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/.gitignore +1 -1
- data/.travis.yml +22 -13
- data/Appraisals +6 -16
- data/README.md +43 -21
- data/Rakefile +7 -3
- data/crypt_keeper.gemspec +9 -9
- data/gemfiles/activerecord_4_2.gemfile +8 -0
- data/gemfiles/activerecord_5_1.gemfile +8 -0
- data/lib/crypt_keeper/helper.rb +31 -21
- data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +11 -11
- data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +29 -10
- data/lib/crypt_keeper/model.rb +64 -17
- data/lib/crypt_keeper/provider/aes_new.rb +1 -1
- data/lib/crypt_keeper/provider/base.rb +21 -0
- data/lib/crypt_keeper/provider/mysql_aes_new.rb +2 -2
- data/lib/crypt_keeper/provider/postgres_base.rb +28 -0
- data/lib/crypt_keeper/provider/postgres_pgp.rb +27 -8
- data/lib/crypt_keeper/provider/postgres_pgp_public_key.rb +4 -20
- data/lib/crypt_keeper/version.rb +1 -1
- data/lib/crypt_keeper.rb +11 -2
- data/spec/crypt_keeper/log_subscriber/mysql_aes_spec.rb +50 -0
- data/spec/crypt_keeper/log_subscriber/postgres_pgp_spec.rb +78 -0
- data/spec/crypt_keeper/model_spec.rb +178 -0
- data/spec/crypt_keeper/provider/aes_new_spec.rb +41 -0
- data/spec/crypt_keeper/provider/mysql_aes_new_spec.rb +50 -0
- data/spec/crypt_keeper/provider/postgres_pgp_public_key_spec.rb +67 -0
- data/spec/crypt_keeper/provider/postgres_pgp_spec.rb +87 -0
- data/spec/spec_helper.rb +0 -1
- data/spec/support/active_record.rb +36 -26
- data/spec/support/encryptors.rb +9 -3
- data/spec/support/logging.rb +89 -0
- metadata +84 -92
- data/bin/crypt_keeper +0 -99
- data/gemfiles/activerecord_3_1.gemfile +0 -8
- data/gemfiles/activerecord_3_1.gemfile.lock +0 -109
- data/gemfiles/activerecord_3_2.gemfile +0 -8
- data/gemfiles/activerecord_3_2.gemfile.lock +0 -109
- data/gemfiles/activerecord_4_0.gemfile +0 -8
- data/gemfiles/activerecord_4_0.gemfile.lock +0 -117
- data/gemfiles/activerecord_4_1.gemfile +0 -8
- data/gemfiles/activerecord_4_1.gemfile.lock +0 -117
- data/lib/crypt_keeper/provider/aes.rb +0 -66
- data/lib/crypt_keeper/provider/mysql_aes.rb +0 -47
- data/spec/log_subscriber/postgres_pgp_spec.rb +0 -98
- data/spec/model_spec.rb +0 -95
- data/spec/provider/aes_new_spec.rb +0 -45
- data/spec/provider/aes_spec.rb +0 -67
- data/spec/provider/mysql_aes_new_spec.rb +0 -52
- data/spec/provider/mysql_aes_spec.rb +0 -35
- data/spec/provider/postgres_pgp_public_key_spec.rb +0 -70
- data/spec/provider/postgres_pgp_spec.rb +0 -68
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78d7a7f1ea679f9a1542a65b537a446065c9cd27
|
|
4
|
+
data.tar.gz: 518d78e7d4a80e07faa80b9eac9c3b268e2db2c8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 30b9ccbea99c9ac89ee4bf4c9df73fc0b95e2ae3f65aca3e9936a95bd406d271e672e73cefa2c717a2f0c057c638ca876a238c7058da2c3ad6e3f2d2d12720cb
|
|
7
|
+
data.tar.gz: 07f33f2caca79c5be2c45f2365601e8939b5c64200fba53c00de3e70a9d6b221a68efe47aad00fc7b42003bce2c964ce68701656966149afe4b65dfc45a4e0a7
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
branches: master
|
|
2
1
|
language: ruby
|
|
2
|
+
|
|
3
|
+
rvm:
|
|
4
|
+
- 2.1.10
|
|
5
|
+
- 2.2.5
|
|
6
|
+
- 2.3.1
|
|
7
|
+
|
|
3
8
|
gemfile:
|
|
4
|
-
- gemfiles/
|
|
5
|
-
- gemfiles/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
- gemfiles/activerecord_4_2.gemfile
|
|
10
|
+
- gemfiles/activerecord_5_1.gemfile
|
|
11
|
+
|
|
12
|
+
matrix:
|
|
13
|
+
exclude:
|
|
14
|
+
- gemfile: gemfiles/activerecord_5_1.gemfile
|
|
15
|
+
rvm: 2.1.10
|
|
16
|
+
|
|
17
|
+
addons:
|
|
18
|
+
postgresql: 9.3
|
|
19
|
+
|
|
20
|
+
sudo: false
|
|
21
|
+
|
|
10
22
|
before_script:
|
|
11
23
|
- cp spec/default.database.yml spec/database.yml
|
|
12
24
|
- psql -c 'CREATE DATABASE crypt_keeper_providers;' -U postgres
|
|
13
25
|
- psql crypt_keeper_providers -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' -U postgres
|
|
14
26
|
- mysql -e 'CREATE DATABASE crypt_keeper_providers'
|
|
27
|
+
|
|
28
|
+
branches: master
|
|
29
|
+
|
|
15
30
|
notifications:
|
|
16
31
|
email:
|
|
17
32
|
recipients:
|
|
18
33
|
jmazzi@gmail.com
|
|
19
34
|
on_success: never
|
|
20
35
|
on_failure: always
|
|
21
|
-
rvm:
|
|
22
|
-
- 1.9.3
|
|
23
|
-
- 2.0.0
|
|
24
|
-
- 2.1.1
|
|
25
|
-
addons:
|
|
26
|
-
postgresql: 9.3
|
data/Appraisals
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
appraise "
|
|
2
|
-
gem
|
|
3
|
-
gem
|
|
1
|
+
appraise "activerecord_4_2" do
|
|
2
|
+
gem "activerecord", "~> 4.2.0"
|
|
3
|
+
gem "activesupport", "~> 4.2.0"
|
|
4
4
|
end
|
|
5
5
|
|
|
6
|
-
appraise "
|
|
7
|
-
gem
|
|
8
|
-
gem
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
appraise "activerecord_4_0" do
|
|
12
|
-
gem 'activerecord', '~> 4.0.0'
|
|
13
|
-
gem 'activesupport', '~> 4.0.0'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
appraise "activerecord_4_1" do
|
|
17
|
-
gem 'activerecord', '~> 4.1.0'
|
|
18
|
-
gem 'activesupport', '~> 4.1.0'
|
|
6
|
+
appraise "activerecord_5_0" do
|
|
7
|
+
gem "activerecord", "~> 5.0.0"
|
|
8
|
+
gem "activesupport", "~> 5.0.0"
|
|
19
9
|
end
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[](http://travis-ci.org/jmazzi/crypt_keeper)
|
|
1
|
+
[](http://travis-ci.org/jmazzi/crypt_keeper) [](http://badge.fury.io/rb/crypt_keeper)
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
@@ -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/
|
|
18
|
+
You can see an AES example [here](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes_new.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 => :
|
|
30
|
+
crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt'
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
model = MyModel.new(field: 'sometext')
|
|
@@ -47,28 +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
|
-
##
|
|
50
|
+
## Encodings
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
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
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
```ruby
|
|
55
|
+
class MyModel < ActiveRecord::Base
|
|
56
|
+
crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt', :encoding => 'UTF-8'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
model = MyModel.new(field: 'Tromsø')
|
|
60
|
+
model.save! #=> Your data is now encrypted
|
|
61
|
+
model.field #=> 'Tromsø'
|
|
62
|
+
model.field.encoding #=> #<Encoding:UTF-8>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Adding encryption to an existing table
|
|
57
66
|
|
|
58
|
-
|
|
67
|
+
If you are working with an existing table you would like to encrypt, you must use the `MyExistingModel.encrypt_table!` class method.
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class MyExistingModel < ActiveRecord::Base
|
|
71
|
+
crypt_keeper :field, :other_field, :encryptor => :aes_new, :key => 'super_good_password', salt: 'salt'
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
MyExistingModel.encrypt_table!
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Running `encrypt_table!` will encrypt all rows in the database using the encryption method specificed by the `crypt_keeper` line in your model.
|
|
78
|
+
|
|
79
|
+
## Supported Available Encryptors
|
|
80
|
+
|
|
81
|
+
There are four supported encryptors: `aes_new`, `mysql_aes_new`, `postgres_pgp`, `postgres_pgp_public_key`.
|
|
82
|
+
|
|
83
|
+
* [AES New](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes_new.rb)
|
|
59
84
|
* Encryption is peformed using AES-256 via OpenSSL.
|
|
85
|
+
* Passphrases are derived using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
|
|
60
86
|
|
|
61
|
-
* [MySQL AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes_new.rb)
|
|
87
|
+
* [MySQL AES New](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes_new.rb)
|
|
62
88
|
* Encryption is peformed MySQL's native AES functions.
|
|
63
89
|
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/mysql_aes.rb)
|
|
64
90
|
filtered for you to protect sensitive data from being logged.
|
|
65
91
|
* Passphrases are derived using [PBKDF2](http://en.wikipedia.org/wiki/PBKDF2)
|
|
66
92
|
|
|
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.
|
|
71
|
-
|
|
72
93
|
* [PostgreSQL PGP](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/postgres_pgp.rb).
|
|
73
94
|
* Encryption is performed using PostgresSQL's native [PGP functions](http://www.postgresql.org/docs/9.1/static/pgcrypto.html).
|
|
74
95
|
* It requires the `pgcrypto` PostgresSQL extension:
|
|
@@ -110,12 +131,13 @@ Model.where(something: 'blah').search_by_plaintext(:field, 'searchstring')
|
|
|
110
131
|
## Creating your own encryptor
|
|
111
132
|
|
|
112
133
|
Creating your own encryptor is easy. All you have to do is create a class
|
|
113
|
-
under the `CryptKeeper::Provider` namespace,
|
|
134
|
+
under the `CryptKeeper::Provider` namespace, and inherit from the `Base` encryptor,
|
|
135
|
+
like this:
|
|
114
136
|
|
|
115
137
|
```ruby
|
|
116
138
|
module CryptKeeper
|
|
117
139
|
module Provider
|
|
118
|
-
class MyEncryptor
|
|
140
|
+
class MyEncryptor < Base
|
|
119
141
|
def initialize(options = {})
|
|
120
142
|
end
|
|
121
143
|
|
|
@@ -141,11 +163,11 @@ end
|
|
|
141
163
|
|
|
142
164
|
## Requirements
|
|
143
165
|
|
|
144
|
-
CryptKeeper has been tested against ActiveRecord
|
|
145
|
-
1.
|
|
166
|
+
CryptKeeper has been tested against ActiveRecord 4.2 and 5.0 using Ruby
|
|
167
|
+
2.1.10, 2.2.5 and 2.3.1.
|
|
146
168
|
|
|
147
|
-
ActiveRecord 4.
|
|
148
|
-
ActiveRecord
|
|
169
|
+
ActiveRecord 4.2 is supported starting with v0.19.0.
|
|
170
|
+
ActiveRecord 5.0 is supported starting with v0.23.0.
|
|
149
171
|
|
|
150
172
|
## Installation
|
|
151
173
|
|
data/Rakefile
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env rake
|
|
2
2
|
require "bundler/gem_tasks"
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require "rspec/core/rake_task"
|
|
4
|
+
require "appraisal"
|
|
5
5
|
|
|
6
6
|
RSpec::Core::RakeTask.new :spec
|
|
7
7
|
Bundler::GemHelper.install_tasks
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
|
|
10
|
+
task default: :appraisal
|
|
11
|
+
else
|
|
12
|
+
task default: :spec
|
|
13
|
+
end
|
data/crypt_keeper.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ require File.expand_path('../lib/crypt_keeper/version', __FILE__)
|
|
|
4
4
|
Gem::Specification.new do |gem|
|
|
5
5
|
gem.authors = ["Justin Mazzi"]
|
|
6
6
|
gem.email = ["jmazzi@gmail.com"]
|
|
7
|
-
gem.description = %q{Transparent
|
|
7
|
+
gem.description = %q{Transparent ActiveRecord encryption}
|
|
8
8
|
gem.summary = gem.description
|
|
9
9
|
gem.homepage = "http://jmazzi.github.com/crypt_keeper/"
|
|
10
10
|
gem.license = 'MIT'
|
|
@@ -16,18 +16,18 @@ 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', '>=
|
|
20
|
-
gem.add_runtime_dependency 'activesupport', '>=
|
|
19
|
+
gem.add_runtime_dependency 'activerecord', '>= 4.2', '< 5.2'
|
|
20
|
+
gem.add_runtime_dependency 'activesupport', '>= 4.2', '< 5.2'
|
|
21
21
|
gem.add_runtime_dependency 'aes', '~> 0.5.0'
|
|
22
22
|
gem.add_runtime_dependency 'armor', '~> 0.0.2'
|
|
23
23
|
|
|
24
|
-
gem.add_development_dependency 'rspec', '~>
|
|
25
|
-
gem.add_development_dependency 'guard', '~>
|
|
26
|
-
gem.add_development_dependency 'guard-rspec', '~> 2.
|
|
27
|
-
gem.add_development_dependency 'rake', '~> 10.
|
|
24
|
+
gem.add_development_dependency 'rspec', '~> 3.5.0'
|
|
25
|
+
gem.add_development_dependency 'guard', '~> 2.6.1'
|
|
26
|
+
gem.add_development_dependency 'guard-rspec', '~> 4.2.9'
|
|
27
|
+
gem.add_development_dependency 'rake', '~> 10.3.1'
|
|
28
28
|
gem.add_development_dependency 'rb-fsevent', '~> 0.9.1'
|
|
29
29
|
gem.add_development_dependency 'coveralls'
|
|
30
|
-
gem.add_development_dependency 'appraisal', '~>
|
|
30
|
+
gem.add_development_dependency 'appraisal', '~> 2.1.0'
|
|
31
31
|
|
|
32
32
|
if RUBY_PLATFORM == 'java'
|
|
33
33
|
gem.add_development_dependency 'jruby-openssl', '~> 0.7.7'
|
|
@@ -36,7 +36,7 @@ Gem::Specification.new do |gem|
|
|
|
36
36
|
gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
|
|
37
37
|
else
|
|
38
38
|
gem.add_development_dependency 'sqlite3'
|
|
39
|
-
gem.add_development_dependency 'pg', '~> 0.
|
|
39
|
+
gem.add_development_dependency 'pg', '~> 0.18.0'
|
|
40
40
|
gem.add_development_dependency 'mysql2', '~> 0.3.11'
|
|
41
41
|
end
|
|
42
42
|
end
|
data/lib/crypt_keeper/helper.rb
CHANGED
|
@@ -3,10 +3,38 @@ module CryptKeeper
|
|
|
3
3
|
module SQL
|
|
4
4
|
private
|
|
5
5
|
|
|
6
|
-
# Private: Sanitize an sql query and then execute it
|
|
7
|
-
|
|
6
|
+
# Private: Sanitize an sql query and then execute it.
|
|
7
|
+
#
|
|
8
|
+
# query - the sql query
|
|
9
|
+
# new_transaction - if the query should run inside a new transaction
|
|
10
|
+
#
|
|
11
|
+
# Returns the ActiveRecord response.
|
|
12
|
+
def escape_and_execute_sql(query, new_transaction: false)
|
|
8
13
|
query = ::ActiveRecord::Base.send :sanitize_sql_array, query
|
|
9
|
-
|
|
14
|
+
|
|
15
|
+
if CryptKeeper.silence_logs?
|
|
16
|
+
::ActiveRecord::Base.logger.silence do
|
|
17
|
+
execute_sql(query, new_transaction: new_transaction)
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
execute_sql(query, new_transaction: new_transaction)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Private: Executes the query.
|
|
25
|
+
#
|
|
26
|
+
# query - the sql query
|
|
27
|
+
# new_transaction - if the query should run inside a new transaction
|
|
28
|
+
#
|
|
29
|
+
# Returns an Array.
|
|
30
|
+
def execute_sql(query, new_transaction: false)
|
|
31
|
+
if new_transaction
|
|
32
|
+
::ActiveRecord::Base.transaction(requires_new: true) do
|
|
33
|
+
::ActiveRecord::Base.connection.execute(query).first
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
::ActiveRecord::Base.connection.execute(query).first
|
|
37
|
+
end
|
|
10
38
|
end
|
|
11
39
|
end
|
|
12
40
|
|
|
@@ -17,23 +45,5 @@ module CryptKeeper
|
|
|
17
45
|
::Armor.digest(key, salt)
|
|
18
46
|
end
|
|
19
47
|
end
|
|
20
|
-
|
|
21
|
-
module Serializer
|
|
22
|
-
def dump(value)
|
|
23
|
-
if value.blank?
|
|
24
|
-
value
|
|
25
|
-
else
|
|
26
|
-
encrypt(value)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def load(value)
|
|
31
|
-
if value.blank?
|
|
32
|
-
value
|
|
33
|
-
else
|
|
34
|
-
decrypt(value)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
48
|
end
|
|
39
49
|
end
|
|
@@ -4,26 +4,26 @@ require 'active_support/lazy_load_hooks'
|
|
|
4
4
|
module CryptKeeper
|
|
5
5
|
module LogSubscriber
|
|
6
6
|
module MysqlAes
|
|
7
|
-
extend ActiveSupport::Concern
|
|
8
|
-
|
|
9
|
-
included do
|
|
10
|
-
alias_method_chain :sql, :mysql_aes
|
|
11
|
-
end
|
|
12
|
-
|
|
13
7
|
# Public: Prevents sensitive data from being logged
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
#
|
|
9
|
+
# event - An ActiveSupport::Notifications::Event
|
|
10
|
+
#
|
|
11
|
+
# Returns a boolean.
|
|
12
|
+
def sql(event)
|
|
13
|
+
filter = /(aes_(encrypt|decrypt))\(.*\)/i
|
|
14
|
+
payload = event.payload[:sql]
|
|
15
|
+
.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
|
|
16
16
|
|
|
17
|
-
event.payload[:sql] =
|
|
17
|
+
event.payload[:sql] = payload.gsub(filter) do |_|
|
|
18
18
|
"#{$1}([FILTERED])"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
super(event)
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
ActiveSupport.on_load :crypt_keeper_mysql_aes_log do
|
|
28
|
-
ActiveRecord::LogSubscriber.
|
|
28
|
+
ActiveRecord::LogSubscriber.prepend CryptKeeper::LogSubscriber::MysqlAes
|
|
29
29
|
end
|
|
@@ -4,26 +4,45 @@ require 'active_support/lazy_load_hooks'
|
|
|
4
4
|
module CryptKeeper
|
|
5
5
|
module LogSubscriber
|
|
6
6
|
module PostgresPgp
|
|
7
|
-
|
|
7
|
+
FILTER = /(\(*)(?<operation>pgp_sym_encrypt|pgp_sym_decrypt|pgp_pub_encrypt|pgp_pub_decrypt|pgp_key_id)(\(+.*\)+)/im
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# Public: Prevents sensitive data from being logged
|
|
10
|
+
#
|
|
11
|
+
# event - An ActiveSupport::Notifications::Event
|
|
12
|
+
#
|
|
13
|
+
# Returns a boolean.
|
|
14
|
+
def sql(event)
|
|
15
|
+
payload = crypt_keeper_payload_parse(event.payload[:sql])
|
|
16
|
+
event.payload[:sql] = crypt_keeper_filter_postgres_log(payload)
|
|
17
|
+
super(event)
|
|
11
18
|
end
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
def sql_with_postgres_pgp(event)
|
|
15
|
-
filter = /(\(*)pgp_(sym|pub)_(?<operation>decrypt|encrypt)(\(+.*\)+)/im
|
|
20
|
+
private
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
# Private: Parses the payload to UTF.
|
|
23
|
+
#
|
|
24
|
+
# payload - the payload string
|
|
25
|
+
#
|
|
26
|
+
# Returns a string.
|
|
27
|
+
def crypt_keeper_payload_parse(payload)
|
|
28
|
+
payload.encode('UTF-8', 'binary',
|
|
29
|
+
invalid: :replace, undef: :replace, replace: '')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Private: Filters the payload.
|
|
33
|
+
#
|
|
34
|
+
# payload - the payload string
|
|
35
|
+
#
|
|
36
|
+
# Returns a string.
|
|
37
|
+
def crypt_keeper_filter_postgres_log(payload)
|
|
38
|
+
payload.gsub(FILTER) do |_|
|
|
18
39
|
"#{$~[:operation]}([FILTERED])"
|
|
19
40
|
end
|
|
20
|
-
|
|
21
|
-
sql_without_postgres_pgp(event)
|
|
22
41
|
end
|
|
23
42
|
end
|
|
24
43
|
end
|
|
25
44
|
end
|
|
26
45
|
|
|
27
46
|
ActiveSupport.on_load :crypt_keeper_postgres_pgp_log do
|
|
28
|
-
ActiveRecord::LogSubscriber.
|
|
47
|
+
ActiveRecord::LogSubscriber.prepend CryptKeeper::LogSubscriber::PostgresPgp
|
|
29
48
|
end
|
data/lib/crypt_keeper/model.rb
CHANGED
|
@@ -10,8 +10,9 @@ module CryptKeeper
|
|
|
10
10
|
def ensure_valid_field!(field)
|
|
11
11
|
if self.class.columns_hash["#{field}"].nil?
|
|
12
12
|
raise ArgumentError, "Column :#{field} does not exist"
|
|
13
|
-
elsif self.class.columns_hash["#{field}"].type
|
|
14
|
-
raise ArgumentError, "Column :#{field} must be of type 'text'
|
|
13
|
+
elsif !%i(text binary).include?(self.class.columns_hash["#{field}"].type)
|
|
14
|
+
raise ArgumentError, "Column :#{field} must be of type 'text' or 'binary' \
|
|
15
|
+
to be used for encryption"
|
|
15
16
|
end
|
|
16
17
|
end
|
|
17
18
|
|
|
@@ -24,6 +25,15 @@ module CryptKeeper
|
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
28
|
+
# Private: Force string encodings if the option is set
|
|
29
|
+
def force_encodings_on_fields
|
|
30
|
+
crypt_keeper_fields.each do |field|
|
|
31
|
+
if attributes.has_key?(field.to_s) && send(field).respond_to?(:force_encoding)
|
|
32
|
+
send(field).force_encoding(crypt_keeper_encoding)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
27
37
|
module ClassMethods
|
|
28
38
|
# Public: Setup fields for encryption
|
|
29
39
|
#
|
|
@@ -42,27 +52,65 @@ module CryptKeeper
|
|
|
42
52
|
class_attribute :crypt_keeper_fields
|
|
43
53
|
class_attribute :crypt_keeper_encryptor
|
|
44
54
|
class_attribute :crypt_keeper_options
|
|
55
|
+
class_attribute :crypt_keeper_encoding
|
|
45
56
|
|
|
46
57
|
self.crypt_keeper_options = args.extract_options!
|
|
47
58
|
self.crypt_keeper_encryptor = crypt_keeper_options.delete(:encryptor)
|
|
59
|
+
self.crypt_keeper_encoding = crypt_keeper_options.delete(:encoding)
|
|
48
60
|
self.crypt_keeper_fields = args
|
|
49
61
|
|
|
50
62
|
ensure_valid_encryptor!
|
|
51
63
|
|
|
52
64
|
before_save :enforce_column_types_callback
|
|
53
65
|
|
|
66
|
+
if self.crypt_keeper_encoding
|
|
67
|
+
after_find :force_encodings_on_fields
|
|
68
|
+
before_save :force_encodings_on_fields
|
|
69
|
+
end
|
|
70
|
+
|
|
54
71
|
crypt_keeper_fields.each do |field|
|
|
55
|
-
serialize field,
|
|
56
|
-
extend(::CryptKeeper::Helper::Serializer)
|
|
72
|
+
serialize field, encryptor
|
|
57
73
|
end
|
|
58
74
|
end
|
|
59
75
|
|
|
60
76
|
def search_by_plaintext(field, criteria)
|
|
61
77
|
if crypt_keeper_fields.include?(field.to_sym)
|
|
62
|
-
encryptor
|
|
63
|
-
encryptor.search(scoping_strategy, field.to_s, criteria)
|
|
78
|
+
encryptor.search(all, field.to_s, criteria)
|
|
64
79
|
else
|
|
65
|
-
raise "#{field} is not a crypt_keeper field"
|
|
80
|
+
raise ArgumentError, "#{field} is not a crypt_keeper field"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Public: Encrypt a table for the first time.
|
|
85
|
+
def encrypt_table!
|
|
86
|
+
tmp_table = Class.new(ActiveRecord::Base).tap do |c|
|
|
87
|
+
c.table_name = self.table_name
|
|
88
|
+
c.inheritance_column = :type_disabled
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
transaction do
|
|
92
|
+
tmp_table.find_each do |r|
|
|
93
|
+
crypt_keeper_fields.each do |field|
|
|
94
|
+
r.send("#{field}=", encryptor.encrypt(r[field])) if r[field].present?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
r.save!
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Public: Decrypt a table (reverse of encrypt_table!)
|
|
103
|
+
def decrypt_table!
|
|
104
|
+
tmp_table = Class.new(ActiveRecord::Base).tap { |c| c.table_name = self.table_name }
|
|
105
|
+
|
|
106
|
+
transaction do
|
|
107
|
+
tmp_table.find_each do |r|
|
|
108
|
+
crypt_keeper_fields.each do |field|
|
|
109
|
+
r.send("#{field}=", encryptor.decrypt(r[field])) if r[field].present?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
r.save!
|
|
113
|
+
end
|
|
66
114
|
end
|
|
67
115
|
end
|
|
68
116
|
|
|
@@ -73,18 +121,17 @@ module CryptKeeper
|
|
|
73
121
|
@encryptor_klass ||= "CryptKeeper::Provider::#{crypt_keeper_encryptor.to_s.camelize}".constantize
|
|
74
122
|
end
|
|
75
123
|
|
|
76
|
-
# Private:
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
raise "You must specify a valid encryptor `crypt_keeper :encryptor => :aes`"
|
|
80
|
-
end
|
|
124
|
+
# Private: The encryptor instance.
|
|
125
|
+
def encryptor
|
|
126
|
+
@encryptor ||= encryptor_klass.new(crypt_keeper_options)
|
|
81
127
|
end
|
|
82
128
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
129
|
+
# Private: Ensure we have a valid encryptor.
|
|
130
|
+
def ensure_valid_encryptor!
|
|
131
|
+
unless defined?(encryptor_klass) && encryptor_klass.respond_to?(:new) &&
|
|
132
|
+
%i(load dump).all? { |m| encryptor.respond_to?(m) }
|
|
133
|
+
raise ArgumentError, "You must specify a valid encryptor that implements " \
|
|
134
|
+
"the `load` and `dump` methods (you can inherit from CryptKeeper::Provider::Base). Example: `crypt_keeper :encryptor => :aes`"
|
|
88
135
|
end
|
|
89
136
|
end
|
|
90
137
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module CryptKeeper
|
|
2
|
+
module Provider
|
|
3
|
+
class Base
|
|
4
|
+
def dump(value)
|
|
5
|
+
if value.blank? || CryptKeeper.stub_encryption?
|
|
6
|
+
value
|
|
7
|
+
else
|
|
8
|
+
encrypt(value.to_s)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def load(value)
|
|
13
|
+
if value.blank? || CryptKeeper.stub_encryption?
|
|
14
|
+
value
|
|
15
|
+
else
|
|
16
|
+
decrypt(value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -2,7 +2,7 @@ require 'crypt_keeper/log_subscriber/mysql_aes'
|
|
|
2
2
|
|
|
3
3
|
module CryptKeeper
|
|
4
4
|
module Provider
|
|
5
|
-
class MysqlAesNew
|
|
5
|
+
class MysqlAesNew < Base
|
|
6
6
|
include CryptKeeper::Helper::SQL
|
|
7
7
|
include CryptKeeper::Helper::DigestPassphrase
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ module CryptKeeper
|
|
|
36
36
|
#
|
|
37
37
|
# Returns an Enumerable
|
|
38
38
|
def search(records, field, criteria)
|
|
39
|
-
records.where(field
|
|
39
|
+
records.where("#{field} = ?", encrypt(criteria))
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'crypt_keeper/log_subscriber/postgres_pgp'
|
|
2
|
+
|
|
3
|
+
module CryptKeeper
|
|
4
|
+
module Provider
|
|
5
|
+
class PostgresBase < Base
|
|
6
|
+
include CryptKeeper::Helper::SQL
|
|
7
|
+
include CryptKeeper::LogSubscriber::PostgresPgp
|
|
8
|
+
|
|
9
|
+
INVALID_DATA_ERROR = "Wrong key or corrupt data".freeze
|
|
10
|
+
|
|
11
|
+
# Public: Checks if value is already encrypted.
|
|
12
|
+
#
|
|
13
|
+
# Returns boolean
|
|
14
|
+
def encrypted?(value)
|
|
15
|
+
begin
|
|
16
|
+
escape_and_execute_sql(["SELECT pgp_key_id(?)", value.to_s],
|
|
17
|
+
new_transaction: true)['pgp_key_id'].present?
|
|
18
|
+
rescue ActiveRecord::StatementInvalid => e
|
|
19
|
+
if e.message.include?(INVALID_DATA_ERROR)
|
|
20
|
+
false
|
|
21
|
+
else
|
|
22
|
+
raise
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|