symmetric-encryption 3.8.3 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 788a470ed0ed501adc827789838ad6460fa6070b
4
- data.tar.gz: ba3c6a6865c5127fa409271e477ab2caee65c1a6
3
+ metadata.gz: fad0434ee0b203f85088bc62492c16d2365a284c
4
+ data.tar.gz: c8cd4796787faa48be52155d75e1e312f627b38d
5
5
  SHA512:
6
- metadata.gz: 525c6df5648bbf5af0432eaab63a235e0ac0fbf3a9a503576e3cf89d11a8d074820aeea9017067afcce950d37f9902f3b39620e81c3239f615488316be57ff19
7
- data.tar.gz: ac5ae0631681185ac0c2bbdfdb0fce0b61a803ddaa1410bc19cb77a98be7b1b37a9ef7417b047781c38c69ae6b75369085d654578767143e1d7b7143821ce251
6
+ metadata.gz: 59fe734623957ac4abed7b6b94786153a793947528d6b857612f418c6fa151947076b8802b5fe901bc2a1d0bad02aa79aa6790951b0671d054e4274ea09a29cc
7
+ data.tar.gz: 72270dd87fd4e16616b31e7d173facffc2da383c6f0719c0cc76bb9e353d0b33f1179c419af24f36c4ac108e6279b695202163884c01d0d97fe7f770688223b2
data/README.md CHANGED
@@ -33,8 +33,8 @@ Fully supports Symmetric Encryption to encrypt data in flight and at rest while
33
33
 
34
34
  Symmetric Encryption works with the following Ruby VMs:
35
35
 
36
- - Ruby 2.1, 2.2, 2.3, and above
37
- - JRuby 1.7.23, 9.0.5 and above
36
+ - Ruby 2.1 and higher.
37
+ - JRuby 9.1 and higher.
38
38
 
39
39
  ## Upgrading to SymmetricEncryption V3
40
40
 
data/Rakefile CHANGED
@@ -1,8 +1,9 @@
1
- require 'rake/clean'
2
- require 'rake/testtask'
1
+ # Setup bundler to avoid having to run bundle exec all the time.
2
+ require 'rubygems'
3
+ require 'bundler/setup'
3
4
 
4
- $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
5
- require 'symmetric_encryption/version'
5
+ require 'rake/testtask'
6
+ require_relative 'lib/symmetric_encryption/version'
6
7
 
7
8
  task :gem do
8
9
  system 'gem build symmetric-encryption.gemspec'
@@ -15,17 +16,16 @@ task :publish => :gem do
15
16
  system "rm symmetric-encryption-#{SymmetricEncryption::VERSION}.gem"
16
17
  end
17
18
 
18
- desc 'Run Test Suite'
19
- task :test do
20
- Rake::TestTask.new(:functional) do |t|
21
- t.test_files = FileList['test/*_test.rb']
22
- t.verbose = true
23
- end
24
-
25
- # For mongoid
26
- ENV['RACK_ENV'] = 'test'
27
-
28
- Rake::Task['functional'].invoke
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.pattern = 'test/**/*_test.rb'
21
+ t.verbose = true
22
+ t.warning = false
29
23
  end
30
24
 
31
- task :default => :test
25
+ # By default run tests against all appraisals
26
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
27
+ require 'appraisal'
28
+ task default: :appraisal
29
+ else
30
+ task default: :test
31
+ end
@@ -4,9 +4,9 @@
4
4
  ---
5
5
  # For the development and test environments the test symmetric encryption keys
6
6
  # can be placed directly in the source code.
7
- # And therefore no RSA private key is required
7
+ # And therefore no key encryption key is required
8
8
  development: &development_defaults
9
- key: 1234567890ABCDEF1234567890ABCDEF
9
+ key: 1234567890ABCDEF
10
10
  iv: 1234567890ABCDEF
11
11
  cipher: aes-128-cbc
12
12
 
@@ -14,12 +14,12 @@ test:
14
14
  <<: *development_defaults
15
15
 
16
16
  production:
17
- # Since the key to encrypt and decrypt with must NOT be stored along with the
18
- # source code, we only hold a RSA key that is used to unlock the file
19
- # containing the actual symmetric encryption key
17
+ # Since the encryption key must NOT be stored along with the
18
+ # source code, only store the key encryption key here.
20
19
  #
21
- # Sample RSA Key, DO NOT use this RSA key, generate a new one using
22
- # openssl genrsa 2048
20
+ # Test Key encryption key, DO NOT use this key, generate a new one using
21
+ # SymmetricEncryption::KeyEncryptionKey.generate
22
+ # Or use the rails generator to create a new config file as described in the readme
23
23
  private_rsa_key: |
