app/services/encryption/aes_cipher_v2.rb
# frozen_string_literal: true
module Encryption
class AesCipherV2
def encrypt(plaintext, cek)
self.cipher = self.class.encryption_cipher
# The key length for the AES-256-GCM cipher is fixed at 128 bits, or 32
# characters. Starting with Ruby 2.4, an expection is thrown if you try to
# set a key longer than 32 characters, which is what we have been doing
# all along. In prior versions of Ruby, the key was silently truncated.
cipher.key = cek[0..31]
encipher(plaintext)
end
def decrypt(payload, cek)
self.cipher = OpenSSL::Cipher.new 'aes-256-gcm'
cipher.decrypt
cipher.key = cek[0..31]
decipher(payload)
end
def self.encryption_cipher
OpenSSL::Cipher.new('aes-256-gcm').encrypt
end
private
attr_accessor :cipher
def encipher(plaintext)
iv = cipher.random_iv
cipher.auth_data = 'PII'
ciphertext = cipher.update(plaintext) << cipher.final
tag = cipher.auth_tag
{ iv: iv, ciphertext: ciphertext, tag: tag }.to_msgpack
end
def decipher(payload)
unpacked_payload = unpack_payload(payload)
cipher.iv = iv(unpacked_payload)
cipher.auth_tag = tag(unpacked_payload)
cipher.auth_data = 'PII'
try_decipher(unpacked_payload)
end
def try_decipher(unpacked_payload)
cipher.update(ciphertext(unpacked_payload)) << cipher.final
rescue OpenSSL::Cipher::CipherError => err
raise EncryptionError, "failed to decipher payload: #{err}"
end
def unpack_payload(payload)
MessagePack.unpack(payload)
rescue StandardError
raise EncryptionError, 'Unable to parse encrypted payload'
end
def iv(unpacked_payload)
unpacked_payload['iv']
end
def tag(unpacked_payload)
unpacked_payload['tag']
end
def ciphertext(unpacked_payload)
unpacked_payload['ciphertext']
end
end
end