Module: SymmetricEncryption
- Defined in:
- lib/symmetric_encryption/railtie.rb,
lib/symmetric_encryption.rb,
lib/symmetric_encryption/cipher.rb,
lib/symmetric_encryption/reader.rb,
lib/symmetric_encryption/writer.rb,
lib/symmetric_encryption/version.rb,
lib/symmetric_encryption/generator.rb,
lib/symmetric_encryption/symmetric_encryption.rb,
lib/rails/generators/symmetric_encryption/config/config_generator.rb,
lib/rails/generators/symmetric_encryption/new_keys/new_keys_generator.rb,
lib/rails/generators/symmetric_encryption/heroku_config/heroku_config_generator.rb
Overview
Encrypt using 256 Bit AES CBC symmetric key and initialization vector The symmetric key is protected using the private key below and must be distributed separately from the application
Defined Under Namespace
Modules: Generator, Generators Classes: Cipher, Railtie, Reader, Writer
Constant Summary collapse
- VERSION =
"3.6.0"
- COERCION_TYPES =
List of types supported when encrypting or decrypting data
Each type maps to the built-in Ruby types as follows:
:string => String :integer => Integer :float => Float :decimal => BigDecimal :datetime => DateTime :time => Time :date => Date :json => Uses JSON serialization, useful for hashes and arrays :yaml => Uses YAML serialization, useful for hashes and arrays
[:string, :integer, :float, :decimal, :datetime, :time, :date, :boolean, :json, :yaml]
- MAGIC_HEADER =
'@EnC'
- MAGIC_HEADER_SIZE =
MAGIC_HEADER.size
- MAGIC_HEADER_UNPACK =
"a#{MAGIC_HEADER_SIZE}v"
- @@cipher =
Defaults
nil
- @@secondary_ciphers =
[]
- @@select_cipher =
nil
Class Method Summary collapse
-
.cipher(version = nil) ⇒ Object
Returns the Primary Symmetric Cipher being used If a version is supplied Returns the primary cipher if no match was found and version == 0 Returns nil if no match was found and version != 0.
-
.cipher=(cipher) ⇒ Object
Set the Primary Symmetric Cipher to be used.
-
.cipher? ⇒ Boolean
Returns whether a primary cipher has been set.
-
.decrypt(encrypted_and_encoded_string, version = nil, type = :string) ⇒ Object
AES Symmetric Decryption of supplied string Returns decrypted value Returns nil if the supplied value is nil Returns “” if it is a string and it is empty.
-
.encrypt(str, random_iv = false, compress = false, type = :string) ⇒ Object
AES Symmetric Encryption of supplied string Returns result as a Base64 encoded string Returns nil if the supplied str is nil Returns “” if it is a string and it is empty.
-
.encrypted?(encrypted_data) ⇒ Boolean
Returns [true|false] as to whether the data could be decrypted Parameters: encrypted_data: Encrypted string.
-
.generate_symmetric_key_files(filename = nil, environment = nil) ⇒ Object
Generate new random symmetric keys for use with this Encryption library.
-
.load!(filename = nil, environment = nil) ⇒ Object
Load the Encryption Configuration from a YAML file filename: Name of file to read.
-
.random_password ⇒ Object
Generate a 22 character random password.
-
.secondary_ciphers ⇒ Object
Returns the Primary Symmetric Cipher being used.
-
.secondary_ciphers=(secondary_ciphers) ⇒ Object
Set the Secondary Symmetric Ciphers Array to be used.
-
.select_cipher(&block) ⇒ Object
When no header is present in the encrypted data, this custom Block/Proc is used to determine which cipher to use to decrypt the data.
-
.try_decrypt(str) ⇒ Object
Invokes decrypt Returns decrypted String Return nil if it fails to decrypt a String.
Class Method Details
.cipher(version = nil) ⇒ Object
Returns the Primary Symmetric Cipher being used If a version is supplied
Returns the primary cipher if no match was found and version == 0
Returns nil if no match was found and version != 0
49 50 51 52 53 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 49 def self.cipher(version = nil) raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher return @@cipher if version.nil? || (@@cipher.version == version) secondary_ciphers.find {|c| c.version == version} || (@@cipher if version == 0) end |
.cipher=(cipher) ⇒ Object
Set the Primary Symmetric Cipher to be used
Example: For testing purposes the following test cipher can be used:
SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
key: '1234567890ABCDEF1234567890ABCDEF',
iv: '1234567890ABCDEF',
cipher: 'aes-128-cbc'
)
40 41 42 43 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 40 def self.cipher=(cipher) raise "Cipher must be similar to SymmetricEncryption::Ciphers" unless cipher.nil? || (cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)) @@cipher = cipher end |
.cipher? ⇒ Boolean
Returns whether a primary cipher has been set
56 57 58 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 56 def self.cipher? !@@cipher.nil? end |
.decrypt(encrypted_and_encoded_string, version = nil, type = :string) ⇒ Object
AES Symmetric Decryption of supplied string
Returns decrypted value
Returns nil if the supplied value is nil
Returns "" if it is a string and it is empty
Parameters
str
Encrypted string to decrypt
version
Specify which cipher version to use if no header is present on the
encrypted string
type [:string|:integer|:float|:decimal|:datetime|:time|:date|:boolean]
If value is set to something other than :string, then the coercible gem
will be use to coerce the unencrypted string value into the specified
type. This assumes that the value was stored using the same type.
Note: If type is set to something other than :string, it's expected
that the coercible gem is available in the path.
Default: :string
If the supplied string has an encryption header then the cipher matching
the version number in the header will be used to decrypt the string
When no header is present in the encrypted data, a custom Block/Proc can
be supplied to determine which cipher to use to decrypt the data.
see #cipher_selector=
Raises: OpenSSL::Cipher::CipherError when ‘str’ was not encrypted using the primary key and iv
NOTE: #decrypt will not attempt to use a secondary cipher if it fails
to decrypt the current string. This is because in a very small
yet significant number of cases it is possible to decrypt data using
the incorrect key. Clearly the data returned is garbage, but it still
successfully returns a string of data
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 108 def self.decrypt(encrypted_and_encoded_string, version=nil, type=:string) raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher return encrypted_and_encoded_string if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '') str = encrypted_and_encoded_string.to_s # Decode before decrypting supplied string decoded = @@cipher.decode(str) return unless decoded return decoded if decoded.empty? decrypted = if header = Cipher.parse_header!(decoded) header.decryption_cipher.binary_decrypt(decoded, header) else # Use cipher_selector if present to decide which cipher to use c = @@select_cipher.nil? ? cipher(version) : @@select_cipher.call(str, decoded) c.binary_decrypt(decoded) end if defined?(Encoding) # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding? decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING) end end coerce_from_string(decrypted, type) end |
.encrypt(str, random_iv = false, compress = false, type = :string) ⇒ Object
AES Symmetric Encryption of supplied string
Returns result as a Base64 encoded string
Returns nil if the supplied str is nil
Returns "" if it is a string and it is empty
Parameters
value [Object]
String to be encrypted. If str is not a string, #to_s will be called on it
to convert it to a string
random_iv [true|false]
Whether the encypted value should use a random IV every time the
field is encrypted.
It is recommended to set this to true where feasible. If the encrypted
value could be used as part of a SQL where clause, or as part
of any lookup, then it must be false.
Setting random_iv to true will result in a different encrypted output for
the same input string.
Note: Only set to true if the field will never be used as part of
the where clause in an SQL query.
Note: When random_iv is true it will add a 8 byte header, plus the bytes
to store the random IV in every returned encrypted string, prior to the
encoding if any.
Default: false
Highly Recommended where feasible: true
compress [true|false]
Whether to compress str before encryption
Should only be used for large strings since compression overhead and
the overhead of adding the 'magic' header may exceed any benefits of
compression
Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
Default: false
type [:string|:integer|:float|:decimal|:datetime|:time|:date|:boolean]
Expected data type of the value to encrypt
Uses the coercible gem to coerce non-string values into string values.
When type is set to :string (the default), uses #to_s to convert
non-string values to string values.
Note: If type is set to something other than :string, it's expected that
the coercible gem is available in the path.
Default: :string
178 179 180 181 182 183 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 178 def self.encrypt(str, random_iv=false, compress=false, type=:string) raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher # Encrypt and then encode the supplied string @@cipher.encrypt(coerce_to_string(str, type), random_iv, compress) end |
.encrypted?(encrypted_data) ⇒ Boolean
Returns [true|false] as to whether the data could be decrypted
Parameters:
encrypted_data: Encrypted string
WARNING: This method can only be relied upon if the encrypted data includes the
symmetric encryption header. In some cases data decrypted using the
wrong key will decrypt and return garbage
212 213 214 215 216 217 218 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 212 def self.encrypted?(encrypted_data) raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher # For now have to decrypt it fully result = try_decrypt(encrypted_data) !(result.nil? || result == '') end |
.generate_symmetric_key_files(filename = nil, environment = nil) ⇒ Object
Generate new random symmetric keys for use with this Encryption library
Note: Only the current Encryption key settings are used
Creates Symmetric Key .key
and initilization vector .iv
which is encrypted with the above Public key
Existing key files will be renamed if present
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 273 def self.generate_symmetric_key_files(filename=nil, environment=nil) config_filename = filename || File.join(Rails.root, "config", "symmetric-encryption.yml") config = YAML.load(ERB.new(File.new(config_filename).read).result)[environment || Rails.env] # RSA key to decrypt key files private_rsa_key = config.delete('private_rsa_key') raise "The configuration file must contain a 'private_rsa_key' parameter to generate symmetric keys" unless private_rsa_key rsa_key = OpenSSL::PKey::RSA.new(private_rsa_key) # Check if config file contains 1 or multiple ciphers ciphers = config.delete('ciphers') cfg = ciphers.nil? ? config : ciphers.first # Convert keys to symbols cipher_cfg = {} cfg.each_pair{|k,v| cipher_cfg[k.to_sym] = v} cipher_name = cipher_cfg[:cipher_name] || cipher_cfg[:cipher] # Generate a new Symmetric Key pair iv_filename = cipher_cfg[:iv_filename] key_pair = SymmetricEncryption::Cipher.random_key_pair(cipher_name || 'aes-256-cbc') if key_filename = cipher_cfg[:key_filename] # Save symmetric key after encrypting it with the private RSA key, backing up existing files if present File.rename(key_filename, "#{key_filename}.#{Time.now.to_i}") if File.exist?(key_filename) File.open(key_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:key]) ) } puts("Generated new Symmetric Key for encryption. Please copy #{key_filename} to the other web servers in #{environment}.") elsif !cipher_cfg[:key] key = rsa_key.public_encrypt(key_pair[:key]) puts "Generated new Symmetric Key for encryption. Set the KEY environment variable in #{environment} to:" puts ::Base64.encode64(key) end if iv_filename File.rename(iv_filename, "#{iv_filename}.#{Time.now.to_i}") if File.exist?(iv_filename) File.open(iv_filename, 'wb') {|file| file.write( rsa_key.public_encrypt(key_pair[:iv]) ) } puts("Generated new Symmetric Key for encryption. Please copy #{iv_filename} to the other web servers in #{environment}.") elsif !cipher_cfg[:iv] iv = rsa_key.public_encrypt(key_pair[:iv]) puts "Generated new Symmetric Key for encryption. Set the IV environment variable in #{environment} to:" puts ::Base64.encode64(iv) end end |
.load!(filename = nil, environment = nil) ⇒ Object
Load the Encryption Configuration from a YAML file
filename:
Name of file to read.
Mandatory for non-Rails apps
Default: Rails.root/config/symmetric-encryption.yml
environment:
Which environments config to load. Usually: production, development, etc.
Default: Rails.env
257 258 259 260 261 262 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 257 def self.load!(filename=nil, environment=nil) ciphers = read_config(filename, environment) @@cipher = ciphers.shift @@secondary_ciphers = ciphers true end |
.random_password ⇒ Object
Generate a 22 character random password
319 320 321 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 319 def self.random_password Base64.encode64(OpenSSL::Cipher.new('aes-128-cbc').random_key)[0..-4].strip end |
.secondary_ciphers ⇒ Object
Returns the Primary Symmetric Cipher being used
70 71 72 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 70 def self.secondary_ciphers @@secondary_ciphers end |
.secondary_ciphers=(secondary_ciphers) ⇒ Object
Set the Secondary Symmetric Ciphers Array to be used
61 62 63 64 65 66 67 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 61 def self.secondary_ciphers=(secondary_ciphers) raise "secondary_ciphers must be a collection" unless secondary_ciphers.respond_to? :each secondary_ciphers.each do |cipher| raise "secondary_ciphers can only consist of SymmetricEncryption::Ciphers" unless cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt) end @@secondary_ciphers = secondary_ciphers end |
.select_cipher(&block) ⇒ Object
When no header is present in the encrypted data, this custom Block/Proc is used to determine which cipher to use to decrypt the data.
The Block must return a valid cipher
Parameters
encoded_str
The original encoded string
decoded_str
The string after being decoded using the global encoding
NOTE: Do not attempt to use a secondary cipher if the previous fails
to decrypt due to an OpenSSL::Cipher::CipherError exception.
This is because in a very small, yet significant number of cases it is
possible to decrypt data using the incorrect key.
Clearly the data returned is garbage, but it still successfully
returns a string of data
Example:
SymmetricEncryption.select_cipher do |encoded_str, decoded_str|
# Use cipher version 0 if the encoded string ends with "\n" otherwise
# use the current default cipher
encoded_str.end_with?("\n") ? SymmetricEncryption.cipher(0) : SymmetricEncryption.cipher
end
245 246 247 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 245 def self.select_cipher(&block) @@select_cipher = block ? block : nil end |
.try_decrypt(str) ⇒ Object
Invokes decrypt
Returns decrypted String
Return nil if it fails to decrypt a String
Useful for example when decoding passwords encrypted using a key from a different environment. I.e. We cannot decode production passwords in the test or development environments but still need to be able to load YAML config files that contain encrypted development and production passwords
WARNING: It is possible to decrypt data using the wrong key, so the value
returned should not be relied upon
196 197 198 199 200 201 202 203 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 196 def self.try_decrypt(str) raise "Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data" unless @@cipher begin decrypt(str) rescue OpenSSL::Cipher::CipherError nil end end |