24
24
  -----BEGIN RSA PRIVATE KEY-----
25
25
  MIIEpAIBAAKCAQEAxIL9H/jYUGpA38v6PowRSRJEo3aNVXULNM/QNRpx2DTf++KH
@@ -49,11 +49,11 @@ production:
49
49
  r1URaMAun2PfAB4g2N/kEZTExgeOGqXjFhvvjdzl97ux2cTyZhaTXg==
50
50
  -----END RSA PRIVATE KEY-----
51
51
 
52
- # List Symmetric Key Ciphers in the order of current / latest first
52
+ # List Symmetric Key Ciphers in the order of current / newest first
53
53
  ciphers:
54
- # Filename containing Symmetric Encryption Key encrypted using the
55
- # RSA public key derived from the private key above
56
- - key_filename: /etc/rails/.rails.key
54
+ -
55
+ # Name of the file containing the encrypted key and iv.
56
+ key_filename: /etc/rails/.rails.key
57
57
  iv_filename: /etc/rails/.rails.iv
58
58
 
59
59
  # Encryption cipher
@@ -83,31 +83,26 @@ production:
83
83
  # Default: base64
84
84
  encoding: base64strict
85
85
 
86
- # FUTURE ENHANCEMENT:
87
- #
88
- # By adding a version indicator all encrypted data will include
89
- # an additional first Byte that includes this version number to
90
- # assist with speeding up decryption when adding new encryption keys
91
- # and to support old data decryption using older keys
92
- #
93
- # By not specifying a version, or setting it to 0 will disable version
94
- # identification prior to decrypting data
95
- # During decryption these Keys will be tried in the order listed in the
96
- # configuration file starting with the first in the list
97
- # Slower since a decryption attempt is made for every key until the
98
- # correct key is located. However, all encrypted data does not require
99
- # the 1 Byte version header prefix
100
- #
101
- # Default: 0
102
- #version: 0
86
+ # Version of this key so that when a new key is supplied, old encrypted data can be decrypted
87
+ # using the correct key.
88
+ # Increment this version with every time a new key is generated.
89
+ version: 2
90
+
91
+ # Highly Recommended to always set this to true.
92
+ # Add a header to every encrypted message.
93
+ always_add_header: true
94
+
95
+ # OPTIONAL:
96
+ #
97
+ # Any previous Symmetric Encryption Keys
98
+ #
99
+ # Only used when old data still exists that requires old decryption keys
100
+ # to be used
101
+ -
102
+ key_filename: /etc/rails/.rails_old.key
103
+ iv_filename: /etc/rails/.rails_old.iv
104
+ cipher: aes-256-cbc
105
+ encoding: base64strict
106
+ version: 1
107
+ always_add_header: true
103
108
 
104
- # OPTIONAL:
105
- #
106
- # Any previous Symmetric Encryption Keys
107
- #
108
- # Only used when old data still exists that requires old decryption keys
109
- # to be used
110
- - key_filename: /etc/rails/.rails_old.key
111
- iv_filename: /etc/rails/.rails_old.iv
112
- cipher: aes-256-cbc
113
- encoding: base64strict
@@ -4,9 +4,9 @@
4
4
  ---
5
5
  # For the development and test environments the test symmetric encryption keys
6
6
  # can be placed directly in the source code.
7
- # And therefore no RSA private key is required
7
+ # And therefore no key encryption key is required
8
8
  development: &development_defaults
9
- key: 1234567890ABCDEF1234567890ABCDEF
9
+ key: 1234567890ABCDEF
10
10
  iv: 1234567890ABCDEF
11
11
  cipher_name: aes-128-cbc
12
12
  encoding: :base64strict
@@ -16,17 +16,15 @@ test:
16
16
  <<: *development_defaults
17
17
 
18
18
  release:
19
- # Since the key to encrypt and decrypt with must NOT be stored along with the
20
- # source code, we only hold a RSA key that is used to unlock the file
21
- # containing the actual symmetric encryption key
19
+ # Since the encryption key must NOT be stored along with the
20
+ # source code, only store the key encryption key here.
22
21
  private_rsa_key: |
23
- <%= OpenSSL::PKey::RSA.generate(2048).to_s.each_line.collect { |line| " #{line}" }.join('') %>
22
+ <%= SymmetricEncryption::KeyEncryptionKey.generate.each_line.collect { |line| " #{line}" }.join('') %>
24
23
 
