Class: Dry::Credentials::Encryptor

Inherits:
Object
  • Object
show all
Defined in:
lib/dry/credentials/encryptor.rb

Constant Summary collapse

DEFAULT_CIPHER =
'aes-256-gcm'
DEFAULT_DIGEST =
'sha256'
DEFAULT_SERIALIZER =
Marshal
SEPARATOR =
'--'

Instance Method Summary collapse

Constructor Details

#initialize(cipher: DEFAULT_CIPHER, digest: DEFAULT_DIGEST, serializer: DEFAULT_SERIALIZER) ⇒ Encryptor

Returns a new instance of Encryptor.

Parameters:

  • cipher (String) (defaults to: DEFAULT_CIPHER)

    any of OpenSSL::Cipher.ciphers

  • digest (String) (defaults to: DEFAULT_DIGEST)

    any of openssl list

  • serializer (Class) (defaults to: DEFAULT_SERIALIZER)

    must respond to dump and load



18
19
20
21
# File 'lib/dry/credentials/encryptor.rb', line 18

def initialize(cipher: DEFAULT_CIPHER, digest: DEFAULT_DIGEST, serializer: DEFAULT_SERIALIZER)
  @cipher = OpenSSL::Cipher.new(cipher)
  @digest, @serializer = digest, serializer
end

Instance Method Details

#decrypt(encrypted_object, key:) ⇒ Object

Decrypts the encrypted object

Parameters:

  • encrypted_object (String)

    encrypted object to be decrypted

  • key (String)

    key (Base64 encoded and unpacked to hex)

Returns:

  • (Object)

    verified and decrypted object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/dry/credentials/encryptor.rb', line 60

def decrypt(encrypted_object, key:)
  @cipher.decrypt
  @cipher.key = decoded_key = decode(pack(key.strip))
  payload, iv, auth_tag = encrypted_object.strip.split(SEPARATOR)
  if auth_tag.nil? ||
    (aead? && decode(auth_tag).bytes.length != auth_tag_length) ||
    (!aead? && hmac(decoded_key, payload + SEPARATOR + iv) != auth_tag)
  then
    fail Dry::Credentials::InvalidEncryptedObjectError
  end
  @cipher.iv = decode(iv)
  if aead?
    @cipher.auth_tag = decode(auth_tag)
    @cipher.auth_data = ''
  end
  @cipher.update(decode(payload)).then do |data|
    data << @cipher.final
    @serializer.load(data)
  end
rescue OpenSSL::Cipher::CipherError, TypeError, ArgumentError
  raise Dry::Credentials::InvalidEncryptedObjectError
end

#encrypt(object, key:) ⇒ String

Encrypts the object

Relies on encrypted authenticated encryption mode if available for the selected cipher. Otherwise, the encrypted string is HMAC signed.

Parameters:

  • object (Object)

    object to be encrypted

  • key (String)

    key (Base64 encoded and unpacked to hex)

Returns:

  • (String)

    encrypted and authenticated/signed string



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/dry/credentials/encryptor.rb', line 39

def encrypt(object, key:)
  @cipher.encrypt
  @cipher.key = decoded_key = decode(pack(key.strip))
  iv = @cipher.random_iv
  @cipher.auth_data = '' if aead?
  @cipher.update(@serializer.dump(object)).then do |data|
    data << @cipher.final
    data = encode(data) + SEPARATOR + encode(iv)
    data << SEPARATOR + if aead?
      encode(@cipher.auth_tag)
    else
      hmac(decoded_key, data)
    end
  end
end

#generate_keyString

Generate a random key with the length required by the current cipher, then Base64 encodes and unpacks all bytes to hex.

Returns:

  • (String)

    key



27
28
29
# File 'lib/dry/credentials/encryptor.rb', line 27

def generate_key
  unpack(encode(SecureRandom.bytes(@cipher.key_len)))
end