Module: SymmetricEncryption
- Defined in:
- lib/symmetric_encryption/key.rb,
lib/symmetric_encryption/cli.rb,
lib/symmetric_encryption/core.rb,
lib/symmetric_encryption/cipher.rb,
lib/symmetric_encryption/coerce.rb,
lib/symmetric_encryption/config.rb,
lib/symmetric_encryption/header.rb,
lib/symmetric_encryption/reader.rb,
lib/symmetric_encryption/writer.rb,
lib/symmetric_encryption/encoder.rb,
lib/symmetric_encryption/railtie.rb,
lib/symmetric_encryption/rsa_key.rb,
lib/symmetric_encryption/version.rb,
lib/symmetric_encryption/keystore.rb,
lib/symmetric_encryption/exception.rb,
lib/symmetric_encryption/generator.rb,
lib/symmetric_encryption/utils/aws.rb,
lib/symmetric_encryption/utils/files.rb,
lib/symmetric_encryption/keystore/aws.rb,
lib/symmetric_encryption/keystore/gcp.rb,
lib/symmetric_encryption/keystore/file.rb,
lib/symmetric_encryption/keystore/heroku.rb,
lib/symmetric_encryption/keystore/memory.rb,
lib/symmetric_encryption/keystore/environment.rb,
lib/symmetric_encryption/symmetric_encryption.rb,
lib/symmetric_encryption/utils/re_encrypt_files.rb,
lib/symmetric_encryption/railties/attr_encrypted.rb
Overview
Used for re-encrypting encrypted passwords stored in configuration files.
Search for any encrypted value and re-encrypt it using the latest encryption key. Note:
-
Only works with encrypted values that have the standard header.
-
The search looks for the header and then replaces the encrypted value.
-
Example:
re_encrypt = SymmetricEncryption::Utils::ReEncryptFiles.new(version: 4)
re_encrypt.process_directory('../../**/*.yml')
Notes:
-
Only supports the output from encrypting data.
-
I.e. Manually adding newlines to base 64 output is not supported.
-
-
For now only supports one encrypted value per line.
Defined Under Namespace
Modules: Coerce, Encoder, Generator, Keystore, Railties, Utils Classes: CLI, Cipher, CipherError, Config, ConfigError, Error, Header, Key, RSAKey, Railtie, Reader, Writer
Constant Summary collapse
- VERSION =
'4.2.1'.freeze
- 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
%i[string integer float decimal datetime time date boolean json yaml].freeze
- BINARY_ENCODING =
Encoding.find('binary')
- UTF8_ENCODING =
Encoding.find('UTF-8')
- @@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
Decrypt supplied string.
-
.encrypt(str, random_iv: false, compress: false, type: :string, header: cipher.always_add_header) ⇒ 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] whether the string is encrypted.
-
.header(encrypted_and_encoded_string) ⇒ Object
Returns the header for the encrypted string Returns [nil] if no header is present.
-
.load!(file_name = nil, env = nil) ⇒ Object
Load the Encryption Configuration from a YAML file file_name: Name of file to read.
-
.random_password(size = 22) ⇒ Object
Generate a 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 54 55 56 57 58 59 60 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 49 def self.cipher(version = nil) unless cipher? raise( SymmetricEncryption::ConfigError, 'Call SymmetricEncryption.load! or SymmetricEncryption.cipher= prior to encrypting or decrypting data' ) end return @@cipher if version.nil? || (@@cipher.version == version) secondary_ciphers.find { |c| c.version == version } || (@@cipher if version.zero?) 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: '1234567890ABCDEF',
iv: '1234567890ABCDEF',
cipher: 'aes-128-cbc'
)
39 40 41 42 43 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 39 def self.cipher=(cipher) raise(ArgumentError, 'Cipher must respond to :encrypt and :decrypt') unless cipher.nil? || (cipher.respond_to?(:encrypt) && cipher.respond_to?(:decrypt)) @@cipher = cipher end |
.cipher? ⇒ Boolean
Returns whether a primary cipher has been set
63 64 65 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 63 def self.cipher? !@@cipher.nil? end |
.decrypt(encrypted_and_encoded_string, version: nil, type: :string) ⇒ Object
Decrypt supplied string.
Returns [String] the decrypted string.
Returns [nil] if the supplied value is nil.
Returns [''] if it is a string and it is empty.
Parameters
string [String]
Encrypted string to decrypt.
version [Integer]
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
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 117 def self.decrypt(encrypted_and_encoded_string, version: nil, type: :string) 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? header = Header.new decrypted = if header.parse!(decoded) header.cipher.binary_decrypt(decoded, header: header) else c = if version # Supplied version takes preference cipher(version) elsif @@select_cipher # Use cipher_selector if present to decide which cipher to use @@select_cipher.call(str, decoded) else # Global cipher cipher end c.binary_decrypt(decoded) end # Try to force result to UTF-8 encoding, but if it is not valid, force it back to Binary decrypted.force_encoding(SymmetricEncryption::BINARY_ENCODING) unless decrypted.force_encoding(SymmetricEncryption::UTF8_ENCODING).valid_encoding? Coerce.coerce_from_string(decrypted, type) end |
.encrypt(str, random_iv: false, compress: false, type: :string, header: cipher.always_add_header) ⇒ 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
206 207 208 209 210 211 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 206 def self.encrypt(str, random_iv: false, compress: false, type: :string, header: cipher.always_add_header) return str if str.nil? || (str == '') # Encrypt and then encode the supplied string cipher.encrypt(Coerce.coerce_to_string(str, type), random_iv: random_iv, compress: compress, header: header) end |
.encrypted?(encrypted_data) ⇒ Boolean
Returns [true|false] whether the string is encrypted.
Notes:
-
This method only works reliably when the encrypted data includes the symmetric encryption header.
-
nil and ” are considered “encrypted” so that validations do not blow up on empty values.
235 236 237 238 239 240 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 235 def self.encrypted?(encrypted_data) return false if encrypted_data.nil? || (encrypted_data == '') @header ||= SymmetricEncryption.cipher.encoded_magic_header encrypted_data.to_s.start_with?(@header) end |
.header(encrypted_and_encoded_string) ⇒ Object
Returns the header for the encrypted string Returns [nil] if no header is present
153 154 155 156 157 158 159 160 161 162 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 153 def self.header(encrypted_and_encoded_string) return if encrypted_and_encoded_string.nil? || (encrypted_and_encoded_string == '') # Decode before decrypting supplied string decoded = cipher.encoder.decode(encrypted_and_encoded_string.to_s) return if decoded.nil? || decoded.empty? h = Header.new h.parse(decoded).zero? ? nil : h end |
.load!(file_name = nil, env = nil) ⇒ Object
Load the Encryption Configuration from a YAML file
file_name:
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
279 280 281 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 279 def self.load!(file_name = nil, env = nil) Config.load!(file_name: file_name, env: env) end |
.random_password(size = 22) ⇒ Object
Generate a Random password
284 285 286 287 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 284 def self.random_password(size = 22) require 'securerandom' unless defined?(SecureRandom) SecureRandom.urlsafe_base64(size) end |
.secondary_ciphers ⇒ Object
Returns the Primary Symmetric Cipher being used
78 79 80 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 78 def self.secondary_ciphers @@secondary_ciphers end |
.secondary_ciphers=(secondary_ciphers) ⇒ Object
Set the Secondary Symmetric Ciphers Array to be used
68 69 70 71 72 73 74 75 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 68 def self.secondary_ciphers=(secondary_ciphers) raise(ArgumentError, 'secondary_ciphers must be a collection') unless secondary_ciphers.respond_to? :each secondary_ciphers.each do |cipher| raise(ArgumentError, '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
267 268 269 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 267 def self.select_cipher(&block) @@select_cipher = 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
224 225 226 227 228 |
# File 'lib/symmetric_encryption/symmetric_encryption.rb', line 224 def self.try_decrypt(str) decrypt(str) rescue OpenSSL::Cipher::CipherError, SymmetricEncryption::CipherError nil end |