25
24
  # List Symmetric Key files in the order of current / latest first
26
25
  ciphers:
27
26
  -
28
- # Filename containing Symmetric Encryption Key encrypted using the
29
- # RSA public key derived from the private key above
27
+ # Name of the file containing the encrypted key and iv.
30
28
  key_filename: <%= File.join(key_path, "#{app_name}_release.key") %>
31
29
  iv_filename: <%= File.join(key_path, "#{app_name}_release.iv") %>
32
30
  cipher_name: aes-256-cbc
@@ -35,17 +33,15 @@ release:
35
33
  always_add_header: true
36
34
 
37
35
  production:
38
- # Since the key to encrypt and decrypt with must NOT be stored along with the
39
- # source code, we only hold a RSA key that is used to unlock the file
40
- # containing the actual symmetric encryption key
36
+ # Since the encryption key must NOT be stored along with the
37
+ # source code, only store the key encryption key here.
41
38
  private_rsa_key: |
42
- <%= OpenSSL::PKey::RSA.generate(2048).to_s.each_line.collect { |line| " #{line}" }.join('') %>
39
+ <%= SymmetricEncryption::KeyEncryptionKey.generate.each_line.collect { |line| " #{line}" }.join('') %>
43
40
 
44
41
  # List Symmetric Key files in the order of current / latest first
45
42
  ciphers:
46
43
  -
47
- # Filename containing Symmetric Encryption Key encrypted using the
48
- # RSA public key derived from the private key above
44
+ # Name of the file containing the encrypted key and iv.
49
45
  key_filename: <%= File.join(key_path, "#{app_name}_production.key") %>
50
46
  iv_filename: <%= File.join(key_path, "#{app_name}_production.iv") %>
51
47
  cipher_name: aes-256-cbc
@@ -4,9 +4,9 @@
4
4
  ---
5
5
  # For the development and test environments the test symmetric encryption keys
6
6
  # can be placed directly in the source code.
7
- # And therefore no RSA private key is required
7
+ # And therefore no key encryption key is required
8
8
  development: &development_defaults
9
- key: 1234567890ABCDEF1234567890ABCDEF
9
+ key: 1234567890ABCDEF
10
10
  iv: 1234567890ABCDEF
11
11
  cipher_name: aes-128-cbc
12
12
  encoding: :base64strict
@@ -15,60 +15,63 @@ test:
15
15
  <<: *development_defaults
16
16
 
17
17
  <%
18
- cipher_name = 'aes-256-cbc'
19
- rsa_key = OpenSSL::PKey::RSA.generate(2048)
20
- key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name)
21
- iv = ::Base64.strict_encode64(key_pair[:iv])
22
- encrypted_key = ::Base64.strict_encode64(rsa_key.public_encrypt(key_pair[:key]))
18
+ rsa_key = SymmetricEncryption::KeyEncryptionKey.generate
19
+ cipher_conf = SymmetricEncryption::Cipher.generate_random_keys(private_rsa_key: rsa_key, encrypted_key: '', encrypted_iv: '')
20
+ cipher_name = cipher_conf[:cipher_name]
21
+ encrypted_iv = cipher_conf[:encrypted_iv]
22
+ encrypted_key = cipher_conf[:encrypted_key]
23
23
 
24
24
  puts "\n\n********************************************************************************"
25
25
  puts "Add the release environment key to Heroku: (Optional)\n\n"
26
26
  puts " heroku config:add RELEASE_KEY1=#{encrypted_key}\n\n"
27
+ puts "\n\n********************************************************************************"
28
+ puts "Add the release environment key to Heroku: (Optional)\n\n"
29
+ puts " heroku config:add RELEASE_IV1=#{encrypted_iv}\n\n"
27
30
  -%>
28
31
  release:
29
- # Since the key to encrypt and decrypt with must NOT be stored along with the
30
- # source code, we only hold a RSA key that is used to unlock the file
31
- # containing the actual symmetric encryption key
32
+ # Key encryption key
33
+ # Key used to secure the encryption key when it is stored in a file or encrypted.
32
34
  private_rsa_key: |
33
- <%= rsa_key.to_s.each_line.collect { |line| " #{line}" }.join('') %>
35
+ <%= rsa_key.each_line.collect { |line| " #{line}" }.join('') %>
34
36
 
35
37
  # List Symmetric Key files in the order of current / latest first
36
38
  ciphers:
37
39
  -
38
40
  # Filename containing Symmetric Encryption Key encrypted using the
