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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/Rakefile +16 -16
  4. data/examples/symmetric-encryption.yml +33 -38
  5. data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +10 -14
  6. data/lib/rails/generators/symmetric_encryption/heroku_config/templates/symmetric-encryption.yml +28 -25
  7. data/lib/symmetric_encryption.rb +14 -6
  8. data/lib/symmetric_encryption/cipher.rb +151 -130
  9. data/lib/symmetric_encryption/config.rb +0 -1
  10. data/lib/symmetric_encryption/encoder.rb +79 -0
  11. data/lib/symmetric_encryption/extensions/active_record/base.rb +94 -134
  12. data/lib/symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key.rb +3 -89
  13. data/lib/symmetric_encryption/key_encryption_key.rb +32 -0
  14. data/lib/symmetric_encryption/railtie.rb +3 -3
  15. data/lib/symmetric_encryption/symmetric_encryption.rb +41 -8
  16. data/lib/symmetric_encryption/utils/re_encrypt_config_files.rb +82 -0
  17. data/lib/symmetric_encryption/version.rb +1 -1
  18. data/test/active_record_test.rb +149 -140
  19. data/test/cipher_test.rb +98 -6
  20. data/test/config/{mongoid_v5.yml → mongoid.yml} +0 -0
  21. data/test/config/symmetric-encryption.yml +4 -10
  22. data/test/config/test_new.key +2 -2
  23. data/test/encoder_test.rb +61 -0
  24. data/test/mongoid_test.rb +12 -22
  25. data/test/reader_test.rb +16 -11
  26. data/test/symmetric_encryption_test.rb +23 -3
  27. data/test/test_db.sqlite3 +0 -0
  28. data/test/test_helper.rb +2 -16
  29. data/test/writer_test.rb +1 -5
  30. metadata +11 -12
  31. data/test/config/mongoid_v2.yml +0 -6
  32. data/test/config/mongoid_v3.yml +0 -9
  33. 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
- class << self # Class methods
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
- options = params.last.is_a?(Hash) ? params.pop.dup : {}
45
-
46
- params.each do |attribute|
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
- # Returns whether an attribute has been configured to be encrypted
79
- #
80
- # Example
81
- #
82
- # class User < ActiveRecord::Base
83
- # attr_accessor :name
84
- # attr_encrypted :email
85
- # end
86
- #
87
- # User.encrypted_attribute?(:name) # false
88
- # User.encrypted_attribute?(:email) # true
89
- def encrypted_attribute?(attribute)
90
- encrypted_keys.include?(attribute)
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
- # Returns whether the attribute is the database column to hold the
94
- # encrypted data for a matching encrypted attribute
95
- #
96
- # Example
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
- private
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
- # Allows you to use dynamic methods like <tt>find_by_email</tt> or <tt>scoped_by_email</tt> for
112
- # encrypted attributes
113
- #
114
- # This is useful for encrypting fields like email addresses. Your user's email addresses
115
- # are encrypted in the database, but you can still look up a user by email for logging in
116
- #
117
- # Example
118
- #
119
- # class User < ActiveRecord::Base
120
- # attr_encrypted :email
121
- # end
122
- #
123
- # User.find_by_email_and_password('test@example.com', 'testing')
124
- # # results in a call to
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
- # Dynamic finders dropped in Rails 4.1
142
- if ActiveRecord::VERSION::STRING.to_f < 4.1
143
- alias_method_chain :method_missing, :attr_encrypted
144
- end
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
- # Support Encryption and decryption of fields in MongoMapper
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