Class: SymmetricEncryption::Cipher
- Inherits:
-
Object
- Object
- SymmetricEncryption::Cipher
- Defined in:
- lib/symmetric_encryption/cipher.rb
Overview
Hold all information related to encryption keys as well as encrypt and decrypt data using those keys.
Cipher is thread safe so that the same instance can be called by multiple threads at the same time without needing an instance of Cipher per thread.
Instance Attribute Summary collapse
-
#always_add_header ⇒ Object
Cipher to use for encryption and decryption.
-
#cipher_name ⇒ Object
Cipher to use for encryption and decryption.
-
#encoding ⇒ Object
Returns the value of attribute encoding.
-
#iv ⇒ Object
Cipher to use for encryption and decryption.
-
#key ⇒ Object
writeonly
Sets the attribute key.
-
#version ⇒ Object
Cipher to use for encryption and decryption.
Class Method Summary collapse
-
.build_header(version, compress = false, iv = nil, key = nil, cipher_name = nil) ⇒ Object
DEPRECATED.
-
.from_config(cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict, **config) ⇒ Object
Returns [Cipher] from a cipher config instance.
-
.has_header?(buffer) ⇒ Boolean
DEPRECATED.
-
.parse_header!(buffer) ⇒ Object
DEPRECATED.
Instance Method Summary collapse
-
#binary_decrypt(encrypted_string, header: Header.new) ⇒ Object
Advanced use only See #decrypt to decrypt encoded strings.
-
#binary_encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) ⇒ Object
Advanced use only.
-
#block_size ⇒ Object
Returns the block size for the configured cipher_name.
-
#decode(encoded_string) ⇒ Object
Decode the supplied string using the encoding in this cipher instance Note: No encryption or decryption is performed.
-
#decrypt(str) ⇒ Object
Decode and Decrypt string Returns a decrypted string after decoding it first according to the encoding setting of this cipher Returns nil if encrypted_string is nil Returns ” if encrypted_string == ”.
-
#encode(binary_string) ⇒ Object
Returns UTF8 encoded string after encoding the supplied Binary string.
-
#encoded_magic_header ⇒ Object
Returns the magic header after applying the encoding in this cipher.
-
#encoder ⇒ Object
Returns [SymmetricEncryption::Encoder] the encoder to use for the current encoding.
-
#encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) ⇒ Object
Encrypt and then encode a string.
-
#initialize(key:, iv: nil, cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict) ⇒ Cipher
constructor
Returns [SymmetricEncryption::Cipher] for encryption and decryption purposes.
-
#inspect ⇒ Object
Returns [String] object represented as a string, filtering out the key.
-
#random_iv ⇒ Object
Return a new random IV using the configured cipher_name Useful for generating new symmetric keys.
-
#random_key ⇒ Object
Return a new random key using the configured cipher_name Useful for generating new symmetric keys.
Constructor Details
#initialize(key:, iv: nil, cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict) ⇒ Cipher
Returns [SymmetricEncryption::Cipher] for encryption and decryption purposes.
Parameters:
key [String]
The Symmetric Key to use for encryption and decryption.
iv [String]
The Initialization Vector to use.
cipher_name [String]
Optional. Encryption Cipher to use
Default: aes-256-cbc
encoding [Symbol]
:base64strict
Return as a base64 encoded string that does not include additional newlines
This is the recommended format since newlines in the values to
SQL queries are cumbersome. Also the newline reformatting is unnecessary
It is not the default for backward compatibility
:base64urlsafe
Same as base64strict except that base64urlsafe uses '-' instead of '+' and '_' instead of '/'.
:base64
Return as a base64 encoded string
:base16
Return as a Hex encoded string
:none
Return as raw binary data string. Note: String can contain embedded nulls
Default: :base64strict
version [Integer]
Optional. The version number of this encryption key
Used by SymmetricEncryption to select the correct key when decrypting data
Valid Range: 0..255
Default: 1
always_add_header [true|false]
Whether to always include the header when encrypting data.
** Highly recommended to set this value to true **
Increases the length of the encrypted data by a few bytes, but makes
migration to a new key trivial
Default: true
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/symmetric_encryption/cipher.rb', line 75 def initialize(key:, iv: nil, cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict) @key = key @iv = iv @cipher_name = cipher_name self.encoding = encoding.to_sym @version = version.to_i @always_add_header = always_add_header return unless (@version > 255) || @version.negative? raise(ArgumentError, "Cipher version has a valid range of 0 to 255. #{@version} is too high, or negative") end |
Instance Attribute Details
#always_add_header ⇒ Object
Cipher to use for encryption and decryption
10 11 12 |
# File 'lib/symmetric_encryption/cipher.rb', line 10 def always_add_header @always_add_header end |
#cipher_name ⇒ Object
Cipher to use for encryption and decryption
10 11 12 |
# File 'lib/symmetric_encryption/cipher.rb', line 10 def cipher_name @cipher_name end |
#encoding ⇒ Object
Returns the value of attribute encoding.
11 12 13 |
# File 'lib/symmetric_encryption/cipher.rb', line 11 def encoding @encoding end |
#iv ⇒ Object
Cipher to use for encryption and decryption
10 11 12 |
# File 'lib/symmetric_encryption/cipher.rb', line 10 def iv @iv end |
#key=(value) ⇒ Object
Sets the attribute key
12 13 14 |
# File 'lib/symmetric_encryption/cipher.rb', line 12 def key=(value) @key = value end |
#version ⇒ Object
Cipher to use for encryption and decryption
10 11 12 |
# File 'lib/symmetric_encryption/cipher.rb', line 10 def version @version end |
Class Method Details
.build_header(version, compress = false, iv = nil, key = nil, cipher_name = nil) ⇒ Object
DEPRECATED
356 357 358 359 |
# File 'lib/symmetric_encryption/cipher.rb', line 356 def self.build_header(version, compress = false, iv = nil, key = nil, cipher_name = nil) h = Header.new(version: version, compress: compress, iv: iv, key: key, cipher_name: cipher_name) h.to_s end |
.from_config(cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict, **config) ⇒ Object
Returns [Cipher] from a cipher config instance.
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/symmetric_encryption/cipher.rb', line 15 def self.from_config(cipher_name: "aes-256-cbc", version: 0, always_add_header: true, encoding: :base64strict, **config) Keystore.migrate_config!(config) key = Keystore.read_key(cipher_name: cipher_name, **config) Cipher.new( key: key.key, iv: key.iv, cipher_name: cipher_name, version: version, always_add_header: always_add_header, encoding: encoding ) end |
.has_header?(buffer) ⇒ Boolean
DEPRECATED
345 346 347 |
# File 'lib/symmetric_encryption/cipher.rb', line 345 def self.has_header?(buffer) SymmetricEncryption::Header.present?(buffer) end |
.parse_header!(buffer) ⇒ Object
DEPRECATED
350 351 352 353 |
# File 'lib/symmetric_encryption/cipher.rb', line 350 def self.parse_header!(buffer) header = SymmetricEncryption::Header.new header.parse!(buffer) ? header : nil end |
Instance Method Details
#binary_decrypt(encrypted_string, header: Header.new) ⇒ Object
Advanced use only See #decrypt to decrypt encoded strings
Returns a Binary decrypted string without decoding the string first The returned string has BINARY encoding
Decryption of supplied string
Returns the decrypted string
Returns nil if encrypted_string is nil
Returns '' if encrypted_string == ''
Parameters
encrypted_string [String]
Binary encrypted string to decrypt
header [SymmetricEncryption::Header]
Optional header for the supplied encrypted_string
Reads the ‘magic’ header if present for key, iv, cipher_name and compression
encrypted_string must be in raw binary form when calling this method
Creates a new OpenSSL::Cipher with every call so that this call is thread-safe and can be called concurrently by multiple threads with the same instance of Cipher
Note:
When a string is encrypted and the header is used, its decrypted form
is automatically set to the same UTF-8 or Binary encoding
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
# File 'lib/symmetric_encryption/cipher.rb', line 313 def binary_decrypt(encrypted_string, header: Header.new) return if encrypted_string.nil? str = encrypted_string.to_s str.force_encoding(SymmetricEncryption::BINARY_ENCODING) return str if str.empty? offset = header.parse(str) data = offset.positive? ? str[offset..-1] : str openssl_cipher = ::OpenSSL::Cipher.new(header.cipher_name || cipher_name) openssl_cipher.decrypt openssl_cipher.key = header.key || @key if (iv = header.iv || @iv) openssl_cipher.iv = iv end result = openssl_cipher.update(data) result << openssl_cipher.final header.compressed? ? Zlib::Inflate.inflate(result) : result end |
#binary_encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) ⇒ Object
Advanced use only
Returns a Binary encrypted string without applying Base64, or any other encoding.
str [String]
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.
Notes:
* Setting random_iv to true will result in a different encrypted output for
the same input string.
* It is recommended to set this to true, except if it will be used as a lookup key.
* Only set to true if the field will never be used as a lookup key, since
the encrypted value needs to be same every time in this case.
* When random_iv is true it adds the random IV string to the header.
Default: false
Highly Recommended where feasible: true
compress [true|false]
Whether to compress str before encryption.
Default: false
Notes:
* Should only be used for large strings since compression overhead and
the overhead of adding the encryption header may exceed any benefits of
compression
header [true|false]
Whether to add a header to the encrypted string.
Default: `always_add_header`
See #encrypt to encrypt and encode the result as a string.
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/symmetric_encryption/cipher.rb', line 255 def binary_encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) return if str.nil? string = str.to_s return string if string.empty? # Header required when adding a random_iv or compressing header = Header.new(version: version, compress: compress) if header || random_iv || compress # Creates a new OpenSSL::Cipher with every call so that this call is thread-safe. openssl_cipher = ::OpenSSL::Cipher.new(cipher_name) openssl_cipher.encrypt openssl_cipher.key = @key result = if header if random_iv openssl_cipher.iv = header.iv = openssl_cipher.random_iv elsif iv openssl_cipher.iv = iv end header.to_s + openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string) else openssl_cipher.iv = iv if iv openssl_cipher.update(string) end result << openssl_cipher.final end |
#block_size ⇒ Object
Returns the block size for the configured cipher_name
217 218 219 |
# File 'lib/symmetric_encryption/cipher.rb', line 217 def block_size ::OpenSSL::Cipher.new(cipher_name).block_size end |
#decode(encoded_string) ⇒ Object
Decode the supplied string using the encoding in this cipher instance Note: No encryption or decryption is performed
Returned string is Binary encoded
198 199 200 201 202 |
# File 'lib/symmetric_encryption/cipher.rb', line 198 def decode(encoded_string) return encoded_string if encoded_string.nil? || (encoded_string == "") encoder.decode(encoded_string) end |
#decrypt(str) ⇒ Object
Decode and Decrypt string
Returns a decrypted string after decoding it first according to the
encoding setting of this cipher
Returns nil if encrypted_string is nil
Returns '' if encrypted_string == ''
Parameters
encrypted_string [String]
Binary encrypted string to decrypt
Reads the header if present for key, iv, cipher_name and compression
encrypted_string must be in raw binary form when calling this method
Creates a new OpenSSL::Cipher with every call so that this call is thread-safe and can be called concurrently by multiple threads with the same instance of Cipher
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'lib/symmetric_encryption/cipher.rb', line 165 def decrypt(str) decoded = decode(str) return unless decoded return decoded if decoded.empty? decrypted = binary_decrypt(decoded) # 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 decrypted end |
#encode(binary_string) ⇒ Object
Returns UTF8 encoded string after encoding the supplied Binary string
Encode the supplied string using the encoding in this cipher instance Returns nil if the supplied string is nil Note: No encryption or decryption is performed
Returned string is UTF8 encoded except for encoding :none
188 189 190 191 192 |
# File 'lib/symmetric_encryption/cipher.rb', line 188 def encode(binary_string) return binary_string if binary_string.nil? || (binary_string == "") encoder.encode(binary_string) end |
#encoded_magic_header ⇒ Object
Returns the magic header after applying the encoding in this cipher
335 336 337 |
# File 'lib/symmetric_encryption/cipher.rb', line 335 def encoded_magic_header @encoded_magic_header ||= encoder.encode(SymmetricEncryption::Header::MAGIC_HEADER).delete("=").strip end |
#encoder ⇒ Object
Returns [SymmetricEncryption::Encoder] the encoder to use for the current encoding.
101 102 103 |
# File 'lib/symmetric_encryption/cipher.rb', line 101 def encoder @encoder ||= SymmetricEncryption::Encoder[encoding] end |
#encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) ⇒ Object
Encrypt and then encode a string
Returns data encrypted and then encoded according to the encoding setting
of this cipher
Returns nil if str is nil Returns “” str is empty
Parameters
str [String]
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 encrypted value should use a random IV every time the
field is encrypted.
Notes:
* Setting random_iv to true will result in a different encrypted output for
the same input string.
* It is recommended to set this to true, except if it will be used as a lookup key.
* Only set to true if the field will never be used as a lookup key, since
the encrypted value needs to be same every time in this case.
* When random_iv is true it adds the random IV string to the header.
Default: false
Highly Recommended where feasible: true
compress [true|false]
Whether to compress str before encryption.
Default: false
Notes:
* Should only be used for large strings since compression overhead and
the overhead of adding the encryption header may exceed any benefits of
compression
138 139 140 141 142 143 144 145 146 |
# File 'lib/symmetric_encryption/cipher.rb', line 138 def encrypt(str, random_iv: SymmetricEncryption.randomize_iv?, compress: false, header: always_add_header) return if str.nil? str = str.to_s return str if str.empty? encrypted = binary_encrypt(str, random_iv: random_iv, compress: compress, header: header) encode(encrypted) end |
#inspect ⇒ Object
Returns [String] object represented as a string, filtering out the key
340 341 342 |
# File 'lib/symmetric_encryption/cipher.rb', line 340 def inspect "#<#{self.class}:0x#{__id__.to_s(16)} @key=\"[FILTERED]\" @iv=#{iv.inspect} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}, @always_add_header=#{always_add_header.inspect}>" end |
#random_iv ⇒ Object
Return a new random IV using the configured cipher_name Useful for generating new symmetric keys
212 213 214 |
# File 'lib/symmetric_encryption/cipher.rb', line 212 def random_iv ::OpenSSL::Cipher.new(cipher_name).random_iv end |
#random_key ⇒ Object
Return a new random key using the configured cipher_name Useful for generating new symmetric keys
206 207 208 |
# File 'lib/symmetric_encryption/cipher.rb', line 206 def random_key ::OpenSSL::Cipher.new(cipher_name).random_key end |