39
- # RSA public key derived from the private key above
41
+ # key encryption key above (private_rsa_key).
40
42
  encrypted_key: "<%= '<' + "%= ENV['RELEASE_KEY1'] %" + '>' %>"
41
- iv: "<%= iv %>"
43
+ encrypted_iv: "<%= '<' + "%= ENV['RELEASE_IV1'] %" + '>' %>"
42
44
  cipher_name: <%= cipher_name %>
43
45
  encoding: :base64strict
44
46
  version: 1
45
47
  always_add_header: true
46
48
 
47
49
  <%
48
- cipher_name = 'aes-256-cbc'
49
- rsa_key = OpenSSL::PKey::RSA.generate(2048)
50
- key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name)
51
- iv = ::Base64.strict_encode64(key_pair[:iv])
52
- encrypted_key = ::Base64.strict_encode64(rsa_key.public_encrypt(key_pair[:key]))
50
+ rsa_key = SymmetricEncryption::KeyEncryptionKey.generate
51
+ cipher_conf = SymmetricEncryption::Cipher.generate_random_keys(private_rsa_key: rsa_key, encrypted_key: '', encrypted_iv: '')
52
+ cipher_name = cipher_conf[:cipher_name]
53
+ encrypted_iv = cipher_conf[:encrypted_iv]
54
+ encrypted_key = cipher_conf[:encrypted_key]
53
55
 
54
56
  puts "Add the production key to Heroku:\n\n"
55
57
  puts " heroku config:add PRODUCTION_KEY1=#{encrypted_key}\n\n"
56
58
  puts "********************************************************************************\n\n\n"
59
+ puts "Add the production key to Heroku:\n\n"
60
+ puts " heroku config:add PRODUCTION_IV1=#{encrypted_iv}\n\n"
61
+ puts "********************************************************************************\n\n\n"
57
62
  -%>
58
63
  production:
59
- # Since the key to encrypt and decrypt with must NOT be stored along with the
60
- # source code, we only hold a RSA key that is used to unlock the file
61
- # containing the actual symmetric encryption key
64
+ # Since the encryption key must NOT be stored along with the
65
+ # source code, only store the key encryption key here.
62
66
  private_rsa_key: |
63
- <%= rsa_key.to_s.each_line.collect { |line| " #{line}" }.join('') %>
67
+ <%= rsa_key.each_line.collect { |line| " #{line}" }.join('') %>
64
68
 
65
69
  # List Symmetric Key files in the order of current / latest first
66
70
  ciphers:
67
71
  -
68
- # Filename containing Symmetric Encryption Key encrypted using the
69
- # RSA public key derived from the private key above
72
+ # Encrypted key is supplied via an environment variable.
70
73
  encrypted_key: "<%= '<' + "%= ENV['PRODUCTION_KEY1'] %" + '>' %>"
71
- iv: "<%= iv %>"
74
+ encrypted_iv: "<%= '<' + "%= ENV['PRODUCTION_IV1'] %" + '>' %>"
72
75
  cipher_name: <%= cipher_name %>
73
76
  encoding: :base64strict
74
77
  version: 1
@@ -10,11 +10,16 @@ require 'symmetric_encryption/exception'
10
10
 
11
11
  #@formatter:off
12
12
  module SymmetricEncryption
13
- autoload :Coerce, 'symmetric_encryption/coerce'
14
- autoload :Config, 'symmetric_encryption/config'
15
- autoload :Reader, 'symmetric_encryption/reader'
16
- autoload :Writer, 'symmetric_encryption/writer'
17
- autoload :Generator, 'symmetric_encryption/generator'
13
+ autoload :Coerce, 'symmetric_encryption/coerce'
14
+ autoload :Config, 'symmetric_encryption/config'
15
+ autoload :Encoder, 'symmetric_encryption/encoder'
16
+ autoload :KeyEncryptionKey, 'symmetric_encryption/key_encryption_key'
17
+ autoload :Reader, 'symmetric_encryption/reader'
18
+ autoload :Writer, 'symmetric_encryption/writer'
19
+ autoload :Generator, 'symmetric_encryption/generator'
20
+ module Utils
21
+ autoload :ReEncryptConfigFiles, 'symmetric_encryption/re_encrypt_config_files'
22
+ end
18
23
  end
19
24
  #@formatter:on
20
25
 
@@ -25,4 +30,7 @@ if defined?(ActiveRecord::Base) && !defined?(AttrEncrypted::Version)
25
30
  end
26
31
  require 'symmetric_encryption/railties/symmetric_encryption_validator' if defined?(ActiveModel)
