symmetric-encryption 3.8.3 → 3.9.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/Rakefile +16 -16
- data/examples/symmetric-encryption.yml +33 -38
- data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +10 -14
- data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +28 -25
- data/lib/symmetric_encryption.rb +14 -6
- data/lib/symmetric_encryption/cipher.rb +151 -130
- data/lib/symmetric_encryption/config.rb +0 -1
- data/lib/symmetric_encryption/encoder.rb +79 -0
- data/lib/symmetric_encryption/extensions/active_record/base.rb +94 -134
- data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +3 -89
- data/lib/symmetric_encryption/key_encryption_key.rb +32 -0
- data/lib/symmetric_encryption/railtie.rb +3 -3
- data/lib/symmetric_encryption/symmetric_encryption.rb +41 -8
- data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +82 -0
- data/lib/symmetric_encryption/version.rb +1 -1
- data/test/active_record_test.rb +149 -140
- data/test/cipher_test.rb +98 -6
- data/test/config/{mongoid_v5.yml → mongoid.yml} +0 -0
- data/test/config/symmetric-encryption.yml +4 -10
- data/test/config/test_new.key +2 -2
- data/test/encoder_test.rb +61 -0
- data/test/mongoid_test.rb +12 -22
- data/test/reader_test.rb +16 -11
- data/test/symmetric_encryption_test.rb +23 -3
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +2 -16
- data/test/writer_test.rb +1 -5
- metadata +11 -12
- data/test/config/mongoid_v2.yml +0 -6
- data/test/config/mongoid_v3.yml +0 -9
- data/test/mongo_mapper_test.rb +0 -599
@@ -59,7 +59,6 @@ module SymmetricEncryption
|
|
59
59
|
# environment:
|
60
60
|
# Which environments config to load. Usually: production, development, etc.
|
61
61
|
def self.extract_ciphers(config)
|
62
|
-
# RSA key to decrypt key files
|
63
62
|
private_rsa_key = config[:private_rsa_key]
|
64
63
|
|
65
64
|
config[:ciphers].collect do |cipher_config|
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
module Encoder
|
3
|
+
def self.[](encoding)
|
4
|
+
case encoding
|
5
|
+
when :base64
|
6
|
+
Base64.new
|
7
|
+
when :base64strict
|
8
|
+
Base64Strict.new
|
9
|
+
when :base16
|
10
|
+
Base16.new
|
11
|
+
when :none
|
12
|
+
None.new
|
13
|
+
else
|
14
|
+
raise(ArgumentError, "Unknown encoder: #{encoding.inspect}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encode(binary_string, encoding)
|
19
|
+
encoder(encoding).encode(binary_string)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.decode(encoded_string, encoding)
|
23
|
+
encoder(encoding).decode(encoded_string)
|
24
|
+
end
|
25
|
+
|
26
|
+
class None
|
27
|
+
def encode(binary_string)
|
28
|
+
binary_string
|
29
|
+
end
|
30
|
+
|
31
|
+
def decode(encoded_string)
|
32
|
+
encoded_string
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class Base64
|
37
|
+
def encode(binary_string)
|
38
|
+
return binary_string if binary_string.nil? || (binary_string == '')
|
39
|
+
encoded_string = ::Base64.encode64(binary_string)
|
40
|
+
encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode(encoded_string)
|
44
|
+
return encoded_string if encoded_string.nil? || (encoded_string == '')
|
45
|
+
decoded_string = ::Base64.decode64(encoded_string)
|
46
|
+
decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Base64Strict
|
51
|
+
def encode(binary_string)
|
52
|
+
return binary_string if binary_string.nil? || (binary_string == '')
|
53
|
+
encoded_string = ::Base64.strict_encode64(binary_string)
|
54
|
+
encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
55
|
+
end
|
56
|
+
|
57
|
+
def decode(encoded_string)
|
58
|
+
return encoded_string if encoded_string.nil? || (encoded_string == '')
|
59
|
+
decoded_string = ::Base64.decode64(encoded_string)
|
60
|
+
decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class Base16
|
65
|
+
def encode(binary_string)
|
66
|
+
return binary_string if binary_string.nil? || (binary_string == '')
|
67
|
+
encoded_string = binary_string.to_s.unpack('H*').first
|
68
|
+
encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
69
|
+
end
|
70
|
+
|
71
|
+
def decode(encoded_string)
|
72
|
+
return encoded_string if encoded_string.nil? || (encoded_string == '')
|
73
|
+
decoded_string = [encoded_string].pack('H*')
|
74
|
+
decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
@@ -1,147 +1,107 @@
|
|
1
1
|
module ActiveRecord #:nodoc:
|
2
2
|
class Base
|
3
|
+
# Transparently encrypt and decrypt values stored via ActiveRecord.
|
4
|
+
#
|
5
|
+
# Parameters:
|
6
|
+
# * Symbolic names of each method to create which has a corresponding
|
7
|
+
# method already defined in rails starting with: encrypted_
|
8
|
+
# * Followed by an optional hash:
|
9
|
+
# :random_iv [true|false]
|
10
|
+
# Whether the encrypted value should use a random IV every time the
|
11
|
+
# field is encrypted.
|
12
|
+
# It is recommended to set this to true where feasible. If the encrypted
|
13
|
+
# value could be used as part of a SQL where clause, or as part
|
14
|
+
# of any lookup, then it must be false.
|
15
|
+
# Setting random_iv to true will result in a different encrypted output for
|
16
|
+
# the same input string.
|
17
|
+
# Note: Only set to true if the field will never be used as part of
|
18
|
+
# the where clause in an SQL query.
|
19
|
+
# Note: When random_iv is true it will add a 8 byte header, plus the bytes
|
20
|
+
# to store the random IV in every returned encrypted string, prior to the
|
21
|
+
# encoding if any.
|
22
|
+
# Default: false
|
23
|
+
# Highly Recommended where feasible: true
|
24
|
+
#
|
25
|
+
# :type [Symbol]
|
26
|
+
# The type for this field, #see SymmetricEncryption::COERCION_TYPES
|
27
|
+
# Default: :string
|
28
|
+
#
|
29
|
+
# :compress [true|false]
|
30
|
+
# Whether to compress str before encryption
|
31
|
+
# Should only be used for large strings since compression overhead and
|
32
|
+
# the overhead of adding the 'magic' header may exceed any benefits of
|
33
|
+
# compression
|
34
|
+
# Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
35
|
+
# Default: false
|
36
|
+
def self.attr_encrypted(*params)
|
37
|
+
# Ensure ActiveRecord has created all its methods first
|
38
|
+
# Ignore failures since the table may not yet actually exist
|
39
|
+
define_attribute_methods rescue nil
|
3
40
|
|
4
|
-
|
5
|
-
# Drop in replacement for attr_encrypted gem, except that it uses
|
6
|
-
# SymmetricEncryption for managing the encryption key
|
7
|
-
#
|
8
|
-
# Parameters:
|
9
|
-
# * Symbolic names of each method to create which has a corresponding
|
10
|
-
# method already defined in rails starting with: encrypted_
|
11
|
-
# * Followed by an optional hash:
|
12
|
-
# :random_iv [true|false]
|
13
|
-
# Whether the encrypted value should use a random IV every time the
|
14
|
-
# field is encrypted.
|
15
|
-
# It is recommended to set this to true where feasible. If the encrypted
|
16
|
-
# value could be used as part of a SQL where clause, or as part
|
17
|
-
# of any lookup, then it must be false.
|
18
|
-
# Setting random_iv to true will result in a different encrypted output for
|
19
|
-
# the same input string.
|
20
|
-
# Note: Only set to true if the field will never be used as part of
|
21
|
-
# the where clause in an SQL query.
|
22
|
-
# Note: When random_iv is true it will add a 8 byte header, plus the bytes
|
23
|
-
# to store the random IV in every returned encrypted string, prior to the
|
24
|
-
# encoding if any.
|
25
|
-
# Default: false
|
26
|
-
# Highly Recommended where feasible: true
|
27
|
-
#
|
28
|
-
# :type [Symbol]
|
29
|
-
# The type for this field, #see SymmetricEncryption::COERCION_TYPES
|
30
|
-
# Default: :string
|
31
|
-
#
|
32
|
-
# :compress [true|false]
|
33
|
-
# Whether to compress str before encryption
|
34
|
-
# Should only be used for large strings since compression overhead and
|
35
|
-
# the overhead of adding the 'magic' header may exceed any benefits of
|
36
|
-
# compression
|
37
|
-
# Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
38
|
-
# Default: false
|
39
|
-
def attr_encrypted(*params)
|
40
|
-
# Ensure ActiveRecord has created all its methods first
|
41
|
-
# Ignore failures since the table may not yet actually exist
|
42
|
-
define_attribute_methods rescue nil
|
41
|
+
options = params.last.is_a?(Hash) ? params.pop.dup : {}
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
SymmetricEncryption::Generator.generate_decrypted_accessors(self, attribute, "encrypted_#{attribute}", options)
|
48
|
-
encrypted_attributes[attribute.to_sym] = "encrypted_#{attribute}".to_sym
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
|
53
|
-
# names as values
|
54
|
-
#
|
55
|
-
# Example
|
56
|
-
#
|
57
|
-
# class User < ActiveRecord::Base
|
58
|
-
# attr_encrypted :email
|
59
|
-
# end
|
60
|
-
#
|
61
|
-
# User.encrypted_attributes => { email: encrypted_email }
|
62
|
-
def encrypted_attributes
|
63
|
-
@encrypted_attributes ||= superclass.respond_to?(:encrypted_attributes) ? superclass.encrypted_attributes.dup : {}
|
64
|
-
end
|
65
|
-
|
66
|
-
# Return the name of all encrypted virtual attributes as an Array of symbols
|
67
|
-
# Example: [:email, :password]
|
68
|
-
def encrypted_keys
|
69
|
-
@encrypted_keys ||= encrypted_attributes.keys
|
70
|
-
end
|
71
|
-
|
72
|
-
# Return the name of all encrypted columns as an Array of symbols
|
73
|
-
# Example: [:encrypted_email, :encrypted_password]
|
74
|
-
def encrypted_columns
|
75
|
-
@encrypted_columns ||= encrypted_attributes.values
|
43
|
+
params.each do |attribute|
|
44
|
+
SymmetricEncryption::Generator.generate_decrypted_accessors(self, attribute, "encrypted_#{attribute}", options)
|
45
|
+
encrypted_attributes[attribute.to_sym] = "encrypted_#{attribute}".to_sym
|
76
46
|
end
|
47
|
+
end
|
77
48
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
end
|
49
|
+
# Contains a hash of encrypted attributes with virtual attribute names as keys and real attribute
|
50
|
+
# names as values
|
51
|
+
#
|
52
|
+
# Example
|
53
|
+
#
|
54
|
+
# class User < ActiveRecord::Base
|
55
|
+
# attr_encrypted :email
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# User.encrypted_attributes => { email: encrypted_email }
|
59
|
+
def self.encrypted_attributes
|
60
|
+
@encrypted_attributes ||= superclass.respond_to?(:encrypted_attributes) ? superclass.encrypted_attributes.dup : {}
|
61
|
+
end
|
92
62
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# class User < ActiveRecord::Base
|
99
|
-
# attr_accessor :name
|
100
|
-
# attr_encrypted :email
|
101
|
-
# end
|
102
|
-
#
|
103
|
-
# User.encrypted_column?(:encrypted_name) # false
|
104
|
-
# User.encrypted_column?(:encrypted_email) # true
|
105
|
-
def encrypted_column?(attribute)
|
106
|
-
encrypted_columns.include?(attribute)
|
107
|
-
end
|
63
|
+
# Return the name of all encrypted virtual attributes as an Array of symbols
|
64
|
+
# Example: [:email, :password]
|
65
|
+
def self.encrypted_keys
|
66
|
+
@encrypted_keys ||= encrypted_attributes.keys
|
67
|
+
end
|
108
68
|
|
109
|
-
|
69
|
+
# Return the name of all encrypted columns as an Array of symbols
|
70
|
+
# Example: [:encrypted_email, :encrypted_password]
|
71
|
+
def self.encrypted_columns
|
72
|
+
@encrypted_columns ||= encrypted_attributes.values
|
73
|
+
end
|
110
74
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# User.find_by_encrypted_email_and_password('the_encrypted_version_of_test@example.com', 'testing')
|
126
|
-
def method_missing_with_attr_encrypted(method, *args, &block)
|
127
|
-
if match = /^(find|scoped)_(all_by|by)_([_a-zA-Z]\w*)$/.match(method.to_s)
|
128
|
-
attribute_names = match.captures.last.split('_and_')
|
129
|
-
attribute_names.each_with_index do |attribute, index|
|
130
|
-
encrypted_name = "encrypted_#{attribute}"
|
131
|
-
if method_defined? encrypted_name.to_sym
|
132
|
-
args[index] = ::SymmetricEncryption.encrypt(args[index])
|
133
|
-
attribute_names[index] = encrypted_name
|
134
|
-
end
|
135
|
-
end
|
136
|
-
method = "#{match.captures[0]}_#{match.captures[1]}_#{attribute_names.join('_and_')}".to_sym
|
137
|
-
end
|
138
|
-
method_missing_without_attr_encrypted(method, *args, &block)
|
139
|
-
end
|
75
|
+
# Returns whether an attribute has been configured to be encrypted
|
76
|
+
#
|
77
|
+
# Example
|
78
|
+
#
|
79
|
+
# class User < ActiveRecord::Base
|
80
|
+
# attr_accessor :name
|
81
|
+
# attr_encrypted :email
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# User.encrypted_attribute?(:name) # false
|
85
|
+
# User.encrypted_attribute?(:email) # true
|
86
|
+
def self.encrypted_attribute?(attribute)
|
87
|
+
encrypted_keys.include?(attribute)
|
88
|
+
end
|
140
89
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
90
|
+
# Returns whether the attribute is the database column to hold the
|
91
|
+
# encrypted data for a matching encrypted attribute
|
92
|
+
#
|
93
|
+
# Example
|
94
|
+
#
|
95
|
+
# class User < ActiveRecord::Base
|
96
|
+
# attr_accessor :name
|
97
|
+
# attr_encrypted :email
|
98
|
+
# end
|
99
|
+
#
|
100
|
+
# User.encrypted_column?(:encrypted_name) # false
|
101
|
+
# User.encrypted_column?(:encrypted_email) # true
|
102
|
+
def self.encrypted_column?(attribute)
|
103
|
+
encrypted_columns.include?(attribute)
|
145
104
|
end
|
105
|
+
|
146
106
|
end
|
147
107
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
#
|
2
|
+
# DEPRECATED !!!
|
3
|
+
#
|
2
4
|
module MongoMapper
|
3
5
|
module Plugins
|
4
6
|
module EncryptedKey
|
@@ -17,94 +19,6 @@ module MongoMapper
|
|
17
19
|
}
|
18
20
|
|
19
21
|
module ClassMethods
|
20
|
-
# MongoMapper::Document.encrypted_key
|
21
|
-
#
|
22
|
-
# Support automatic encryption and decryption of fields in MongoMapper
|
23
|
-
#
|
24
|
-
# Example:
|
25
|
-
#
|
26
|
-
# class Person
|
27
|
-
# include MongoMapper::Document
|
28
|
-
#
|
29
|
-
# key :name, String
|
30
|
-
# encrypted_key :social_security_number, String
|
31
|
-
# key :date_of_birth, Date
|
32
|
-
# encrypted_key :life_history, String, encrypted: { compress: true, random_iv: true }
|
33
|
-
#
|
34
|
-
# # Encrypted fields are _always_ stored in Mongo as a String
|
35
|
-
# # By specifying a type other than String, Symmetric Encryption will
|
36
|
-
# # perform the necessary conversions
|
37
|
-
# #
|
38
|
-
# # The following types are supported:
|
39
|
-
# # String
|
40
|
-
# # Integer
|
41
|
-
# # Float
|
42
|
-
# # BigDecimal
|
43
|
-
# # DateTime
|
44
|
-
# # Time
|
45
|
-
# # Date
|
46
|
-
# # Hash - (Stored as encrypted JSON in MongoDB)
|
47
|
-
# encrypted_key :age, Integer, encrypted: { random_iv: true }
|
48
|
-
# end
|
49
|
-
#
|
50
|
-
# The above document results in the following document in the Mongo collection 'persons':
|
51
|
-
# {
|
52
|
-
# 'name' : 'Joe',
|
53
|
-
# 'encrypted_social_security_number' : '...',
|
54
|
-
# 'age' : 21
|
55
|
-
# 'encrypted_life_history' : '...',
|
56
|
-
# }
|
57
|
-
#
|
58
|
-
# Symmetric Encryption creates the getters and setters to be able to work with the field
|
59
|
-
# in it's decrypted form. For example
|
60
|
-
#
|
61
|
-
# Example:
|
62
|
-
# person = Person.where(encrypted_social_security_number: '...').first
|
63
|
-
#
|
64
|
-
# puts "Decrypted Social Security Number is: #{person.social_security_number}"
|
65
|
-
#
|
66
|
-
# # Or is the same as
|
67
|
-
# puts "Decrypted Social Security Number is: #{SymmetricEncryption.decrypt(person.encrypted_social_security_number)}"
|
68
|
-
#
|
69
|
-
# # Sets the encrypted_social_security_number to encrypted version
|
70
|
-
# person.social_security_number = '123456789'
|
71
|
-
#
|
72
|
-
# # Or, is equivalent to:
|
73
|
-
# person.encrypted_social_security_number = SymmetricEncryption.encrypt('123456789')
|
74
|
-
#
|
75
|
-
# Note: Only 'String' types are currently supported for encryption
|
76
|
-
#
|
77
|
-
# Note: Unlike attr_encrypted finders must use the encrypted field name
|
78
|
-
# Invalid Example, does not work:
|
79
|
-
# person = Person.where(social_security_number: '123456789').first
|
80
|
-
#
|
81
|
-
# Valid Example:
|
82
|
-
# person = Person.where(encrypted_social_security_number: SymmetricEncryption.encrypt('123456789')).first
|
83
|
-
#
|
84
|
-
# Defines all the fields that are accessible on the Document
|
85
|
-
# For each field that is defined, a getter and setter will be
|
86
|
-
# added as an instance method to the Document.
|
87
|
-
#
|
88
|
-
# @example Define an encrypted key
|
89
|
-
# encrypted_key :social_security_number, String, encrypted: {compress: false, random_iv: false}
|
90
|
-
# encrypted_key :sensitive_text, String, encrypted: {compress: true, random_iv: true}
|
91
|
-
#
|
92
|
-
# @param [ Symbol ] name The name of the key.
|
93
|
-
# @param [ Object ] type The type of the key.
|
94
|
-
# @param [ Hash ] options The options to pass to the field, including any MongoMapper specific options
|
95
|
-
#
|
96
|
-
# @option options [ Hash ] :encrypted consists of:
|
97
|
-
# @option options [ Boolean ] :random_iv Whether the encrypted value should use a random IV every time the field is encrypted.
|
98
|
-
# @option options [ Boolean ] :compress Whether to compress this encrypted field
|
99
|
-
# @option options [ Symbol ] :encrypt_as Name of the encypted field in Mongo
|
100
|
-
#
|
101
|
-
# Some of the other regular MongoMapper options:
|
102
|
-
# :default, :alias, :field_name, :accessors, :abbr
|
103
|
-
#
|
104
|
-
# Note:
|
105
|
-
# Use MongoMapper's built-in support for :field_name to specify a different
|
106
|
-
# field name in MongoDB for the encrypted field from what is used via the model
|
107
|
-
#
|
108
22
|
def encrypted_key(key_name, type, full_options={})
|
109
23
|
full_options = full_options.is_a?(Hash) ? full_options.dup : {}
|
110
24
|
options = full_options.delete(:encrypted) || {}
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
module SymmetricEncryption
|
3
|
+
# Class that manages the key that is used to encrypt the encryption key.
|
4
|
+
# Currently uses RSA asymmetric encryption to secure the key.
|
5
|
+
#
|
6
|
+
# Note:
|
7
|
+
# No encoding or decoding is performed.
|
8
|
+
class KeyEncryptionKey
|
9
|
+
# Returns [String] a new key encryption key.
|
10
|
+
def self.generate(options = {})
|
11
|
+
options = options.dup
|
12
|
+
size = options.delete(:size) || 2048
|
13
|
+
OpenSSL::PKey::RSA.generate(size).to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(key_encryption_key)
|
17
|
+
@rsa = OpenSSL::PKey::RSA.new(key_encryption_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def encrypt(key)
|
21
|
+
rsa.public_encrypt(key)
|
22
|
+
end
|
23
|
+
|
24
|
+
def decrypt(encrypted_key)
|
25
|
+
rsa.private_decrypt(encrypted_key)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :rsa
|
31
|
+
end
|
32
|
+
end
|