symmetric-encryption 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +41 -11
- data/Rakefile +3 -3
- data/lib/symmetric-encryption.rb +10 -7
- data/lib/symmetric_encryption/cipher.rb +114 -0
- data/lib/{symmetric → symmetric_encryption}/extensions/active_record/base.rb +4 -7
- data/lib/{symmetric → symmetric_encryption}/extensions/mongoid/fields.rb +19 -10
- data/lib/{symmetric → symmetric_encryption}/railtie.rb +4 -4
- data/lib/{symmetric → symmetric_encryption}/railties/symmetric_encryption.rake +5 -5
- data/lib/{symmetric/railties/symmetric_encrypted_validator.rb → symmetric_encryption/railties/symmetric_encryption_validator.rb} +4 -4
- data/lib/symmetric_encryption/reader.rb +221 -0
- data/lib/symmetric_encryption/symmetric_encryption.rb +280 -0
- data/lib/symmetric_encryption/version.rb +4 -0
- data/lib/symmetric_encryption/writer.rb +132 -0
- data/nbproject/private/private.xml +14 -1
- data/symmetric-encryption-0.2.0.gem +0 -0
- data/symmetric-encryption-0.4.0.gem +0 -0
- data/test/attr_encrypted_test.rb +7 -7
- data/test/cipher_test.rb +8 -13
- data/test/field_encrypted_test.rb +3 -3
- data/test/reader_test.rb +76 -0
- data/test/symmetric_encryption_test.rb +53 -0
- data/test/writer_test.rb +56 -0
- metadata +20 -15
- data/lib/symmetric/cipher.rb +0 -184
- data/lib/symmetric/encryption.rb +0 -262
- data/lib/symmetric/version.rb +0 -4
- data/symmetric-encryption-0.3.0.gem +0 -0
- data/symmetric-encryption-0.3.1.gem +0 -0
- data/test/encryption_test.rb +0 -51
@@ -2,17 +2,17 @@
|
|
2
2
|
#
|
3
3
|
# Example:
|
4
4
|
# class MyModel < ActiveRecord::Base
|
5
|
-
# validates :encrypted_ssn, :
|
5
|
+
# validates :encrypted_ssn, :symmetric_encryption => true
|
6
6
|
# end
|
7
7
|
#
|
8
8
|
# m = MyModel.new
|
9
9
|
# m.valid?
|
10
10
|
# # => false
|
11
|
-
# m.encrypted_ssn =
|
11
|
+
# m.encrypted_ssn = SymmetricEncryption.encrypt('123456789')
|
12
12
|
# m.valid?
|
13
13
|
# # => true
|
14
|
-
class
|
14
|
+
class SymmetricEncryptionValidator < ActiveModel::EachValidator
|
15
15
|
def validate_each(record, attribute, value)
|
16
|
-
record.errors.add(attribute, "must be a value encrypted using
|
16
|
+
record.errors.add(attribute, "must be a value encrypted using SymmetricEncryption.encrypt") unless SymmetricEncryption.encrypted?(value)
|
17
17
|
end
|
18
18
|
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module SymmetricEncryption
|
2
|
+
# Read from encrypted files and other IO streams
|
3
|
+
#
|
4
|
+
# Features:
|
5
|
+
# * Decryption on the fly whilst reading files
|
6
|
+
# * Large file support by only buffering small amounts of data in memory
|
7
|
+
#
|
8
|
+
# # Example: Read and decrypt a line at a time from a file
|
9
|
+
# SymmetricEncryption::Reader.open('test_file') do |file|
|
10
|
+
# file.each_line {|line| p line }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # Example: Read and decrypt entire file in memory
|
14
|
+
# # Not recommended for large files
|
15
|
+
# SymmetricEncryption::Reader.open('test_file') {|f| f.read }
|
16
|
+
#
|
17
|
+
# # Example: Reading a limited number of bytes at a time from the file
|
18
|
+
# SymmetricEncryption::Reader.open('test_file') do |file|
|
19
|
+
# file.read(1)
|
20
|
+
# file.read(5)
|
21
|
+
# file.read
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # Example: Read and decrypt 5 bytes at a time until the end of file is reached
|
25
|
+
# SymmetricEncryption::Reader.open('test_file') do |file|
|
26
|
+
# while !file.eof? do
|
27
|
+
# file.read(5)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# # Example: Read, Unencrypt and decompress data in a file
|
32
|
+
# SymmetricEncryption::Reader.open('encrypted_compressed.zip', :compress => true) do |file|
|
33
|
+
# file.each_line {|line| p line }
|
34
|
+
# end
|
35
|
+
class Reader
|
36
|
+
# Open a file for reading, or use the supplied IO Stream
|
37
|
+
#
|
38
|
+
# Parameters:
|
39
|
+
# filename_or_stream:
|
40
|
+
# The filename to open if a string, otherwise the stream to use
|
41
|
+
# The file or stream will be closed on completion, use .initialize to
|
42
|
+
# avoid having the stream closed automatically
|
43
|
+
#
|
44
|
+
# options:
|
45
|
+
# :compress [true|false]
|
46
|
+
# Uses Zlib to decompress the data after it is decrypted
|
47
|
+
# Note: This option is only used if the file does not have a header
|
48
|
+
# indicating whether it is compressed
|
49
|
+
# Default: false
|
50
|
+
#
|
51
|
+
# :version
|
52
|
+
# Version of the encryption key to use when decrypting and the
|
53
|
+
# file/stream does not include a header at the beginning
|
54
|
+
# Default: Current primary key
|
55
|
+
#
|
56
|
+
# :mode
|
57
|
+
# See File.open for open modes
|
58
|
+
# Default: 'r'
|
59
|
+
#
|
60
|
+
# :buffer_size
|
61
|
+
# Amount of data to read at a time
|
62
|
+
# Default: 4096
|
63
|
+
#
|
64
|
+
# Note: Decryption occurs before decompression
|
65
|
+
def self.open(filename_or_stream, options={}, &block)
|
66
|
+
raise "options must be a hash" unless options.respond_to?(:each_pair)
|
67
|
+
mode = options.fetch(:mode, 'r')
|
68
|
+
compress = options.fetch(:compress, false)
|
69
|
+
ios = filename_or_stream.is_a?(String) ? ::File.open(filename_or_stream, mode) : filename_or_stream
|
70
|
+
|
71
|
+
begin
|
72
|
+
file = self.new(ios, options)
|
73
|
+
file = Zlib::GzipReader.new(file) if file.compressed? || compress
|
74
|
+
block.call(file)
|
75
|
+
ensure
|
76
|
+
file.close if file
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Decrypt data before reading from the supplied stream
|
81
|
+
def initialize(ios,options={})
|
82
|
+
@ios = ios
|
83
|
+
@buffer_size = options.fetch(:buffer_size, 4096).to_i
|
84
|
+
@compressed = nil
|
85
|
+
@read_buffer = ''
|
86
|
+
|
87
|
+
# Read first block and check for the header
|
88
|
+
buf = @ios.read(@buffer_size)
|
89
|
+
if buf.start_with?(SymmetricEncryption::MAGIC_HEADER)
|
90
|
+
# Header includes magic header and version byte
|
91
|
+
# Remove header and extract flags
|
92
|
+
header, flags = buf.slice!(0..MAGIC_HEADER_SIZE).unpack(MAGIC_HEADER_UNPACK)
|
93
|
+
@compressed = flags & 0b1000_0000_0000_0000
|
94
|
+
@version = @compressed ? flags - 0b1000_0000_0000_0000 : flags
|
95
|
+
else
|
96
|
+
@version = options[:version]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Use primary cipher by default, but allow a secondary cipher to be selected for encryption
|
100
|
+
@cipher = SymmetricEncryption.cipher(@version)
|
101
|
+
raise "Cipher with version:#{@version} not found in any of the configured SymmetricEncryption ciphers" unless @cipher
|
102
|
+
@stream_cipher = @cipher.send(:openssl_cipher, :decrypt)
|
103
|
+
|
104
|
+
# First call to #update should return an empty string anyway
|
105
|
+
@read_buffer << @stream_cipher.update(buf)
|
106
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns whether the stream being read is compressed
|
110
|
+
#
|
111
|
+
# Should be called before any reads are performed to determine if the file or
|
112
|
+
# stream is compressed.
|
113
|
+
#
|
114
|
+
# Returns true when the header is present in the stream and it is compressed
|
115
|
+
# Returns false when the header is present in the stream and it is not compressed
|
116
|
+
# Returns nil when the header is not present in the stream
|
117
|
+
#
|
118
|
+
# Note: The file will not be decompressed automatically when compressed.
|
119
|
+
# To decompress the data automatically call SymmetricEncryption.open
|
120
|
+
def compressed?
|
121
|
+
@compressed
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the Cipher encryption version used to encrypt this file
|
125
|
+
# Returns nil when the header was not present in the stream and no :version
|
126
|
+
# option was supplied
|
127
|
+
#
|
128
|
+
# Note: When no header is present, the version is set to the one supplied
|
129
|
+
# in the options
|
130
|
+
def version
|
131
|
+
@version
|
132
|
+
end
|
133
|
+
|
134
|
+
# Close the IO Stream
|
135
|
+
#
|
136
|
+
# Note: Also closes the passed in io stream or file
|
137
|
+
#
|
138
|
+
# It is recommended to call Symmetric::EncryptedStream.open or Symmetric::EncryptedStream.io
|
139
|
+
# rather than creating an instance of Symmetric::EncryptedStream directly to
|
140
|
+
# ensure that the encrypted stream is closed before the stream itself is closed
|
141
|
+
def close(close_child_stream = true)
|
142
|
+
@ios.close if close_child_stream
|
143
|
+
end
|
144
|
+
|
145
|
+
# Read from the stream and return the decrypted data
|
146
|
+
# See IOS#read
|
147
|
+
#
|
148
|
+
# Reads at most length bytes from the I/O stream, or to the end of file if
|
149
|
+
# length is omitted or is nil. length must be a non-negative integer or nil.
|
150
|
+
#
|
151
|
+
# At end of file, it returns nil or "" depending on length.
|
152
|
+
def read(length=nil)
|
153
|
+
data = nil
|
154
|
+
if length
|
155
|
+
return '' if length == 0
|
156
|
+
# Read length bytes
|
157
|
+
while (@read_buffer.length < length) && !@ios.eof?
|
158
|
+
read_block
|
159
|
+
end
|
160
|
+
if @read_buffer.length > length
|
161
|
+
data = @read_buffer.slice!(0..length-1)
|
162
|
+
else
|
163
|
+
data = @read_buffer
|
164
|
+
@read_buffer = ''
|
165
|
+
end
|
166
|
+
else
|
167
|
+
# Capture anything already in the buffer
|
168
|
+
data = @read_buffer
|
169
|
+
@read_buffer = ''
|
170
|
+
|
171
|
+
if !@ios.eof?
|
172
|
+
# Read entire file
|
173
|
+
buf = @ios.read || ''
|
174
|
+
data << @stream_cipher.update(buf) if buf && buf.length > 0
|
175
|
+
data << @stream_cipher.final
|
176
|
+
end
|
177
|
+
end
|
178
|
+
data
|
179
|
+
end
|
180
|
+
|
181
|
+
# Reads a single decrypted line from the file up to and including the optional sep_string.
|
182
|
+
# Returns nil on eof
|
183
|
+
# The stream must be opened for reading or an IOError will be raised.
|
184
|
+
def readline(sep_string = "\n")
|
185
|
+
# Read more data until we get the sep_string
|
186
|
+
while (index = @read_buffer.index(sep_string)).nil? && !@ios.eof?
|
187
|
+
read_block
|
188
|
+
end
|
189
|
+
index ||= -1
|
190
|
+
@read_buffer.slice!(0..index)
|
191
|
+
end
|
192
|
+
|
193
|
+
# ios.each(sep_string="\n") {|line| block } => ios
|
194
|
+
# ios.each_line(sep_string="\n") {|line| block } => ios
|
195
|
+
# Executes the block for every line in ios, where lines are separated by sep_string.
|
196
|
+
# ios must be opened for reading or an IOError will be raised.
|
197
|
+
def each_line(sep_string = "\n")
|
198
|
+
while !eof?
|
199
|
+
yield readline(sep_string)
|
200
|
+
end
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
alias_method :each, :each_line
|
205
|
+
|
206
|
+
# Returns whether the end of file has been reached for this stream
|
207
|
+
def eof?
|
208
|
+
(@read_buffer.size == 0) && @ios.eof?
|
209
|
+
end
|
210
|
+
|
211
|
+
private
|
212
|
+
|
213
|
+
# Read a block of data and append the decrypted data in the read buffer
|
214
|
+
def read_block
|
215
|
+
buf = @ios.read(@buffer_size)
|
216
|
+
@read_buffer << @stream_cipher.update(buf) if buf && buf.length > 0
|
217
|
+
@read_buffer << @stream_cipher.final if @ios.eof?
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'zlib'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
# Encrypt using 256 Bit AES CBC symmetric key and initialization vector
|
7
|
+
# The symmetric key is protected using the private key below and must
|
8
|
+
# be distributed separately from the application
|
9
|
+
module SymmetricEncryption
|
10
|
+
|
11
|
+
# Defaults
|
12
|
+
@@cipher = nil
|
13
|
+
@@secondary_ciphers = []
|
14
|
+
|
15
|
+
# Set the Primary Symmetric Cipher to be used
|
16
|
+
def self.cipher=(cipher)
|
17
|
+
raise "Cipher must be similar to SymmetricEncryption::Ciphers" unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
|
18
|
+
@@cipher = cipher
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the Primary Symmetric Cipher being used
|
22
|
+
# If a version is supplied, then the cipher matching that version will be
|
23
|
+
# returned or nil if no match was found
|
24
|
+
def self.cipher(version = nil)
|
25
|
+
return @@cipher if version.nil? || (@@cipher.version == version)
|
26
|
+
secondary_ciphers.find {|c| c.version == version}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the Secondary Symmetric Ciphers Array to be used
|
30
|
+
def self.secondary_ciphers=(secondary_ciphers)
|
31
|
+
raise "secondary_ciphers must be a collection" unless secondary_ciphers.respond_to? :each
|
32
|
+
secondary_ciphers.each do |cipher|
|
33
|
+
raise "secondary_ciphers can only consist of SymmetricEncryption::Ciphers" unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)
|
34
|
+
end
|
35
|
+
@@secondary_ciphers = secondary_ciphers
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the Primary Symmetric Cipher being used
|
39
|
+
def self.secondary_ciphers
|
40
|
+
@@secondary_ciphers
|
41
|
+
end
|
42
|
+
|
43
|
+
# AES Symmetric Decryption of supplied string
|
44
|
+
# Returns decrypted string
|
45
|
+
# Returns nil if the supplied str is nil
|
46
|
+
# Returns "" if it is a string and it is empty
|
47
|
+
#
|
48
|
+
# Note: If secondary ciphers are supplied in the configuration file the
|
49
|
+
# first key will be used to decrypt 'str'. If it fails each cipher in the
|
50
|
+
# order supplied will be tried.
|
51
|
+
# It is slow to try each cipher in turn, so should be used during migrations
|
52
|
+
# only
|
53
|
+
#
|
54
|
+
# Raises: OpenSSL::Cipher::CipherError when 'str' was not encrypted using
|
55
|
+
# the supplied key and iv
|
56
|
+
#
|
57
|
+
def self.decrypt(str)
|
58
|
+
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
59
|
+
binary = ::Base64.decode64(str) if str
|
60
|
+
begin
|
61
|
+
@@cipher.decrypt(binary)
|
62
|
+
rescue OpenSSL::Cipher::CipherError => exc
|
63
|
+
@@secondary_ciphers.each do |cipher|
|
64
|
+
begin
|
65
|
+
return cipher.decrypt(binary)
|
66
|
+
rescue OpenSSL::Cipher::CipherError
|
67
|
+
end
|
68
|
+
end
|
69
|
+
raise exc
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# AES Symmetric Encryption of supplied string
|
74
|
+
# Returns result as a Base64 encoded string
|
75
|
+
# Returns nil if the supplied str is nil
|
76
|
+
# Returns "" if it is a string and it is empty
|
77
|
+
def self.encrypt(str)
|
78
|
+
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
79
|
+
|
80
|
+
# Encrypt data as a binary string
|
81
|
+
result = @@cipher.encrypt(str)
|
82
|
+
|
83
|
+
# Base 64 Encoding of binary data
|
84
|
+
result = ::Base64.encode64(result) if result
|
85
|
+
result
|
86
|
+
end
|
87
|
+
|
88
|
+
# Invokes decrypt
|
89
|
+
# Returns decrypted String
|
90
|
+
# Return nil if it fails to decrypt a String
|
91
|
+
#
|
92
|
+
# Useful for example when decoding passwords encrypted using a key from a
|
93
|
+
# different environment. I.e. We cannot decode production passwords
|
94
|
+
# in the test or development environments but still need to be able to load
|
95
|
+
# YAML config files that contain encrypted development and production passwords
|
96
|
+
def self.try_decrypt(str)
|
97
|
+
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
98
|
+
begin
|
99
|
+
decrypt(str)
|
100
|
+
rescue OpenSSL::Cipher::CipherError
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns [true|false] as to whether the data could be decrypted
|
106
|
+
# Parameters:
|
107
|
+
# encrypted_data: Encrypted string
|
108
|
+
def self.encrypted?(encrypted_data)
|
109
|
+
raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher
|
110
|
+
|
111
|
+
# First make sure Base64 encoded data still ends with "\n" since it could be used in a key field somewhere
|
112
|
+
return false unless encrypted_data.end_with?("\n")
|
113
|
+
|
114
|
+
# For now have to decrypt it fully
|
115
|
+
!try_decrypt(encrypted_data).nil?
|
116
|
+
end
|
117
|
+
|
118
|
+
# Load the Encryption Configuration from a YAML file
|
119
|
+
# filename:
|
120
|
+
# Name of file to read.
|
121
|
+
# Mandatory for non-Rails apps
|
122
|
+
# Default: Rails.root/config/symmetric-encryption.yml
|
123
|
+
# environment:
|
124
|
+
# Which environments config to load. Usually: production, development, etc.
|
125
|
+
# Default: Rails.env
|
126
|
+
def self.load!(filename=nil, environment=nil)
|
127
|
+
config = read_config(filename, environment)
|
128
|
+
|
129
|
+
# Check for hard coded key, iv and cipher
|
130
|
+
if config[:key]
|
131
|
+
@@cipher = Cipher.new(config)
|
132
|
+
@@secondary_ciphers = []
|
133
|
+
else
|
134
|
+
private_rsa_key = config[:private_rsa_key]
|
135
|
+
@@cipher, *@@secondary_ciphers = config[:ciphers].collect do |cipher_conf|
|
136
|
+
cipher_from_encrypted_files(
|
137
|
+
private_rsa_key,
|
138
|
+
cipher_conf[:cipher],
|
139
|
+
cipher_conf[:key_filename],
|
140
|
+
cipher_conf[:iv_filename])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
# Future: Generate private key in config file generator
|
148
|
+
#new_key = OpenSSL::PKey::RSA.generate(2048)
|
149
|
+
|
150
|
+
# Generate new random symmetric keys for use with this Encryption library
|
151
|
+
#
|
152
|
+
# Note: Only the current Encryption key settings are used
|
153
|
+
#
|
154
|
+
# Creates Symmetric Key .key
|
155
|
+
# and initilization vector .iv
|
156
|
+
# which is encrypted with the above Public key
|
157
|
+
#
|
158
|
+
# Warning: Existing files will be overwritten
|
159
|
+
def self.generate_symmetric_key_files(filename=nil, environment=nil)
|
160
|
+
config = read_config(filename, environment)
|
161
|
+
cipher_cfg = config[:ciphers].first
|
162
|
+
key_filename = cipher_cfg[:key_filename]
|
163
|
+
iv_filename = cipher_cfg[:iv_filename]
|
164
|
+
cipher = cipher_cfg[:cipher]
|
165
|
+
|
166
|
+
raise "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys" unless config[:private_rsa_key]
|
167
|
+
rsa_key = OpenSSL::PKey::RSA.new(config[:private_rsa_key])
|
168
|
+
|
169
|
+
# Generate a new Symmetric Key pair
|
170
|
+
key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher || 'aes-256-cbc', !iv_filename.nil?)
|
171
|
+
|
172
|
+
# Save symmetric key after encrypting it with the private RSA key, backing up existing files if present
|
173
|
+
File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename)
|
174
|
+
File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) }
|
175
|
+
|
176
|
+
if iv_filename
|
177
|
+
File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename)
|
178
|
+
File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) }
|
179
|
+
end
|
180
|
+
puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} and #{iv_filename} to the other web servers in #{environment}.")
|
181
|
+
end
|
182
|
+
|
183
|
+
# Generate a 22 character random password
|
184
|
+
def self.random_password
|
185
|
+
Base64.encode64(OpenSSL::Cipher.new('aes-128-cbc').random_key)[0..-4]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Binary encrypted data includes this magic header so that we can quickly
|
189
|
+
# identify binary data versus base64 encoded data that does not have this header
|
190
|
+
unless defined? MAGIC_HEADER
|
191
|
+
MAGIC_HEADER = '@EnC'
|
192
|
+
MAGIC_HEADER_SIZE = MAGIC_HEADER.size
|
193
|
+
MAGIC_HEADER_UNPACK = "A#{MAGIC_HEADER_SIZE}v"
|
194
|
+
end
|
195
|
+
|
196
|
+
protected
|
197
|
+
|
198
|
+
# Returns the Encryption Configuration
|
199
|
+
#
|
200
|
+
# Read the configuration from the YAML file and return in the latest format
|
201
|
+
#
|
202
|
+
# filename:
|
203
|
+
# Name of file to read.
|
204
|
+
# Mandatory for non-Rails apps
|
205
|
+
# Default: Rails.root/config/symmetric-encryption.yml
|
206
|
+
# environment:
|
207
|
+
# Which environments config to load. Usually: production, development, etc.
|
208
|
+
def self.read_config(filename=nil, environment=nil)
|
209
|
+
config = YAML.load_file(filename || File.join(Rails.root, "config", "symmetric-encryption.yml"))[environment || Rails.env]
|
210
|
+
|
211
|
+
# Default cipher
|
212
|
+
default_cipher = config['cipher'] || 'aes-256-cbc'
|
213
|
+
cfg = {}
|
214
|
+
|
215
|
+
# Hard coded symmetric_key? - Dev / Testing use only!
|
216
|
+
if symmetric_key = (config['key'] || config['symmetric_key'])
|
217
|
+
raise "SymmetricEncryption Cannot hard code Production encryption keys in #{filename}" if (environment || Rails.env) == 'production'
|
218
|
+
cfg[:key] = symmetric_key
|
219
|
+
cfg[:iv] = config['iv'] || config['symmetric_iv']
|
220
|
+
cfg[:cipher] = default_cipher
|
221
|
+
|
222
|
+
elsif ciphers = config['ciphers']
|
223
|
+
raise "Missing mandatory config parameter 'private_rsa_key'" unless cfg[:private_rsa_key] = config['private_rsa_key']
|
224
|
+
|
225
|
+
cfg[:ciphers] = ciphers.collect do |cipher_cfg|
|
226
|
+
key_filename = cipher_cfg['key_filename'] || cipher_cfg['symmetric_key_filename']
|
227
|
+
raise "Missing mandatory 'key_filename' for environment:#{environment} in #{filename}" unless key_filename
|
228
|
+
iv_filename = cipher_cfg['iv_filename'] || cipher_cfg['symmetric_iv_filename']
|
229
|
+
{
|
230
|
+
:cipher => cipher_cfg['cipher'] || default_cipher,
|
231
|
+
:key_filename => key_filename,
|
232
|
+
:iv_filename => iv_filename,
|
233
|
+
}
|
234
|
+
end
|
235
|
+
|
236
|
+
else
|
237
|
+
# Migrate old format config
|
238
|
+
raise "Missing mandatory config parameter 'private_rsa_key'" unless cfg[:private_rsa_key] = config['private_rsa_key']
|
239
|
+
cfg[:ciphers] = [ {
|
240
|
+
:cipher => default_cipher,
|
241
|
+
:key_filename => config['symmetric_key_filename'],
|
242
|
+
:iv_filename => config['symmetric_iv_filename'],
|
243
|
+
} ]
|
244
|
+
end
|
245
|
+
|
246
|
+
cfg
|
247
|
+
end
|
248
|
+
|
249
|
+
# Returns an instance of SymmetricEncryption::Cipher initialized from keys
|
250
|
+
# stored in files
|
251
|
+
#
|
252
|
+
# Raises an Exception on failure
|
253
|
+
#
|
254
|
+
# Parameters:
|
255
|
+
# cipher
|
256
|
+
# Encryption cipher for the symmetric encryption key
|
257
|
+
# private_key
|
258
|
+
# Key used to unlock file containing the actual symmetric key
|
259
|
+
# key_filename
|
260
|
+
# Name of file containing symmetric key encrypted using the public
|
261
|
+
# key matching the supplied private_key
|
262
|
+
# iv_filename
|
263
|
+
# Optional. Name of file containing symmetric key initialization vector
|
264
|
+
# encrypted using the public key matching the supplied private_key
|
265
|
+
def self.cipher_from_encrypted_files(private_rsa_key, cipher, key_filename, iv_filename = nil)
|
266
|
+
# Load Encrypted Symmetric keys
|
267
|
+
encrypted_key = File.read(key_filename)
|
268
|
+
encrypted_iv = File.read(iv_filename) if iv_filename
|
269
|
+
|
270
|
+
# Decrypt Symmetric Keys
|
271
|
+
rsa = OpenSSL::PKey::RSA.new(private_rsa_key)
|
272
|
+
iv = rsa.private_decrypt(encrypted_iv) if iv_filename
|
273
|
+
Cipher.new(
|
274
|
+
:key => rsa.private_decrypt(encrypted_key),
|
275
|
+
:iv => iv,
|
276
|
+
:cipher => cipher
|
277
|
+
)
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|