27
32
  require 'symmetric_encryption/extensions/mongoid/encrypted' if defined?(Mongoid)
28
- require 'symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key' if defined?(MongoMapper)
33
+ if defined?(MongoMapper)
34
+ warn 'MongoMapper support is deprecated. Consider upgrading to Mongoid.'
35
+ require 'symmetric_encryption/extensions/mongo_mapper/plugins/encrypted_key'
36
+ end
@@ -1,5 +1,5 @@
1
+ require 'openssl'
1
2
  module SymmetricEncryption
2
-
3
3
  # Hold all information related to encryption keys
4
4
  # as well as encrypt and decrypt data using those keys
5
5
  #
@@ -7,11 +7,9 @@ module SymmetricEncryption
7
7
  # threads at the same time without needing an instance of Cipher per thread
8
8
  class Cipher
9
9
  # Cipher to use for encryption and decryption
10
- attr_reader :cipher_name, :version, :iv
11
- attr_accessor :encoding, :always_add_header
12
-
13
- # Available encodings
14
- ENCODINGS = [:none, :base64, :base64strict, :base16]
10
+ attr_accessor :cipher_name, :version, :iv, :always_add_header
11
+ attr_reader :encoder, :encoding
12
+ attr_writer :key
15
13
 
16
14
  # Backward compatibility
17
15
  alias_method :cipher, :cipher_name
@@ -52,48 +50,104 @@ module SymmetricEncryption
52
50
  }
53
51
  end
54
52
 
55
- # Generate new randomized keys and generate key and iv files if supplied
56
- # Overwrites key files for the current environment
57
- # See: #initialize for parameters
58
- def self.generate_random_keys(params)
59
- environment = params[:environment]
60
- private_rsa_key = params[:private_rsa_key]
61
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
62
- key_pair = SymmetricEncryption::Cipher.random_key_pair(params[:cipher_name] || 'aes-256-cbc')
63
- key = key_pair[:key]
64
- iv = key_pair[:iv]
65
-
66
- puts 'Generated new Symmetric Key for encryption'
67
- if params.has_key?(:key)
68
- puts 'Put this value in your configuration file for :key'
69
- p key
70
- elsif file_name = params.delete(:key_filename)
71
- write_to_file(file_name, key, rsa)
72
- puts("Please copy #{file_name} to the other servers in #{environment}.")
73
- elsif params.has_key?(:encrypted_key)
74
- encrypted_key = encrypt_key(key, rsa)
75
- puts 'If running in Heroku, add the environment specific key:'
76
- puts "heroku config:add #{environment.upcase}_KEY1=#{encrypted_key}"
77
- puts
78
- puts 'Otherwise, set the :encrypted_key value to:'
79
- puts encrypted_key
53
+ # Generate new randomized keys and generate key and iv files if supplied.
54
+ # Overwrites key files for the current environment.
55
+ #
56
+ # Parameters
57
+ # :key_filename
58
+ # Name of file that will contain the symmetric key encrypted using the public
59
+ # key from the private_rsa_key.
60
+ # Or,
61
+ # :encrypted_key
62
+ # Symmetric key encrypted using the public key from the private_rsa_key
63
+ # and then Base64 encoded
64
+ #
65
+ # Note:
66
+ # If :key_filename and :encrypted_key are not supplied then a new :key will be returned.
67
+ # :key is the Symmetric Key to use for encryption and decryption.
68
+ #
69
+ #
70
+ # :iv_filename
71
+ # Name of file containing symmetric key initialization vector
72
+ # encrypted using the public key from the private_rsa_key
73
+ # Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
74
+ # Or,
75
+ # :encrypted_iv
76
+ # Initialization vector encrypted using the public key from the private_rsa_key
77
+ # and then Base64 encoded
78
+ # Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
79
+ #
80
+ # Note:
81
+ # If :iv_filename and :encrypted_iv are not supplied then a new :iv will be returned.
82
+ # :key is the Initialization Vector to use with Symmetric Key.
83
+ #
84
+ #
85
+ # private_rsa_key [String]
86
+ # Key encryption key.
87
+ # To generate a new one: SymmetricEncryption::KeyEncryptionKey.generate
88
+ # Required if :key_filename, :encrypted_key, :iv_filename, or :encrypted_iv is supplied
89
+ #
90
+ # :cipher_name [String]
91
+ # Encryption Cipher to use.
92
+ # Default: aes-256-cbc
93
+ #
94
+ # :encoding [Symbol]
95
+ # :base64strict
96
+ # Return as a base64 encoded string that does not include additional newlines
97
+ # This is the recommended format since newlines in the values to
98
+ # SQL queries are cumbersome. Also the newline reformatting is unnecessary
99
+ # It is not the default for backward compatibility
100
+ # :base64
101
+ # Return as a base64 encoded string
102
+ # :base16
103
+ # Return as a Hex encoded string
104
+ # :none
105
+ # Return as raw binary data string. Note: String can contain embedded nulls
106
+ # Default: :base64strict
107
+ def self.generate_random_keys(params = {})
108
+ params = params.dup
109
+ private_rsa_key = params.delete(:private_rsa_key)
110
+ cipher_name = params.delete(:cipher_name) || 'aes-256-cbc'
111
+ encoding = params.delete(:encoding) || :base64strict
112
+ unless private_rsa_key
113
+ [:key_filename, :encrypted_key, :iv_filename, :encrypted_iv].each do |key|
114
+ raise(SymmetricEncryption::ConfigError, "When :#{key} is supplied, :private_rsa_key is required.") if params.include?(key)
115
+ end
116
+ end
117
+
118
+ key_encryption_key = KeyEncryptionKey.new(private_rsa_key) if private_rsa_key
119
+ cipher_conf = {cipher_name: cipher_name, encoding: encoding}
120
+
121
+ key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name)
122
+ key = key_pair[:key]
123
+ iv = key_pair[:iv]
124
+
125
+ if file_name = params.delete(:key_filename)
126
+ cipher_conf[:key_filename] = file_name
127
+ encrypted_key = key_encryption_key.encrypt(key)
128
+ write_to_file(file_name, encrypted_key)
129
+ elsif params.delete(:encrypted_key)
130
+ encrypted_key = key_encryption_key.encrypt(key)
131
+ cipher_conf[:encrypted_key] = SymmetricEncryption::Encoder[encoding].encode(encrypted_key)
132
+ else
133
+ params.delete(:key)
134
+ cipher_conf[:key] = SymmetricEncryption::Encoder[encoding].encode(key.to_s)
80
135
  end
