symmetric-encryption 3.7.2 → 3.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +65 -83
- data/Rakefile +4 -4
- data/lib/rails/generators/symmetric_encryption/config/config_generator.rb +3 -3
- data/lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb +3 -3
- data/lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb +2 -2
- data/lib/symmetric_encryption.rb +7 -1
- data/lib/symmetric_encryption/cipher.rb +180 -50
- data/lib/symmetric_encryption/coerce.rb +75 -0
- data/lib/symmetric_encryption/config.rb +88 -0
- data/lib/symmetric_encryption/extensions/active_record/base.rb +2 -2
- data/lib/symmetric_encryption/extensions/mongoid/encrypted.rb +2 -2
- data/lib/symmetric_encryption/generator.rb +5 -1
- data/lib/symmetric_encryption/railtie.rb +3 -3
- data/lib/symmetric_encryption/railties/symmetric_encryption.rake +6 -6
- data/lib/symmetric_encryption/railties/symmetric_encryption_validator.rb +1 -1
- data/lib/symmetric_encryption/reader.rb +16 -14
- data/lib/symmetric_encryption/symmetric_encryption.rb +30 -285
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +13 -13
- data/test/active_record_test.rb +126 -73
- data/test/cipher_test.rb +42 -42
- data/test/mongo_mapper_test.rb +171 -114
- data/test/mongoid_test.rb +173 -115
- data/test/reader_test.rb +63 -63
- data/test/symmetric_encryption_test.rb +81 -80
- data/test/test_db.sqlite3 +0 -0
- data/test/test_helper.rb +1 -2
- data/test/writer_test.rb +20 -20
- metadata +13 -13
- data/lib/_test_empty +0 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
# For coercing data types to from strings
|
3
|
+
module Coerce
|
4
|
+
TYPE_MAP = {
|
5
|
+
string: String,
|
6
|
+
integer: Integer,
|
7
|
+
float: Float,
|
8
|
+
decimal: BigDecimal,
|
9
|
+
datetime: DateTime,
|
10
|
+
time: Time,
|
11
|
+
date: Date
|
12
|
+
}
|
13
|
+
|
14
|
+
# Coerce given value into given type
|
15
|
+
# Does not coerce json or yaml values
|
16
|
+
def self.coerce(value, type, from_type=nil)
|
17
|
+
return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
|
18
|
+
|
19
|
+
from_type ||= value.class
|
20
|
+
case type
|
21
|
+
when :json
|
22
|
+
value
|
23
|
+
when :yaml
|
24
|
+
value
|
25
|
+
else
|
26
|
+
coercer = Coercible::Coercer.new
|
27
|
+
coercer[from_type].send("to_#{type}".to_sym, value)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Uses coercible gem to coerce values from strings into the target type
|
32
|
+
# Note: if the type is :string, then the value is returned as is, and the
|
33
|
+
# coercible gem is not used at all.
|
34
|
+
def self.coerce_from_string(value, type)
|
35
|
+
return if value.nil?
|
36
|
+
case type
|
37
|
+
when :string
|
38
|
+
value
|
39
|
+
when :json
|
40
|
+
JSON.load(value)
|
41
|
+
when :yaml
|
42
|
+
YAML.load(value)
|
43
|
+
else
|
44
|
+
self.coerce(value, type, String)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Uses coercible gem to coerce values to strings from the specified type
|
49
|
+
# Note: if the type is :string, and value is not nil, then #to_s is called
|
50
|
+
# on the value and the coercible gem is not used at all.
|
51
|
+
def self.coerce_to_string(value, type)
|
52
|
+
return if value.nil?
|
53
|
+
|
54
|
+
case type
|
55
|
+
when :string
|
56
|
+
value.to_s
|
57
|
+
when :json
|
58
|
+
value.to_json
|
59
|
+
when :yaml
|
60
|
+
value.to_yaml
|
61
|
+
else
|
62
|
+
self.coerce(value, :string, coercion_type(type, value))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the correct coercion type to use for the specified symbol and value
|
67
|
+
def self.coercion_type(symbol, value)
|
68
|
+
if symbol == :boolean
|
69
|
+
value.class
|
70
|
+
else
|
71
|
+
TYPE_MAP[symbol]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
module Config
|
3
|
+
# Load the Encryption Configuration from a YAML file
|
4
|
+
# filename:
|
5
|
+
# Name of file to read.
|
6
|
+
# Mandatory for non-Rails apps
|
7
|
+
# Default: Rails.root/config/symmetric-encryption.yml
|
8
|
+
# environment:
|
9
|
+
# Which environments config to load. Usually: production, development, etc.
|
10
|
+
# Default: Rails.env
|
11
|
+
def self.load!(filename=nil, environment=nil)
|
12
|
+
config = read_config(filename, environment)
|
13
|
+
ciphers = extract_ciphers(config)
|
14
|
+
|
15
|
+
SymmetricEncryption.cipher = ciphers.shift
|
16
|
+
SymmetricEncryption.secondary_ciphers = ciphers
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Returns [Hash] the configuration for the supplied environment
|
23
|
+
def self.read_config(filename=nil, environment=nil)
|
24
|
+
config_filename = filename || File.join(Rails.root, 'config', 'symmetric-encryption.yml')
|
25
|
+
cfg = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
|
26
|
+
extract_config(cfg)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns [ private_rsa_key, ciphers ] config
|
30
|
+
def self.extract_config(config)
|
31
|
+
config = deep_symbolize_keys(config)
|
32
|
+
|
33
|
+
# Old format?
|
34
|
+
unless config.has_key?(:ciphers)
|
35
|
+
config = {
|
36
|
+
private_rsa_key: config.delete(:private_rsa_key),
|
37
|
+
ciphers: [config]
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
# Old format cipher name?
|
42
|
+
config[:ciphers] = config[:ciphers].collect do |cipher|
|
43
|
+
if old_key_name_cipher = cipher.delete(:cipher)
|
44
|
+
cipher[:cipher_name] = old_key_name_cipher
|
45
|
+
end
|
46
|
+
cipher
|
47
|
+
end
|
48
|
+
config
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
|
52
|
+
#
|
53
|
+
# Read the configuration from the YAML file and return in the latest format
|
54
|
+
#
|
55
|
+
# filename:
|
56
|
+
# Name of file to read.
|
57
|
+
# Mandatory for non-Rails apps
|
58
|
+
# Default: Rails.root/config/symmetric-encryption.yml
|
59
|
+
# environment:
|
60
|
+
# Which environments config to load. Usually: production, development, etc.
|
61
|
+
def self.extract_ciphers(config)
|
62
|
+
# RSA key to decrypt key files
|
63
|
+
private_rsa_key = config[:private_rsa_key]
|
64
|
+
|
65
|
+
config[:ciphers].collect do |cipher_config|
|
66
|
+
Cipher.new({private_rsa_key: private_rsa_key}.merge(cipher_config))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Iterate through the Hash symbolizing all keys
|
71
|
+
def self.deep_symbolize_keys(x)
|
72
|
+
case x
|
73
|
+
when Hash
|
74
|
+
result = {}
|
75
|
+
x.each_pair do |key, value|
|
76
|
+
key = key.to_sym if key.is_a?(String)
|
77
|
+
result[key] = deep_symbolize_keys(value)
|
78
|
+
end
|
79
|
+
result
|
80
|
+
when Array
|
81
|
+
x.collect { |i| deep_symbolize_keys(i) }
|
82
|
+
else
|
83
|
+
x
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -41,7 +41,7 @@ module ActiveRecord #:nodoc:
|
|
41
41
|
# Ignore failures since the table may not yet actually exist
|
42
42
|
define_attribute_methods rescue nil
|
43
43
|
|
44
|
-
options
|
44
|
+
options = params.last.is_a?(Hash) ? params.pop.dup : {}
|
45
45
|
|
46
46
|
params.each do |attribute|
|
47
47
|
SymmetricEncryption::Generator.generate_decrypted_accessors(self, attribute, "encrypted_#{attribute}", options)
|
@@ -129,7 +129,7 @@ module ActiveRecord #:nodoc:
|
|
129
129
|
attribute_names.each_with_index do |attribute, index|
|
130
130
|
encrypted_name = "encrypted_#{attribute}"
|
131
131
|
if method_defined? encrypted_name.to_sym
|
132
|
-
args[index]
|
132
|
+
args[index] = ::SymmetricEncryption.encrypt(args[index])
|
133
133
|
attribute_names[index] = encrypted_name
|
134
134
|
end
|
135
135
|
end
|
@@ -10,7 +10,7 @@
|
|
10
10
|
# Mongoid.load!('config/mongoid.yml')
|
11
11
|
#
|
12
12
|
# # Initialize SymmetricEncryption in a standalone environment. In a Rails app this is not required
|
13
|
-
# SymmetricEncryption.load!('config/symmetric-encryption.yml', 'test')
|
13
|
+
# SymmetricEncryption::Config.load!('config/symmetric-encryption.yml', 'test')
|
14
14
|
#
|
15
15
|
# class Person
|
16
16
|
# include Mongoid::Document
|
@@ -89,7 +89,7 @@
|
|
89
89
|
# @return [ Field ] The generated field
|
90
90
|
Mongoid::Fields.option :encrypted do |model, field, options|
|
91
91
|
if options != false
|
92
|
-
options
|
92
|
+
options = options.is_a?(Hash) ? options.dup : {}
|
93
93
|
encrypted_field_name = field.name
|
94
94
|
|
95
95
|
# Support overriding the name of the decrypted attribute
|
@@ -24,7 +24,7 @@ module SymmetricEncryption
|
|
24
24
|
# Also updates the encrypted field with the encrypted value
|
25
25
|
# Freeze the decrypted field value so that it is not modified directly
|
26
26
|
def #{decrypted_name}=(value)
|
27
|
-
v = SymmetricEncryption::coerce(value, :#{type})
|
27
|
+
v = SymmetricEncryption::Coerce.coerce(value, :#{type})
|
28
28
|
self.#{encrypted_name} = @stored_#{encrypted_name} = ::SymmetricEncryption.encrypt(v,#{random_iv},#{compress},:#{type})
|
29
29
|
@#{decrypted_name} = v.freeze
|
30
30
|
end
|
@@ -40,6 +40,10 @@ module SymmetricEncryption
|
|
40
40
|
@#{decrypted_name}
|
41
41
|
end
|
42
42
|
|
43
|
+
# Map changes to encrypted value to unencrypted equivalent
|
44
|
+
def #{decrypted_name}_changed?
|
45
|
+
#{encrypted_name}_changed?
|
46
|
+
end
|
43
47
|
EOS
|
44
48
|
end
|
45
49
|
end
|
@@ -17,7 +17,7 @@ module SymmetricEncryption #:nodoc:
|
|
17
17
|
config.symmetric_encryption = ::SymmetricEncryption
|
18
18
|
|
19
19
|
rake_tasks do
|
20
|
-
load
|
20
|
+
load 'symmetric_encryption/railties/symmetric_encryption.rake'
|
21
21
|
end
|
22
22
|
|
23
23
|
# Initialize Symmetry. This will look for a symmetry.yml in the config
|
@@ -35,9 +35,9 @@ module SymmetricEncryption #:nodoc:
|
|
35
35
|
config.before_configuration do
|
36
36
|
# Check if already configured
|
37
37
|
unless ::SymmetricEncryption.cipher?
|
38
|
-
config_file = Rails.root.join(
|
38
|
+
config_file = Rails.root.join('config', 'symmetric-encryption.yml')
|
39
39
|
if config_file.file?
|
40
|
-
::SymmetricEncryption.load!(config_file, Rails.env)
|
40
|
+
::SymmetricEncryption::Config.load!(config_file, Rails.env)
|
41
41
|
else
|
42
42
|
puts "\nSymmetric Encryption config not found."
|
43
43
|
puts "To generate one for the first time: rails generate symmetric_encryption:config\n\n"
|
@@ -17,11 +17,11 @@ namespace :symmetric_encryption do
|
|
17
17
|
password2 = 0
|
18
18
|
|
19
19
|
while password1 != password2
|
20
|
-
password1 = HighLine.new.ask(
|
21
|
-
password2 = HighLine.new.ask(
|
20
|
+
password1 = HighLine.new.ask('Enter the value to encrypt:') { |q| q.echo = '*' }
|
21
|
+
password2 = HighLine.new.ask('Re-enter the value to encrypt:') { |q| q.echo = '*' }
|
22
22
|
|
23
23
|
if (password1 != password2)
|
24
|
-
puts
|
24
|
+
puts 'Passwords do not match, please try again'
|
25
25
|
end
|
26
26
|
end
|
27
27
|
puts "\nEncrypted: #{SymmetricEncryption.encrypt(password1)}\n\n"
|
@@ -51,7 +51,7 @@ namespace :symmetric_encryption do
|
|
51
51
|
end
|
52
52
|
puts "\n#{output_filename} now contains the decrypted contents of #{input_filename}\n\n"
|
53
53
|
else
|
54
|
-
puts
|
54
|
+
puts 'Missing input and/or output filename. Usage:'
|
55
55
|
puts ' INFILE="encrypted_filename" OUTFILE="filename" rake symmetric_encryption:decrypt_file'
|
56
56
|
end
|
57
57
|
end
|
@@ -74,9 +74,9 @@ namespace :symmetric_encryption do
|
|
74
74
|
end
|
75
75
|
puts "\n#{output_filename} now contains the encrypted #{"and compressed " if compress}contents of #{input_filename}\n\n"
|
76
76
|
else
|
77
|
-
puts
|
77
|
+
puts 'Missing input and/or output filename. Usage:'
|
78
78
|
puts ' INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
|
79
|
-
puts
|
79
|
+
puts 'To compress the file before encrypting:'
|
80
80
|
puts ' COMPRESS=1 INFILE="filename" OUTFILE="encrypted_filename" rake symmetric_encryption:encrypt_file'
|
81
81
|
end
|
82
82
|
end
|
@@ -13,6 +13,6 @@
|
|
13
13
|
# # => true
|
14
14
|
class SymmetricEncryptionValidator < ActiveModel::EachValidator
|
15
15
|
def validate_each(record, attribute, value)
|
16
|
-
record.errors.add(attribute,
|
16
|
+
record.errors.add(attribute, 'must be a value encrypted using SymmetricEncryption.encrypt') unless SymmetricEncryption.encrypted?(value)
|
17
17
|
end
|
18
18
|
end
|
@@ -94,12 +94,12 @@ module SymmetricEncryption
|
|
94
94
|
# Returns [true|false] whether the file or stream contains any data
|
95
95
|
# excluding the header should it have one
|
96
96
|
def self.empty?(filename_or_stream)
|
97
|
-
open(filename_or_stream) {|file| file.eof? }
|
97
|
+
open(filename_or_stream) { |file| file.eof? }
|
98
98
|
end
|
99
99
|
|
100
100
|
# Returns [true|false] whether the file contains the encryption header
|
101
101
|
def self.header_present?(filename)
|
102
|
-
::File.open(filename, 'rb') {|file| new(file).header_present?}
|
102
|
+
::File.open(filename, 'rb') { |file| new(file).header_present? }
|
103
103
|
end
|
104
104
|
|
105
105
|
# After opening a file Returns [true|false] whether the file being
|
@@ -109,7 +109,7 @@ module SymmetricEncryption
|
|
109
109
|
end
|
110
110
|
|
111
111
|
# Decrypt data before reading from the supplied stream
|
112
|
-
def initialize(ios,options={})
|
112
|
+
def initialize(ios, options={})
|
113
113
|
@ios = ios
|
114
114
|
@buffer_size = options.fetch(:buffer_size, 4096).to_i
|
115
115
|
@version = options[:version]
|
@@ -193,12 +193,12 @@ module SymmetricEncryption
|
|
193
193
|
elsif @read_buffer.length > length
|
194
194
|
data = @read_buffer.slice!(0..length-1)
|
195
195
|
else
|
196
|
-
data
|
196
|
+
data = @read_buffer
|
197
197
|
@read_buffer = ''
|
198
198
|
end
|
199
199
|
else
|
200
200
|
# Capture anything already in the buffer
|
201
|
-
data
|
201
|
+
data = @read_buffer
|
202
202
|
@read_buffer = ''
|
203
203
|
|
204
204
|
if !@ios.eof?
|
@@ -216,14 +216,14 @@ module SymmetricEncryption
|
|
216
216
|
# Raises EOFError on eof
|
217
217
|
# The stream must be opened for reading or an IOError will be raised.
|
218
218
|
def readline(sep_string = "\n")
|
219
|
-
gets(sep_string) || raise(EOFError.new(
|
219
|
+
gets(sep_string) || raise(EOFError.new('End of file reached when trying to read a line'))
|
220
220
|
end
|
221
221
|
|
222
222
|
# Reads a single decrypted line from the file up to and including the optional sep_string.
|
223
223
|
# A sep_string of nil reads the entire contents of the file
|
224
224
|
# Returns nil on eof
|
225
225
|
# The stream must be opened for reading or an IOError will be raised.
|
226
|
-
def gets(sep_string,length=nil)
|
226
|
+
def gets(sep_string, length=nil)
|
227
227
|
return read(length) if sep_string.nil?
|
228
228
|
|
229
229
|
# Read more data until we get the sep_string
|
@@ -232,8 +232,8 @@ module SymmetricEncryption
|
|
232
232
|
read_block
|
233
233
|
end
|
234
234
|
index ||= -1
|
235
|
-
data
|
236
|
-
@pos
|
235
|
+
data = @read_buffer.slice!(0..index)
|
236
|
+
@pos += data.length
|
237
237
|
return nil if data.length == 0 && eof?
|
238
238
|
data
|
239
239
|
end
|
@@ -300,7 +300,7 @@ module SymmetricEncryption
|
|
300
300
|
size = 0
|
301
301
|
while !eof
|
302
302
|
read_block
|
303
|
-
size
|
303
|
+
size += @read_buffer.size
|
304
304
|
@read_buffer = ''
|
305
305
|
end
|
306
306
|
rewind
|
@@ -316,13 +316,15 @@ module SymmetricEncryption
|
|
316
316
|
|
317
317
|
# Read the header from the file if present
|
318
318
|
def read_header
|
319
|
-
@pos
|
319
|
+
@pos = 0
|
320
320
|
|
321
321
|
# Read first block and check for the header
|
322
|
-
buf
|
322
|
+
buf = @ios.read(@buffer_size)
|
323
323
|
|
324
324
|
# Use cipher specified in header, or global cipher if it has no header
|
325
|
-
iv, key
|
325
|
+
iv, key = nil
|
326
|
+
cipher_name = nil
|
327
|
+
decryption_cipher = nil
|
326
328
|
if header = SymmetricEncryption::Cipher.parse_header!(buf)
|
327
329
|
@header_present = true
|
328
330
|
@compressed = header.compressed
|
@@ -340,7 +342,7 @@ module SymmetricEncryption
|
|
340
342
|
@stream_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
341
343
|
@stream_cipher.decrypt
|
342
344
|
@stream_cipher.key = key || decryption_cipher.send(:key)
|
343
|
-
@stream_cipher.iv
|
345
|
+
@stream_cipher.iv = iv || decryption_cipher.iv
|
344
346
|
|
345
347
|
# First call to #update should return an empty string anyway
|
346
348
|
if buf && buf.length > 0
|
@@ -10,9 +10,9 @@ require 'erb'
|
|
10
10
|
module SymmetricEncryption
|
11
11
|
|
12
12
|
# Defaults
|
13
|
-
@@cipher
|
13
|
+
@@cipher = nil
|
14
14
|
@@secondary_ciphers = []
|
15
|
-
@@select_cipher
|
15
|
+
@@select_cipher = nil
|
16
16
|
|
17
17
|
# List of types supported when encrypting or decrypting data
|
18
18
|
#
|
@@ -26,7 +26,7 @@ module SymmetricEncryption
|
|
26
26
|
# :date => Date
|
27
27
|
# :json => Uses JSON serialization, useful for hashes and arrays
|
28
28
|
# :yaml => Uses YAML serialization, useful for hashes and arrays
|
29
|
-
COERCION_TYPES
|
29
|
+
COERCION_TYPES = [:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
|
30
30
|
|
31
31
|
# Set the Primary Symmetric Cipher to be used
|
32
32
|
#
|
@@ -49,7 +49,7 @@ module SymmetricEncryption
|
|
49
49
|
def self.cipher(version = nil)
|
50
50
|
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
|
51
51
|
return @@cipher if version.nil? || (@@cipher.version == version)
|
52
|
-
secondary_ciphers.find {|c| c.version == version} || (@@cipher if version == 0)
|
52
|
+
secondary_ciphers.find { |c| c.version == version } || (@@cipher if version == 0)
|
53
53
|
end
|
54
54
|
|
55
55
|
# Returns whether a primary cipher has been set
|
@@ -59,9 +59,9 @@ module SymmetricEncryption
|
|
59
59
|
|
60
60
|
# Set the Secondary Symmetric Ciphers Array to be used
|
61
61
|
def self.secondary_ciphers=(secondary_ciphers)
|
62
|
-
raise(ArgumentError,
|
62
|
+
raise(ArgumentError, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each
|
63
63
|
secondary_ciphers.each do |cipher|
|
64
|
-
raise(ArgumentError,
|
64
|
+
raise(ArgumentError, 'secondary_ciphers can only consist of SymmetricEncryption::Ciphers') unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
|
65
65
|
end
|
66
66
|
@@secondary_ciphers = secondary_ciphers
|
67
67
|
end
|
@@ -109,28 +109,27 @@ module SymmetricEncryption
|
|
109
109
|
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
|
110
110
|
return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '')
|
111
111
|
|
112
|
-
str
|
112
|
+
str = encrypted_and_encoded_string.to_s
|
113
113
|
|
114
114
|
# Decode before decrypting supplied string
|
115
115
|
decoded = @@cipher.decode(str)
|
116
116
|
return unless decoded
|
117
117
|
return decoded if decoded.empty?
|
118
118
|
|
119
|
-
decrypted =
|
120
|
-
header.
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
if defined?(Encoding)
|
128
|
-
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
129
|
-
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
130
|
-
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
119
|
+
decrypted =
|
120
|
+
if header = Cipher.parse_header!(decoded)
|
121
|
+
header.decryption_cipher.binary_decrypt(decoded, header)
|
122
|
+
else
|
123
|
+
# Use cipher_selector if present to decide which cipher to use
|
124
|
+
c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded)
|
125
|
+
c.binary_decrypt(decoded)
|
131
126
|
end
|
127
|
+
|
128
|
+
# Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary
|
129
|
+
unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding?
|
130
|
+
decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
132
131
|
end
|
133
|
-
coerce_from_string(decrypted, type)
|
132
|
+
Coerce.coerce_from_string(decrypted, type)
|
134
133
|
end
|
135
134
|
|
136
135
|
# AES Symmetric Encryption of supplied string
|
@@ -179,7 +178,7 @@ module SymmetricEncryption
|
|
179
178
|
raise(SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data') unless @@cipher
|
180
179
|
|
181
180
|
# Encrypt and then encode the supplied string
|
182
|
-
@@cipher.encrypt(coerce_to_string(str, type), random_iv, compress)
|
181
|
+
@@cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv, compress)
|
183
182
|
end
|
184
183
|
|
185
184
|
# Invokes decrypt
|
@@ -255,10 +254,7 @@ module SymmetricEncryption
|
|
255
254
|
# Which environments config to load. Usually: production, development, etc.
|
256
255
|
# Default: Rails.env
|
257
256
|
def self.load!(filename=nil, environment=nil)
|
258
|
-
|
259
|
-
@@cipher = ciphers.shift
|
260
|
-
@@secondary_ciphers = ciphers
|
261
|
-
true
|
257
|
+
Config.load!(filename, environment)
|
262
258
|
end
|
263
259
|
|
264
260
|
# Generate new random symmetric keys for use with this Encryption library
|
@@ -266,53 +262,16 @@ module SymmetricEncryption
|
|
266
262
|
# Note: Only the current Encryption key settings are used
|
267
263
|
#
|
268
264
|
# Creates Symmetric Key .key
|
269
|
-
# and
|
265
|
+
# and initialization vector .iv
|
270
266
|
# which is encrypted with the above Public key
|
271
267
|
#
|
272
268
|
# Existing key files will be renamed if present
|
273
269
|
def self.generate_symmetric_key_files(filename=nil, environment=nil)
|
274
|
-
|
275
|
-
config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
|
276
|
-
|
277
|
-
# RSA key to decrypt key files
|
278
|
-
private_rsa_key = config.delete('private_rsa_key')
|
279
|
-
raise(SymmetricEncryption::ConfigError, "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys") unless private_rsa_key
|
280
|
-
rsa_key = OpenSSL::PKey::RSA.new(private_rsa_key)
|
281
|
-
|
282
|
-
# Check if config file contains 1 or multiple ciphers
|
283
|
-
ciphers = config.delete('ciphers')
|
284
|
-
cfg = ciphers.nil? ? config : ciphers.first
|
285
|
-
|
286
|
-
# Convert keys to symbols
|
287
|
-
cipher_cfg = {}
|
288
|
-
cfg.each_pair{|k,v| cipher_cfg[k.to_sym] = v}
|
289
|
-
|
290
|
-
cipher_name = cipher_cfg[:cipher_name] || cipher_cfg[:cipher]
|
291
|
-
|
292
|
-
# Generate a new Symmetric Key pair
|
293
|
-
iv_filename = cipher_cfg[:iv_filename]
|
294
|
-
key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name || 'aes-256-cbc')
|
295
|
-
|
296
|
-
if key_filename = cipher_cfg[:key_filename]
|
297
|
-
# Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
|
298
|
-
File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
|
299
|
-
File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
|
300
|
-
puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} to the other web servers in #{environment}.")
|
301
|
-
elsif !cipher_cfg[:key]
|
302
|
-
key = rsa_key.public_encrypt(key_pair[:key])
|
303
|
-
puts "Generated new Symmetric Key for encryption. Set the KEY environment variable in #{environment} to:"
|
304
|
-
puts ::Base64.encode64(key)
|
305
|
-
end
|
270
|
+
config = Config.read_config(filename, environment)
|
306
271
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
puts("Generated new Symmetric Key for encryption. Please copy #{iv_filename} to the other web servers in #{environment}.")
|
311
|
-
elsif !cipher_cfg[:iv]
|
312
|
-
iv = rsa_key.public_encrypt(key_pair[:iv])
|
313
|
-
puts "Generated new Symmetric Key for encryption. Set the IV environment variable in #{environment} to:"
|
314
|
-
puts ::Base64.encode64(iv)
|
315
|
-
end
|
272
|
+
# Only regenerating the first configured cipher
|
273
|
+
cipher_config = config[:ciphers].first
|
274
|
+
Cipher.generate_random_keys({environment: environment, private_rsa_key: config[:private_rsa_key]}.merge(cipher_config))
|
316
275
|
end
|
317
276
|
|
318
277
|
# Generate a 22 character random password
|
@@ -323,225 +282,11 @@ module SymmetricEncryption
|
|
323
282
|
# Binary encrypted data includes this magic header so that we can quickly
|
324
283
|
# identify binary data versus base64 encoded data that does not have this header
|
325
284
|
unless defined? MAGIC_HEADER
|
326
|
-
MAGIC_HEADER
|
327
|
-
MAGIC_HEADER_SIZE
|
285
|
+
MAGIC_HEADER = '@EnC'
|
286
|
+
MAGIC_HEADER_SIZE = MAGIC_HEADER.size
|
328
287
|
MAGIC_HEADER_UNPACK = "a#{MAGIC_HEADER_SIZE}v"
|
329
288
|
end
|
330
289
|
|
331
|
-
|
332
|
-
|
333
|
-
# Returns [Array(SymmetricEncrytion::Cipher)] ciphers specified in the configuration file
|
334
|
-
#
|
335
|
-
# Read the configuration from the YAML file and return in the latest format
|
336
|
-
#
|
337
|
-
# filename:
|
338
|
-
# Name of file to read.
|
339
|
-
# Mandatory for non-Rails apps
|
340
|
-
# Default: Rails.root/config/symmetric-encryption.yml
|
341
|
-
# environment:
|
342
|
-
# Which environments config to load. Usually: production, development, etc.
|
343
|
-
def self.read_config(filename=nil, environment=nil)
|
344
|
-
config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml")
|
345
|
-
config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env]
|
346
|
-
|
347
|
-
# RSA key to decrypt key files
|
348
|
-
private_rsa_key = config.delete('private_rsa_key')
|
349
|
-
|
350
|
-
if ciphers = config.delete('ciphers')
|
351
|
-
ciphers.collect {|cipher_conf| cipher_from_config(cipher_conf, private_rsa_key)}
|
352
|
-
else
|
353
|
-
[cipher_from_config(config, private_rsa_key)]
|
354
|
-
end
|
355
|
-
end
|
356
|
-
|
357
|
-
# Returns an instance of SymmetricEncryption::Cipher created from
|
358
|
-
# the supplied configuration and optional rsa_encryption_key
|
359
|
-
#
|
360
|
-
# Raises an Exception on failure
|
361
|
-
#
|
362
|
-
# Parameters:
|
363
|
-
# cipher_conf Hash:
|
364
|
-
# :cipher_name
|
365
|
-
# Encryption cipher name for the symmetric encryption key
|
366
|
-
#
|
367
|
-
# :version
|
368
|
-
# The version number of this cipher
|
369
|
-
# Default: 0
|
370
|
-
#
|
371
|
-
# :encoding [Symbol]
|
372
|
-
# Encoding to use after encrypting with this cipher
|
373
|
-
#
|
374
|
-
# :always_add_header
|
375
|
-
# Whether to always include the header when encrypting data.
|
376
|
-
# Highly recommended to set this value to true.
|
377
|
-
# Increases the length of the encrypted data by 6 bytes, but makes
|
378
|
-
# migration to a new key trivial
|
379
|
-
# Default: false
|
380
|
-
#
|
381
|
-
# :key
|
382
|
-
# The actual key to use for encryption/decryption purposes
|
383
|
-
#
|
384
|
-
# :key_filename
|
385
|
-
# Name of file containing symmetric key encrypted using the public
|
386
|
-
# key from the private_rsa_key
|
387
|
-
#
|
388
|
-
# :encrypted_key
|
389
|
-
# Symmetric key encrypted using the public key from the private_rsa_key
|
390
|
-
# and then Base64 encoded
|
391
|
-
#
|
392
|
-
# :iv
|
393
|
-
# Optional: The actual iv to use for encryption/decryption purposes
|
394
|
-
#
|
395
|
-
# :encrypted_iv
|
396
|
-
# Initialization vector encrypted using the public key from the private_rsa_key
|
397
|
-
# and then Base64 encoded
|
398
|
-
#
|
399
|
-
# :iv_filename
|
400
|
-
# Optional: Name of file containing symmetric key initialization vector
|
401
|
-
# encrypted using the public key from the private_rsa_key
|
402
|
-
#
|
403
|
-
# private_rsa_key [String]
|
404
|
-
# RSA Key used to decrypt key and iv as applicable
|
405
|
-
def self.cipher_from_config(cipher_conf, private_rsa_key=nil)
|
406
|
-
config = {}
|
407
|
-
cipher_conf.each_pair{|k,v| config[k.to_sym] = v}
|
408
|
-
|
409
|
-
# To decrypt encrypted key or iv files
|
410
|
-
rsa = OpenSSL::PKey::RSA.new(private_rsa_key) if private_rsa_key
|
411
|
-
|
412
|
-
# Load Encrypted Symmetric keys
|
413
|
-
if key_filename = config.delete(:key_filename)
|
414
|
-
raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :key_filename is supplied") unless rsa
|
415
|
-
encrypted_key = begin
|
416
|
-
File.open(key_filename, 'rb'){|f| f.read}
|
417
|
-
rescue Errno::ENOENT
|
418
|
-
puts "\nSymmetric Encryption key file: '#{key_filename}' not found or readable."
|
419
|
-
puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
|
420
|
-
return
|
421
|
-
end
|
422
|
-
config[:key] = rsa.private_decrypt(encrypted_key)
|
423
|
-
end
|
424
|
-
|
425
|
-
if iv_filename = config.delete(:iv_filename)
|
426
|
-
raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :iv_filename is supplied") unless rsa
|
427
|
-
encrypted_iv = begin
|
428
|
-
File.open(iv_filename, 'rb'){|f| f.read} if iv_filename
|
429
|
-
rescue Errno::ENOENT
|
430
|
-
puts "\nSymmetric Encryption initialization vector file: '#{iv_filename}' not found or readable."
|
431
|
-
puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
|
432
|
-
return
|
433
|
-
end
|
434
|
-
config[:iv] = rsa.private_decrypt(encrypted_iv)
|
435
|
-
end
|
436
|
-
|
437
|
-
if encrypted_key = config.delete(:encrypted_key)
|
438
|
-
raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_key is supplied") unless rsa
|
439
|
-
# Decode value first using encoding specified
|
440
|
-
encrypted_key = ::Base64.decode64(encrypted_key)
|
441
|
-
if !encrypted_key || encrypted_key.empty?
|
442
|
-
puts "\nSymmetric Encryption encrypted_key not found."
|
443
|
-
puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
|
444
|
-
return
|
445
|
-
end
|
446
|
-
config[:key] = rsa.private_decrypt(encrypted_key)
|
447
|
-
end
|
448
|
-
|
449
|
-
if encrypted_iv = config.delete(:encrypted_iv)
|
450
|
-
raise(SymmetricEncryption::ConfigError, "Missing mandatory config parameter :private_rsa_key when :encrypted_iv is supplied") unless rsa
|
451
|
-
# Decode value first using encoding specified
|
452
|
-
encrypted_iv = ::Base64.decode64(encrypted_iv)
|
453
|
-
if !encrypted_key || encrypted_key.empty?
|
454
|
-
puts "\nSymmetric Encryption encrypted_iv not found."
|
455
|
-
puts "To generate the keys for the first time run: rails generate symmetric_encryption:new_keys\n\n"
|
456
|
-
return
|
457
|
-
end
|
458
|
-
config[:iv] = rsa.private_decrypt(encrypted_iv)
|
459
|
-
end
|
460
|
-
|
461
|
-
# Backward compatibility
|
462
|
-
if old_key_name_cipher = config.delete(:cipher)
|
463
|
-
config[:cipher_name] = old_key_name_cipher
|
464
|
-
end
|
465
|
-
|
466
|
-
# Decrypt Symmetric Keys
|
467
|
-
Cipher.new(config)
|
468
|
-
end
|
469
|
-
|
470
|
-
# Coerce given value into given type
|
471
|
-
# Does not coerce json or yaml values
|
472
|
-
def self.coerce(value, type, from_type=nil)
|
473
|
-
return if value.nil? || (value.is_a?(String) && (value !~ /[^[:space:]]/))
|
474
|
-
|
475
|
-
from_type ||= value.class
|
476
|
-
case type
|
477
|
-
when :json
|
478
|
-
value
|
479
|
-
when :yaml
|
480
|
-
value
|
481
|
-
else
|
482
|
-
coercer = Coercible::Coercer.new
|
483
|
-
coercer[from_type].send("to_#{type}".to_sym, value)
|
484
|
-
end
|
485
|
-
end
|
486
|
-
|
487
|
-
# Uses coercible gem to coerce values from strings into the target type
|
488
|
-
# Note: if the type is :string, then the value is returned as is, and the
|
489
|
-
# coercible gem is not used at all.
|
490
|
-
def self.coerce_from_string(value, type)
|
491
|
-
return if value.nil?
|
492
|
-
case type
|
493
|
-
when :string
|
494
|
-
value
|
495
|
-
when :json
|
496
|
-
JSON.load(value)
|
497
|
-
when :yaml
|
498
|
-
YAML.load(value)
|
499
|
-
else
|
500
|
-
self.coerce(value, type, String)
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
|
-
# Uses coercible gem to coerce values to strings from the specified type
|
505
|
-
# Note: if the type is :string, and value is not nil, then #to_s is called
|
506
|
-
# on the value and the coercible gem is not used at all.
|
507
|
-
def self.coerce_to_string(value, type)
|
508
|
-
return if value.nil?
|
509
|
-
|
510
|
-
case type
|
511
|
-
when :string
|
512
|
-
value.to_s
|
513
|
-
when :json
|
514
|
-
value.to_json
|
515
|
-
when :yaml
|
516
|
-
value.to_yaml
|
517
|
-
else
|
518
|
-
self.coerce(value, :string, coercion_type(type, value))
|
519
|
-
end
|
520
|
-
end
|
521
|
-
|
522
|
-
# Returns the correct coercion type to use for the specified symbol and value
|
523
|
-
def self.coercion_type(symbol, value)
|
524
|
-
if symbol == :boolean
|
525
|
-
value.class
|
526
|
-
else
|
527
|
-
COERCION_TYPE_MAP[symbol]
|
528
|
-
end
|
529
|
-
end
|
530
|
-
|
531
|
-
COERCION_TYPE_MAP = {
|
532
|
-
string: String,
|
533
|
-
integer: Integer,
|
534
|
-
float: Float,
|
535
|
-
decimal: BigDecimal,
|
536
|
-
datetime: DateTime,
|
537
|
-
time: Time,
|
538
|
-
date: Date
|
539
|
-
}
|
540
|
-
|
541
|
-
# With Ruby 1.9 strings have encodings
|
542
|
-
if defined?(Encoding)
|
543
|
-
BINARY_ENCODING = Encoding.find("binary")
|
544
|
-
UTF8_ENCODING = Encoding.find("UTF-8")
|
545
|
-
end
|
546
|
-
|
290
|
+
BINARY_ENCODING = Encoding.find('binary')
|
291
|
+
UTF8_ENCODING = Encoding.find('UTF-8')
|
547
292
|
end
|