crypt_keeper 0.4.2 → 0.5.0
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.
- data/.gitignore +1 -0
- data/.travis.yml +11 -0
- data/README.md +17 -13
- data/crypt_keeper.gemspec +4 -1
- data/gemfiles/activerecord_3_0.gemfile.lock +4 -2
- data/gemfiles/activerecord_3_1.gemfile.lock +4 -2
- data/gemfiles/activerecord_3_2.gemfile.lock +4 -2
- data/lib/crypt_keeper.rb +7 -2
- data/lib/crypt_keeper/log_subscriber/mysql_aes.rb +29 -0
- data/lib/crypt_keeper/log_subscriber/postgres_pgp.rb +29 -0
- data/lib/crypt_keeper/model.rb +7 -4
- data/lib/crypt_keeper/provider/aes.rb +53 -0
- data/lib/crypt_keeper/provider/mysql_aes.rb +40 -0
- data/lib/crypt_keeper/provider/postgres_pgp.rb +40 -0
- data/lib/crypt_keeper/version.rb +1 -1
- data/spec/default.database.yml +16 -0
- data/spec/log_subscriber/mysql_aes.rb +25 -0
- data/spec/log_subscriber/postgres_pgp.rb +25 -0
- data/spec/model_spec.rb +18 -1
- data/spec/provider/aes_spec.rb +45 -0
- data/spec/provider/mysql_aes_spec.rb +41 -0
- data/spec/provider/postgres_pgp_spec.rb +35 -0
- data/spec/support/active_record.rb +44 -10
- data/spec/support/encryptors.rb +17 -14
- metadata +53 -20
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -4,6 +4,11 @@ gemfile:
|
|
4
4
|
- gemfiles/activerecord_3_0.gemfile
|
5
5
|
- gemfiles/activerecord_3_1.gemfile
|
6
6
|
- gemfiles/activerecord_3_2.gemfile
|
7
|
+
before_script:
|
8
|
+
- cp spec/default.database.yml spec/database.yml
|
9
|
+
- psql -c 'CREATE DATABASE crypt_keeper_providers;' -U postgres
|
10
|
+
- psql crypt_keeper_providers -c 'CREATE EXTENSION IF NOT EXISTS pgcrypto;' -U postgres
|
11
|
+
- mysql -e 'CREATE DATABASE crypt_keeper_providers'
|
7
12
|
notifications:
|
8
13
|
email:
|
9
14
|
recipients:
|
@@ -13,3 +18,9 @@ notifications:
|
|
13
18
|
rvm:
|
14
19
|
- 1.9.2
|
15
20
|
- 1.9.3
|
21
|
+
- jruby
|
22
|
+
matrix:
|
23
|
+
allow_failures:
|
24
|
+
- rvm: jruby
|
25
|
+
env:
|
26
|
+
- JRUBY_OPTS=--1.9
|
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](
|
18
|
+
You can see an AES example [here](/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes.rb).
|
19
19
|
|
20
20
|
## Why?
|
21
21
|
|
@@ -50,18 +50,20 @@ update the content without going through the current encryptor.
|
|
50
50
|
## Creating your own encryptor
|
51
51
|
|
52
52
|
Creating your own encryptor is easy. All you have to do is create a class
|
53
|
-
under the `
|
53
|
+
under the `CryptKeeper::Provider` namespace, like this:
|
54
54
|
|
55
55
|
```ruby
|
56
|
-
module
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
module CryptKeeper
|
57
|
+
module Provider
|
58
|
+
class MyEncryptor
|
59
|
+
def initialize(options = {})
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
def encrypt(value)
|
63
|
+
end
|
63
64
|
|
64
|
-
|
65
|
+
def decrypt(value)
|
66
|
+
end
|
65
67
|
end
|
66
68
|
end
|
67
69
|
end
|
@@ -81,19 +83,21 @@ end
|
|
81
83
|
|
82
84
|
There are two included encryptors.
|
83
85
|
|
84
|
-
* [AES](https://github.com/jmazzi/
|
86
|
+
* [AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/aes.rb)
|
85
87
|
* Encryption is peformed using AES-256 via OpenSSL.
|
86
88
|
|
87
89
|
|
88
|
-
* [MySQL AES](https://github.com/jmazzi/
|
90
|
+
* [MySQL AES](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/mysql_aes.rb)
|
89
91
|
* Encryption is peformed MySQL's native AES functions.
|
92
|
+
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/mysql_aes.rb)
|
93
|
+
filtered for you to protect senitive data from being logged.
|
90
94
|
|
91
95
|
|
92
|
-
* [PostgreSQL PGP](https://github.com/jmazzi/
|
96
|
+
* [PostgreSQL PGP](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/provider/postgres_pgp.rb).
|
93
97
|
* Encryption is performed using PostgresSQL's native [PGP functions](http://www.postgresql.org/docs/9.1/static/pgcrypto.html).
|
94
98
|
* It requires the `pgcrypto` PostgresSQL extension:
|
95
99
|
`CREATE EXTENSION IF NOT EXISTS pgcrypto`
|
96
|
-
* ActiveRecord logs are [automatically](https://github.com/jmazzi/
|
100
|
+
* ActiveRecord logs are [automatically](https://github.com/jmazzi/crypt_keeper/blob/master/lib/crypt_keeper/log_subscriber/postgres_pgp.rb)
|
97
101
|
filtered for you to protect senitive data from being logged.
|
98
102
|
|
99
103
|
## Requirements
|
data/crypt_keeper.gemspec
CHANGED
@@ -17,7 +17,6 @@ Gem::Specification.new do |gem|
|
|
17
17
|
|
18
18
|
gem.add_runtime_dependency 'activerecord', '>= 3.0'
|
19
19
|
gem.add_runtime_dependency 'activesupport', '>= 3.0'
|
20
|
-
gem.add_runtime_dependency 'crypt_keeper_providers', '0.5.2'
|
21
20
|
gem.add_runtime_dependency 'appraisal', '~> 0.4.1'
|
22
21
|
|
23
22
|
gem.add_development_dependency 'rspec', '~> 2.11.0'
|
@@ -28,7 +27,11 @@ Gem::Specification.new do |gem|
|
|
28
27
|
if RUBY_PLATFORM == 'java'
|
29
28
|
gem.add_development_dependency 'jruby-openssl', '~> 0.7.7'
|
30
29
|
gem.add_development_dependency 'activerecord-jdbcsqlite3-adapter'
|
30
|
+
gem.add_development_dependency 'activerecord-jdbcpostgresql-adapter'
|
31
|
+
gem.add_development_dependency 'activerecord-jdbcmysql-adapter'
|
31
32
|
else
|
32
33
|
gem.add_development_dependency 'sqlite3'
|
34
|
+
gem.add_development_dependency 'pg', '~> 0.14.0'
|
35
|
+
gem.add_development_dependency 'mysql2', '~> 0.3.11'
|
33
36
|
end
|
34
37
|
end
|
@@ -5,7 +5,6 @@ PATH
|
|
5
5
|
activerecord (>= 3.0)
|
6
6
|
activesupport (>= 3.0)
|
7
7
|
appraisal (~> 0.4.1)
|
8
|
-
crypt_keeper_providers (= 0.5.2)
|
9
8
|
|
10
9
|
GEM
|
11
10
|
remote: https://rubygems.org/
|
@@ -26,7 +25,6 @@ GEM
|
|
26
25
|
rake
|
27
26
|
arel (3.0.2)
|
28
27
|
builder (3.0.0)
|
29
|
-
crypt_keeper_providers (0.5.2)
|
30
28
|
diff-lcs (1.1.3)
|
31
29
|
ffi (1.1.5)
|
32
30
|
guard (1.3.2)
|
@@ -40,6 +38,8 @@ GEM
|
|
40
38
|
rb-fsevent (~> 0.9.1)
|
41
39
|
rb-inotify (~> 0.8.8)
|
42
40
|
multi_json (1.3.6)
|
41
|
+
mysql2 (0.3.11)
|
42
|
+
pg (0.14.0)
|
43
43
|
rake (0.9.2.2)
|
44
44
|
rb-fchange (0.0.5)
|
45
45
|
ffi
|
@@ -67,6 +67,8 @@ DEPENDENCIES
|
|
67
67
|
crypt_keeper!
|
68
68
|
guard (~> 1.3.0)
|
69
69
|
guard-rspec (~> 1.2.0)
|
70
|
+
mysql2 (~> 0.3.11)
|
71
|
+
pg (~> 0.14.0)
|
70
72
|
rake (~> 0.9.2.2)
|
71
73
|
rspec (~> 2.11.0)
|
72
74
|
sqlite3
|
@@ -5,7 +5,6 @@ PATH
|
|
5
5
|
activerecord (>= 3.0)
|
6
6
|
activesupport (>= 3.0)
|
7
7
|
appraisal (~> 0.4.1)
|
8
|
-
crypt_keeper_providers (= 0.5.2)
|
9
8
|
|
10
9
|
GEM
|
11
10
|
remote: https://rubygems.org/
|
@@ -26,7 +25,6 @@ GEM
|
|
26
25
|
rake
|
27
26
|
arel (3.0.2)
|
28
27
|
builder (3.0.0)
|
29
|
-
crypt_keeper_providers (0.5.2)
|
30
28
|
diff-lcs (1.1.3)
|
31
29
|
ffi (1.1.5)
|
32
30
|
guard (1.3.2)
|
@@ -40,6 +38,8 @@ GEM
|
|
40
38
|
rb-fsevent (~> 0.9.1)
|
41
39
|
rb-inotify (~> 0.8.8)
|
42
40
|
multi_json (1.3.6)
|
41
|
+
mysql2 (0.3.11)
|
42
|
+
pg (0.14.0)
|
43
43
|
rake (0.9.2.2)
|
44
44
|
rb-fchange (0.0.5)
|
45
45
|
ffi
|
@@ -67,6 +67,8 @@ DEPENDENCIES
|
|
67
67
|
crypt_keeper!
|
68
68
|
guard (~> 1.3.0)
|
69
69
|
guard-rspec (~> 1.2.0)
|
70
|
+
mysql2 (~> 0.3.11)
|
71
|
+
pg (~> 0.14.0)
|
70
72
|
rake (~> 0.9.2.2)
|
71
73
|
rspec (~> 2.11.0)
|
72
74
|
sqlite3
|
@@ -5,7 +5,6 @@ PATH
|
|
5
5
|
activerecord (>= 3.0)
|
6
6
|
activesupport (>= 3.0)
|
7
7
|
appraisal (~> 0.4.1)
|
8
|
-
crypt_keeper_providers (= 0.5.2)
|
9
8
|
|
10
9
|
GEM
|
11
10
|
remote: https://rubygems.org/
|
@@ -26,7 +25,6 @@ GEM
|
|
26
25
|
rake
|
27
26
|
arel (3.0.2)
|
28
27
|
builder (3.0.0)
|
29
|
-
crypt_keeper_providers (0.5.2)
|
30
28
|
diff-lcs (1.1.3)
|
31
29
|
ffi (1.1.5)
|
32
30
|
guard (1.3.2)
|
@@ -40,6 +38,8 @@ GEM
|
|
40
38
|
rb-fsevent (~> 0.9.1)
|
41
39
|
rb-inotify (~> 0.8.8)
|
42
40
|
multi_json (1.3.6)
|
41
|
+
mysql2 (0.3.11)
|
42
|
+
pg (0.14.0)
|
43
43
|
rake (0.9.2.2)
|
44
44
|
rb-fchange (0.0.5)
|
45
45
|
ffi
|
@@ -67,6 +67,8 @@ DEPENDENCIES
|
|
67
67
|
crypt_keeper!
|
68
68
|
guard (~> 1.3.0)
|
69
69
|
guard-rspec (~> 1.2.0)
|
70
|
+
mysql2 (~> 0.3.11)
|
71
|
+
pg (~> 0.14.0)
|
70
72
|
rake (~> 0.9.2.2)
|
71
73
|
rspec (~> 2.11.0)
|
72
74
|
sqlite3
|
data/lib/crypt_keeper.rb
CHANGED
@@ -1,10 +1,15 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
1
3
|
require 'crypt_keeper/version'
|
2
4
|
require 'crypt_keeper/model'
|
3
|
-
|
5
|
+
|
6
|
+
require 'crypt_keeper/provider/aes'
|
7
|
+
require 'crypt_keeper/provider/mysql_aes'
|
8
|
+
require 'crypt_keeper/provider/postgres_pgp'
|
4
9
|
|
5
10
|
module CryptKeeper
|
6
11
|
end
|
7
12
|
|
8
|
-
ActiveSupport.on_load
|
13
|
+
ActiveSupport.on_load :active_record do
|
9
14
|
include CryptKeeper::Model
|
10
15
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/lazy_load_hooks'
|
3
|
+
|
4
|
+
module CryptKeeper
|
5
|
+
module LogSubscriber
|
6
|
+
module MysqlAes
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
alias_method_chain :sql, :mysql_aes
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Prevents sensitive data from being logged
|
14
|
+
def sql_with_mysql_aes(event)
|
15
|
+
filter = /(aes_(encrypt|decrypt))\(((.|\n)*?)\)/i
|
16
|
+
|
17
|
+
event.payload[:sql] = event.payload[:sql].gsub(filter) do |_|
|
18
|
+
"#{$1}([FILTERED])"
|
19
|
+
end
|
20
|
+
|
21
|
+
sql_without_mysql_aes(event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveSupport.on_load :active_record do
|
28
|
+
ActiveRecord::LogSubscriber.send :include, CryptKeeper::LogSubscriber::MysqlAes
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/lazy_load_hooks'
|
3
|
+
|
4
|
+
module CryptKeeper
|
5
|
+
module LogSubscriber
|
6
|
+
module PostgresPgp
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
alias_method_chain :sql, :postgres_pgp
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Prevents sensitive data from being logged
|
14
|
+
def sql_with_postgres_pgp(event)
|
15
|
+
filter = /(pgp_sym_(encrypt|decrypt))\(((.|\n)*?)\)/i
|
16
|
+
|
17
|
+
event.payload[:sql] = event.payload[:sql].gsub(filter) do |_|
|
18
|
+
"#{$1}([FILTERED])"
|
19
|
+
end
|
20
|
+
|
21
|
+
sql_without_postgres_pgp(event)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ActiveSupport.on_load :active_record do
|
28
|
+
ActiveRecord::LogSubscriber.send :include, CryptKeeper::LogSubscriber::PostgresPgp
|
29
|
+
end
|
data/lib/crypt_keeper/model.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'active_support/concern'
|
2
2
|
require 'active_support/core_ext/array/extract_options'
|
3
|
-
require 'crypt_keeper_providers'
|
4
3
|
|
5
4
|
module CryptKeeper
|
6
5
|
module Model
|
@@ -11,14 +10,18 @@ module CryptKeeper
|
|
11
10
|
# Private: Encrypt each crypt_keeper_fields
|
12
11
|
def encrypt_callback
|
13
12
|
crypt_keeper_fields.each do |field|
|
14
|
-
self[field]
|
13
|
+
if !self[field].nil?
|
14
|
+
self[field] = self.class.encrypt read_attribute(field)
|
15
|
+
end
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
19
|
# Private: Decrypt each crypt_keeper_fields
|
19
20
|
def decrypt_callback
|
20
21
|
crypt_keeper_fields.each do |field|
|
21
|
-
self[field]
|
22
|
+
if !self[field].nil?
|
23
|
+
self[field] = self.class.decrypt read_attribute(field)
|
24
|
+
end
|
22
25
|
end
|
23
26
|
end
|
24
27
|
|
@@ -68,7 +71,7 @@ module CryptKeeper
|
|
68
71
|
|
69
72
|
# Private: The encryptor class
|
70
73
|
def encryptor_klass
|
71
|
-
@encryptor_klass ||= "
|
74
|
+
@encryptor_klass ||= "CryptKeeper::Provider::#{crypt_keeper_encryptor.to_s.camelize}".constantize
|
72
75
|
end
|
73
76
|
|
74
77
|
# Private: Ensure that the encryptor responds to new
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'openssl'
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module CryptKeeper
|
6
|
+
module Provider
|
7
|
+
class Aes
|
8
|
+
SEPARATOR = ":crypt_keeper:"
|
9
|
+
|
10
|
+
# Public: The encryption key
|
11
|
+
attr_accessor :key
|
12
|
+
|
13
|
+
# Public: An instance of OpenSSL::Cipher::Cipher
|
14
|
+
attr_accessor :aes
|
15
|
+
|
16
|
+
# Public: Initializes the class
|
17
|
+
#
|
18
|
+
# options - A hash of options. :key is required
|
19
|
+
def initialize(options = {})
|
20
|
+
@aes = ::OpenSSL::Cipher::Cipher.new("AES-256-CBC")
|
21
|
+
@aes.padding = 1
|
22
|
+
|
23
|
+
key = options.fetch(:key) do
|
24
|
+
raise ArgumentError, "Missing :key"
|
25
|
+
end
|
26
|
+
|
27
|
+
@key = Digest::SHA256.digest(key)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Encrypt a string
|
31
|
+
#
|
32
|
+
# Returns a string
|
33
|
+
def encrypt(value)
|
34
|
+
aes.encrypt
|
35
|
+
aes.key = key
|
36
|
+
iv = rand.to_s
|
37
|
+
aes.iv = iv
|
38
|
+
Base64::encode64("#{iv}#{SEPARATOR}#{aes.update(value.to_s) + aes.final}")
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: Decrypt a string
|
42
|
+
#
|
43
|
+
# Returns a string
|
44
|
+
def decrypt(value)
|
45
|
+
iv, value = Base64::decode64(value.to_s).split(SEPARATOR)
|
46
|
+
aes.decrypt
|
47
|
+
aes.key = key
|
48
|
+
aes.iv = iv
|
49
|
+
aes.update(value) + aes.final
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'crypt_keeper/log_subscriber/mysql_aes'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
class MysqlAes
|
6
|
+
attr_accessor :key
|
7
|
+
|
8
|
+
# Public: Initializes the encryptor
|
9
|
+
#
|
10
|
+
# options - A hash, :key is required
|
11
|
+
def initialize(options = {})
|
12
|
+
@key = options.fetch(:key) do
|
13
|
+
raise ArgumentError, "Missing :key"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Encrypts a string
|
18
|
+
#
|
19
|
+
# Returns an encrypted string
|
20
|
+
def encrypt(value)
|
21
|
+
escape_and_execute_sql(["SELECT AES_ENCRYPT(?, ?)", value, key]).first
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Decrypts a string
|
25
|
+
#
|
26
|
+
# Returns a plaintext string
|
27
|
+
def decrypt(value)
|
28
|
+
escape_and_execute_sql(["SELECT AES_DECRYPT(?, ?)", value, key]).first
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Private: Sanitize an sql query and then execute it
|
34
|
+
def escape_and_execute_sql(query)
|
35
|
+
query = ::ActiveRecord::Base.send :sanitize_sql_array, query
|
36
|
+
::ActiveRecord::Base.connection.execute(query).first
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'crypt_keeper/log_subscriber/postgres_pgp'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
class PostgresPgp
|
6
|
+
attr_accessor :key
|
7
|
+
|
8
|
+
# Public: Initializes the encryptor
|
9
|
+
#
|
10
|
+
# options - A hash, :key is required
|
11
|
+
def initialize(options = {})
|
12
|
+
@key = options.fetch(:key) do
|
13
|
+
raise ArgumentError, "Missing :key"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Encrypts a string
|
18
|
+
#
|
19
|
+
# Returns an encrypted string
|
20
|
+
def encrypt(value)
|
21
|
+
escape_and_execute_sql(["SELECT pgp_sym_encrypt(?, ?)", value, key])['pgp_sym_encrypt']
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Decrypts a string
|
25
|
+
#
|
26
|
+
# Returns a plaintext string
|
27
|
+
def decrypt(value)
|
28
|
+
escape_and_execute_sql(["SELECT pgp_sym_decrypt(?, ?)", value, key])['pgp_sym_decrypt']
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Private: Sanitize an sql query and then execute it
|
34
|
+
def escape_and_execute_sql(query)
|
35
|
+
query = ::ActiveRecord::Base.send :sanitize_sql_array, query
|
36
|
+
::ActiveRecord::Base.connection.execute(query).first
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/crypt_keeper/version.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
postgres:
|
2
|
+
adapter: postgresql
|
3
|
+
encoding: utf8
|
4
|
+
reconnect: false
|
5
|
+
database: crypt_keeper_providers
|
6
|
+
pool: 5
|
7
|
+
username: postgres
|
8
|
+
password:
|
9
|
+
mysql:
|
10
|
+
adapter: mysql2
|
11
|
+
encoding: utf8
|
12
|
+
reconnect: false
|
13
|
+
database: crypt_keeper_providers
|
14
|
+
pool: 5
|
15
|
+
username: root
|
16
|
+
password:
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeperProviders
|
4
|
+
describe MysqlAesLogSubscriber do
|
5
|
+
use_postgres
|
6
|
+
|
7
|
+
subject { ::ActiveRecord::LogSubscriber.new }
|
8
|
+
|
9
|
+
let(:input_query) do
|
10
|
+
"SELECT AES_ENCRYPT('encrypt_value', 'encrypt_key'), AES_ENCRYPT('decrypt_value', 'decrypt_key') FROM DUAL;"
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:output_query) do
|
14
|
+
"SELECT AES_ENCRYPT([FILTERED]), AES_DECRYPT([FILTERED]) FROM DUAL;"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "filters mysql aes functions" do
|
18
|
+
subject.should_receive(:sql_without_mysql_aes) do |event|
|
19
|
+
event.payload[:sql].should == output_query
|
20
|
+
end
|
21
|
+
|
22
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: output_query }))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeperProviders
|
4
|
+
describe PostgresPgpLogSubscriber do
|
5
|
+
use_postgres
|
6
|
+
|
7
|
+
subject { ::ActiveRecord::LogSubscriber.new }
|
8
|
+
|
9
|
+
let(:input_query) do
|
10
|
+
"SELECT pgp_sym_encrypt('encrypt_value', 'encrypt_key'), pgp_sym_decrypt('decrypt_value', 'decrypt_key') FROM DUAL;"
|
11
|
+
end
|
12
|
+
|
13
|
+
let(:output_query) do
|
14
|
+
"SELECT pgp_sym_encrypt([FILTERED]), pgp_sym_decrypt([FILTERED]) FROM DUAL;"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "filters pgp functions" do
|
18
|
+
subject.should_receive(:sql_without_postgres_pgp) do |event|
|
19
|
+
event.payload[:sql].should == output_query
|
20
|
+
end
|
21
|
+
|
22
|
+
subject.sql(ActiveSupport::Notifications::Event.new(:sql, 1, 1, 1, { sql: output_query }))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/spec/model_spec.rb
CHANGED
@@ -2,7 +2,10 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module CryptKeeper
|
4
4
|
describe Model do
|
5
|
+
use_sqlite
|
6
|
+
|
5
7
|
subject { SensitiveData }
|
8
|
+
|
6
9
|
describe "#crypt_keeper" do
|
7
10
|
after(:each) do
|
8
11
|
subject.instance_variable_set(:@encryptor_klass, nil)
|
@@ -29,7 +32,7 @@ module CryptKeeper
|
|
29
32
|
|
30
33
|
it "should accept class name (as string) for encryptor option" do
|
31
34
|
subject.crypt_keeper :storage, :secret, key1: 1, key2: 2, encryptor: "FakeEncryptor"
|
32
|
-
subject.send(:encryptor_klass).should ==
|
35
|
+
subject.send(:encryptor_klass).should == CryptKeeper::Provider::FakeEncryptor
|
33
36
|
|
34
37
|
subject.instance_variable_set(:@encryptor_klass, nil)
|
35
38
|
end
|
@@ -53,6 +56,13 @@ module CryptKeeper
|
|
53
56
|
subject.save!
|
54
57
|
subject.storage.should == cipher_text
|
55
58
|
end
|
59
|
+
|
60
|
+
it "should not encrypt nil" do
|
61
|
+
subject.storage = nil
|
62
|
+
subject.stub :decrypt_callback
|
63
|
+
subject.save!
|
64
|
+
subject.storage.should be_nil
|
65
|
+
end
|
56
66
|
end
|
57
67
|
|
58
68
|
describe "#decrypt" do
|
@@ -62,6 +72,13 @@ module CryptKeeper
|
|
62
72
|
subject.save!
|
63
73
|
subject.storage.should == plain_text
|
64
74
|
end
|
75
|
+
|
76
|
+
it "should not decrypt nil" do
|
77
|
+
subject.storage = nil
|
78
|
+
subject.stub :encrypt_callback
|
79
|
+
subject.save!
|
80
|
+
subject.storage.should be_nil
|
81
|
+
end
|
65
82
|
end
|
66
83
|
|
67
84
|
describe "Encrypt & Decrypt" do
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe Aes do
|
6
|
+
subject { Aes.new(key: 'cake') }
|
7
|
+
|
8
|
+
describe "#initialize" do
|
9
|
+
let(:hexed_key) do
|
10
|
+
Digest::SHA256.digest('cake')
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should extract the key and digest it" do
|
14
|
+
subject.key.should == hexed_key
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should raise an exception with a missing key" do
|
18
|
+
expect { Aes.new }.to raise_error(ArgumentError, "Missing :key")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#encrypt" do
|
23
|
+
let(:encrypted) do
|
24
|
+
subject.encrypt 'string'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should encrypt the string" do
|
28
|
+
encrypted.should_not == 'string'
|
29
|
+
encrypted.should_not be_nil
|
30
|
+
encrypted.should_not be_empty
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#decrypt" do
|
35
|
+
let(:decrypted) do
|
36
|
+
subject.decrypt "MC41MDk5MjI2NjgxMDI1MDI2OmNyeXB0X2tlZXBlcjpPI/8dCqWXDMVj7Jqs\nuwf/\n"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should decrypt the string" do
|
40
|
+
decrypted.should == 'string'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe MysqlAes do
|
6
|
+
use_mysql
|
7
|
+
|
8
|
+
let(:plain_text) { 'test' }
|
9
|
+
|
10
|
+
# MySQL stores AES encrypted strings in binary which you can't paste
|
11
|
+
# into a spec :). This is a Base64 encoded string of 'test' AES encrypted
|
12
|
+
# by AES_ENCRYPT()
|
13
|
+
let(:cipher_text) do
|
14
|
+
Base64.decode64 "nbKOoWn8kvAw9k/C2Mex6Q==\n"
|
15
|
+
end
|
16
|
+
|
17
|
+
subject { MysqlAes.new key: 'candy' }
|
18
|
+
|
19
|
+
its(:key) { should == 'candy' }
|
20
|
+
|
21
|
+
describe "#initialize" do
|
22
|
+
it "should raise an exception with a missing key" do
|
23
|
+
expect { MysqlAes.new }.to raise_error(ArgumentError, "Missing :key")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#encrypt" do
|
28
|
+
it "should encrypt the string" do
|
29
|
+
subject.encrypt(plain_text).should_not == plain_text
|
30
|
+
subject.encrypt(plain_text).should_not be_empty
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#decrypt" do
|
35
|
+
it "should decrypt the string" do
|
36
|
+
subject.decrypt(cipher_text).should == plain_text
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module CryptKeeper
|
4
|
+
module Provider
|
5
|
+
describe PostgresPgp do
|
6
|
+
use_postgres
|
7
|
+
|
8
|
+
let(:cipher_text) { '\\xc30d0407030283b15f71b6a7d0296cd23501bd2c8fe3c7a56005ff4619527c4291509a78c77a6758cddd2a14acbde589fa10b3e0686865182d3beadaf237b9f928e7ba1810b8' }
|
9
|
+
let(:plain_text) { 'test' }
|
10
|
+
|
11
|
+
subject { PostgresPgp.new key: 'candy' }
|
12
|
+
|
13
|
+
its(:key) { should == 'candy' }
|
14
|
+
|
15
|
+
describe "#initialize" do
|
16
|
+
it "should raise an exception with a missing key" do
|
17
|
+
expect { PostgresPgp.new }.to raise_error(ArgumentError, "Missing :key")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#encrypt" do
|
22
|
+
it "should encrypt the string" do
|
23
|
+
subject.encrypt(plain_text).should_not == plain_text
|
24
|
+
subject.encrypt(plain_text).should_not be_empty
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#decrypt" do
|
29
|
+
it "should decrypt the string" do
|
30
|
+
subject.decrypt(cipher_text).should == plain_text
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -1,16 +1,50 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require 'logger'
|
3
3
|
|
4
|
-
ActiveRecord::Base.
|
5
|
-
ActiveRecord::
|
6
|
-
|
7
|
-
|
8
|
-
ActiveRecord::
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
4
|
+
::ActiveRecord::Base.logger = Logger.new SPEC_ROOT.join('debug.log').to_s
|
5
|
+
::ActiveRecord::Migration.verbose = false
|
6
|
+
|
7
|
+
module CryptKeeper
|
8
|
+
class SensitiveData < ActiveRecord::Base; end
|
9
|
+
|
10
|
+
module ConnectionHelpers
|
11
|
+
|
12
|
+
def use_postgres
|
13
|
+
before :all do
|
14
|
+
::ActiveRecord::Base.clear_active_connections!
|
15
|
+
config = YAML.load_file SPEC_ROOT.join('database.yml')
|
16
|
+
::ActiveRecord::Base.establish_connection(config['postgres'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def use_mysql
|
21
|
+
before :all do
|
22
|
+
::ActiveRecord::Base.clear_active_connections!
|
23
|
+
config = YAML.load_file SPEC_ROOT.join('database.yml')
|
24
|
+
::ActiveRecord::Base.establish_connection(config['mysql'])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def use_sqlite
|
29
|
+
before :all do
|
30
|
+
::ActiveRecord::Base.clear_active_connections!
|
31
|
+
::ActiveRecord::Base.establish_connection(:adapter => 'sqlite3',
|
32
|
+
:database => ':memory:')
|
33
|
+
|
34
|
+
::ActiveRecord::Schema.define do
|
35
|
+
create_table :sensitive_data, :force => true do |t|
|
36
|
+
t.column :name, :string
|
37
|
+
t.column :storage, :text
|
38
|
+
t.column :secret, :text
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
13
44
|
end
|
14
45
|
end
|
15
46
|
|
16
|
-
|
47
|
+
|
48
|
+
RSpec.configure do |config|
|
49
|
+
config.extend CryptKeeper::ConnectionHelpers
|
50
|
+
end
|
data/spec/support/encryptors.rb
CHANGED
@@ -1,26 +1,29 @@
|
|
1
1
|
# A fake class that does no encryption
|
2
|
-
module
|
3
|
-
|
4
|
-
|
2
|
+
module CryptKeeper
|
3
|
+
module Provider
|
4
|
+
class FakeEncryptor
|
5
|
+
def initialize(*args)
|
6
|
+
end
|
5
7
|
end
|
6
8
|
end
|
7
9
|
end
|
8
10
|
|
9
11
|
# This class embeds the passphrase in the beginning of the string
|
10
12
|
# and then reverses the 'plaintext'
|
11
|
-
module
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
module CryptKeeper
|
14
|
+
module Provider
|
15
|
+
class Encryptor
|
16
|
+
def initialize(options = {})
|
17
|
+
@passphrase = options[:passphrase]
|
18
|
+
end
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
def encrypt(data)
|
21
|
+
@passphrase + data.reverse
|
22
|
+
end
|
20
23
|
|
21
|
-
|
22
|
-
|
24
|
+
def decrypt(data)
|
25
|
+
data.sub(/^#{@passphrase}/, '').reverse
|
26
|
+
end
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
26
|
-
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crypt_keeper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -43,22 +43,6 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '3.0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: crypt_keeper_providers
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
|
-
requirements:
|
51
|
-
- - '='
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: 0.5.2
|
54
|
-
type: :runtime
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
|
-
requirements:
|
59
|
-
- - '='
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 0.5.2
|
62
46
|
- !ruby/object:Gem::Dependency
|
63
47
|
name: appraisal
|
64
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -155,6 +139,38 @@ dependencies:
|
|
155
139
|
- - ! '>='
|
156
140
|
- !ruby/object:Gem::Version
|
157
141
|
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: pg
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.14.0
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.14.0
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: mysql2
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 0.3.11
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ~>
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.3.11
|
158
174
|
description: Transparent encryption for ActiveRecord that isn't over-engineered
|
159
175
|
email:
|
160
176
|
- jmazzi@gmail.com
|
@@ -179,9 +195,20 @@ files:
|
|
179
195
|
- gemfiles/activerecord_3_2.gemfile
|
180
196
|
- gemfiles/activerecord_3_2.gemfile.lock
|
181
197
|
- lib/crypt_keeper.rb
|
198
|
+
- lib/crypt_keeper/log_subscriber/mysql_aes.rb
|
199
|
+
- lib/crypt_keeper/log_subscriber/postgres_pgp.rb
|
182
200
|
- lib/crypt_keeper/model.rb
|
201
|
+
- lib/crypt_keeper/provider/aes.rb
|
202
|
+
- lib/crypt_keeper/provider/mysql_aes.rb
|
203
|
+
- lib/crypt_keeper/provider/postgres_pgp.rb
|
183
204
|
- lib/crypt_keeper/version.rb
|
205
|
+
- spec/default.database.yml
|
206
|
+
- spec/log_subscriber/mysql_aes.rb
|
207
|
+
- spec/log_subscriber/postgres_pgp.rb
|
184
208
|
- spec/model_spec.rb
|
209
|
+
- spec/provider/aes_spec.rb
|
210
|
+
- spec/provider/mysql_aes_spec.rb
|
211
|
+
- spec/provider/postgres_pgp_spec.rb
|
185
212
|
- spec/spec_helper.rb
|
186
213
|
- spec/support/active_record.rb
|
187
214
|
- spec/support/encryptors.rb
|
@@ -199,7 +226,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
199
226
|
version: '0'
|
200
227
|
segments:
|
201
228
|
- 0
|
202
|
-
hash:
|
229
|
+
hash: 777875145635862709
|
203
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
231
|
none: false
|
205
232
|
requirements:
|
@@ -208,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
235
|
version: '0'
|
209
236
|
segments:
|
210
237
|
- 0
|
211
|
-
hash:
|
238
|
+
hash: 777875145635862709
|
212
239
|
requirements: []
|
213
240
|
rubyforge_project:
|
214
241
|
rubygems_version: 1.8.23
|
@@ -216,7 +243,13 @@ signing_key:
|
|
216
243
|
specification_version: 3
|
217
244
|
summary: Transparent encryption for ActiveRecord that isn't over-engineered
|
218
245
|
test_files:
|
246
|
+
- spec/default.database.yml
|
247
|
+
- spec/log_subscriber/mysql_aes.rb
|
248
|
+
- spec/log_subscriber/postgres_pgp.rb
|
219
249
|
- spec/model_spec.rb
|
250
|
+
- spec/provider/aes_spec.rb
|
251
|
+
- spec/provider/mysql_aes_spec.rb
|
252
|
+
- spec/provider/postgres_pgp_spec.rb
|
220
253
|
- spec/spec_helper.rb
|
221
254
|
- spec/support/active_record.rb
|
222
255
|
- spec/support/encryptors.rb
|