81
136
 
82
- puts 'Generated new Initialization Vector for encryption'
83
- if params.has_key?(:iv)
84
- puts 'Put this value in your configuration file for :iv'
85
- p iv
86
- elsif file_name = params.delete(:iv_filename)
87
- write_to_file(file_name, iv, rsa)
88
- puts("Please copy #{file_name} to the other servers in #{environment}.")
89
- elsif params.has_key?(:encrypted_iv)
90
- encrypted_iv = encrypt_key(iv, rsa)
91
- puts 'If running in Heroku, add the environment specific key:'
92
- puts "heroku config:add #{environment.upcase}_KEY1=#{encrypted_iv}"
93
- puts
94
- puts 'Otherwise, set the :encrypted_iv value to:'
95
- puts encrypted_iv
137
+ if file_name = params.delete(:iv_filename)
138
+ cipher_conf[:iv_filename] = file_name
139
+ encrypted_iv = key_encryption_key.encrypt(iv)
140
+ write_to_file(file_name, encrypted_iv)
141
+ elsif params.delete(:encrypted_iv)
142
+ encrypted_iv = key_encryption_key.encrypt(iv)
143
+ cipher_conf[:encrypted_iv] = SymmetricEncryption::Encoder[encoding].encode(encrypted_iv)
144
+ else
145
+ params.delete(:iv)
146
+ cipher_conf[:iv] = SymmetricEncryption::Encoder[encoding].encode(iv.to_s)
96
147
  end
148
+
149
+ raise(ArgumentError, "SymmetricEncryption::Cipher Invalid options #{params.inspect}") if params.size > 0
150
+ cipher_conf
97
151
  end
98
152
 
99
153
  # Create a Symmetric::Key for encryption and decryption purposes
@@ -114,15 +168,15 @@ module SymmetricEncryption
114
168
  # Optional. The Initialization Vector to use with Symmetric Key
115
169
  # Highly Recommended as it is the input into the CBC algorithm
116
170
  # Or,
117
- # Note: The following 2 options are deprecated since it is _not_ necessary
118
- # to encrypt the initialization vector (IV)
119
171
  # :iv_filename
120
172
  # Name of file containing symmetric key initialization vector
121
173
  # encrypted using the public key from the private_rsa_key
174
+ # Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
122
175
  # Or,
123
176
  # :encrypted_iv
124
177
  # Initialization vector encrypted using the public key from the private_rsa_key
125
178
  # and then Base64 encoded
179
+ # Deprecated: It is _not_ necessary to encrypt the initialization vector (IV)
126
180
  #
127
181
  # :cipher_name [String]
128
182
  # Optional. Encryption Cipher to use
@@ -157,41 +211,57 @@ module SymmetricEncryption
157
211
  # Recommended: true
158
212
  #
159
213
  # private_rsa_key [String]
160
- # RSA Key used to decrypt key and iv as applicable
161
- # Mandatory if :key_filename, :encrypted_key, :iv_filename, or :encrypted_iv is supplied
214
+ # Key encryption key.
215
+ # To generate a new one: SymmetricEncryption::KeyEncryptionKey.generate
216
+ # Required if :key_filename, :encrypted_key, :iv_filename, or :encrypted_iv is supplied
162
217
  def initialize(params={})
163
218
  params = params.dup
164
219
  @cipher_name = params.delete(:cipher_name) || params.delete(:cipher) || 'aes-256-cbc'
165
220
  @version = params.delete(:version)
166
221
  @always_add_header = params.delete(:always_add_header) || false
167
- @encoding = (params.delete(:encoding) || :base64).to_sym
168
-
169
- # To decrypt encrypted key or iv files
222
+ self.encoding = (params.delete(:encoding) || :base64).to_sym
170
223
  private_rsa_key = params.delete(:private_rsa_key)
171
- rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
172
-
173
- if key = params.delete(:key)
174
- @key = key
175
- elsif file_name = params.delete(:key_filename)
176
- @key = read_from_file(file_name, rsa)
177
- elsif encrypted_key = params.delete(:encrypted_key)
178
- @key = decrypt_key(encrypted_key, rsa)
224
+ unless private_rsa_key
225
+ [:key_filename, :encrypted_key, :iv_filename, :encrypted_iv].each do |key|
226
+ raise(SymmetricEncryption::ConfigError, "When :#{key} is supplied, :private_rsa_key is required.") if params.include?(key)
227
+ end
179
228
  end
180
229
 
181
- if iv = params.delete(:iv)
182
- @iv = iv
183
- elsif file_name = params.delete(:iv_filename)
184
- @iv = read_from_file(file_name, rsa)
185
- elsif encrypted_iv = params.delete(:encrypted_iv)
186
- @iv = decrypt_key(encrypted_iv, rsa)
187
- end
230
+ key_encryption_key = KeyEncryptionKey.new(private_rsa_key) if private_rsa_key
231
+ @key =
232
+ if key = params.delete(:key)
233
+ key
234
+ elsif file_name = params.delete(:key_filename)
235
+ encrypted_key = self.class.read_from_file(file_name)
236
+ key_encryption_key.decrypt(encrypted_key)
237
+ elsif encrypted_key = params.delete(:encrypted_key)
238
+ binary = self.encoder.decode(encrypted_key)
239
+ key_encryption_key.decrypt(binary)
240
+ else
241
+ raise(ArgumentError, 'Missing mandatory parameter :key, :key_filename, or :encrypted_key')
242
+ end
243
+
244
+ @iv =
245
+ if iv = params.delete(:iv)
246
+ iv
247
+ elsif file_name = params.delete(:iv_filename)
248
+ encrypted_iv = self.class.read_from_file(file_name)
249
+ key_encryption_key.decrypt(encrypted_iv)
250
+ elsif encrypted_iv = params.delete(:encrypted_iv)
251
+ binary = self.encoder.decode(encrypted_iv)
252
+ key_encryption_key.decrypt(binary)
253
+ end
188
254
 
189
- raise(ArgumentError, 'Missing mandatory parameter :key, :key_filename, or :encrypted_key') unless @key
190
- raise(ArgumentError, "Invalid Encoding: #{@encoding}") unless ENCODINGS.include?(@encoding)
191
255
  raise(ArgumentError, "Cipher version has a valid range of 0 to 255. #{@version} is too high, or negative") if (@version.to_i > 255) || (@version.to_i < 0)
192
256
  raise(ArgumentError, "SymmetricEncryption::Cipher Invalid options #{params.inspect}") if params.size > 0
193
257
  end
194
258
 
259
+ # Change the encoding
260
+ def encoding=(encoding)
261
+ @encoder = SymmetricEncryption::Encoder[encoding]
262
+ @encoding = encoding
263
+ end
264
+
195
265
  # Encrypt and then encode a string
196
266
  #
197
267
  # Returns data encrypted and then encoded according to the encoding setting
@@ -277,21 +347,7 @@ module SymmetricEncryption
277
347
  # Returned string is UTF8 encoded except for encoding :none
278
348
  def encode(binary_string)
279
349
  return binary_string if binary_string.nil? || (binary_string == '')
280
-
281
- # Now encode data based on encoding setting
282
- case encoding
283
- when :base64
284
- encoded_string = ::Base64.encode64(binary_string)
285
- encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
286
- when :base64strict
287
- encoded_string = ::Base64.encode64(binary_string).gsub(/\n/, '')
288
- encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
289
- when :base16
290
- encoded_string = binary_string.to_s.unpack('H*').first
291
- encoded_string.force_encoding(SymmetricEncryption::UTF8_ENCODING)
292
- else
293
- binary_string
294
- end
350
+ encoder.encode(binary_string)
295
351
  end
296
352
 
297
353
  # Decode the supplied string using the encoding in this cipher instance
@@ -300,17 +356,7 @@ module SymmetricEncryption
300
356
  # Returned string is Binary encoded
301
357
  def decode(encoded_string)
302
358
  return encoded_string if encoded_string.nil? || (encoded_string == '')
303
-
304
- case encoding
305
- when :base64, :base64strict
306
- decoded_string = ::Base64.decode64(encoded_string)
307
- decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
308
- when :base16
309
- decoded_string = [encoded_string].pack('H*')
310
- decoded_string.force_encoding(SymmetricEncryption::BINARY_ENCODING)
311
- else
312
- encoded_string
313
- end
359
+ encoder.decode(encoded_string)
314
360
  end
315
361
 
316
362
  # Return a new random key using the configured cipher_name
@@ -548,44 +594,19 @@ module SymmetricEncryption
548
594
 
549
595
  attr_reader :key
550
596
 
551
- # Read the encrypted key from file
552
- def read_from_file(file_name, rsa)
553
- raise(SymmetricEncryption::ConfigError, 'Missing mandatory config parameter :private_rsa_key when filename key is used') unless rsa
554
- begin
555
- encrypted_key = File.open(file_name, 'rb') { |f| f.read }
556
- rsa.private_decrypt(encrypted_key)
557
- rescue Errno::ENOENT
558
- puts "\nSymmetric Encryption key file: '#{file_name}' not found or readable."
559
- puts "To generate the keys for the first time run: bin/rails generate symmetric_encryption:new_keys production\n\n"
560
- end
597
+ # Read from the file, raising an exception if it is not found
598
+ def self.read_from_file(file_name)
599
+ File.open(file_name, 'rb') { |f| f.read }
600
+ rescue Errno::ENOENT => exc
601
+ puts "\nSymmetric Encryption key file: '#{file_name}' not found or readable."
602
+ puts "To generate the keys for the first time run: bin/rails generate symmetric_encryption:new_keys production\n\n"
603
+ raise(exc)
561
604
  end
562
605
 
563
- # Save symmetric key after encrypting it with the private RSA key
564
- # Backing up existing files if present
565
- def self.write_to_file(file_name, key, rsa)
566
- raise(SymmetricEncryption::ConfigError, 'Missing mandatory config parameter :private_rsa_key when filename key is used') unless rsa
606
+ # Write to the supplied filename, backing up the existing file if present
607
+ def self.write_to_file(file_name, data)
567
608
  File.rename(file_name, "#{file_name}.#{Time.now.to_i}") if File.exist?(file_name)
568
- File.open(file_name, 'wb') { |file| file.write(rsa.public_encrypt(key)) }
569
- end
570
-
571
- # Read the encrypted key from file
572
- def decrypt_key(encrypted_key, rsa)
573
- raise(SymmetricEncryption::ConfigError, 'Missing mandatory config parameter :private_rsa_key when encrypted key is supplied') unless rsa
574
-
575
- # Decode value first using encoding specified
576
- encrypted_key = ::Base64.decode64(encrypted_key)
577
- if !encrypted_key || encrypted_key.empty?
578
- puts "\nSymmetric Encryption encrypted_key not found."
579
- puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
580
- else
581
- rsa.private_decrypt(encrypted_key)
582
- end
583
- end
584
-
585
- # Returns [String] encrypted form of supplied key
586
- def encrypt_key(key, rsa)
587
- raise(SymmetricEncryption::ConfigError, 'Missing mandatory config parameter :private_rsa_key when encrypted key is supplied') unless rsa
588
- ::Base64.encode64(rsa.public_encrypt(key))
609
+ File.open(file_name, 'wb') { |file| file.write(data) }
589
610
  end
590
611
 
591
612
  end