1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
module Lockbox
class AES_GCM
def initialize(key)
raise ArgumentError, "Key must be 32 bytes" unless key && key.bytesize == 32
raise ArgumentError, "Key must be binary" unless key.encoding == Encoding::BINARY
@key = key
end
def encrypt(nonce, message, associated_data)
cipher = OpenSSL::Cipher.new("aes-256-gcm")
# do not change order of operations
cipher.encrypt
cipher.key = @key
cipher.iv = nonce
# From Ruby 2.5.3 OpenSSL::Cipher docs:
# If no associated data shall be used, this method must still be called with a value of ""
# In encryption mode, it must be set after calling #encrypt and setting #key= and #iv=
cipher.auth_data = associated_data || ""
ciphertext = String.new
ciphertext << cipher.update(message) unless message.empty?
ciphertext << cipher.final
ciphertext << cipher.auth_tag
ciphertext
end
def decrypt(nonce, ciphertext, associated_data)
auth_tag, ciphertext = extract_auth_tag(ciphertext.to_s)
fail_decryption if nonce.to_s.bytesize != nonce_bytes
fail_decryption if auth_tag.to_s.bytesize != auth_tag_bytes
cipher = OpenSSL::Cipher.new("aes-256-gcm")
# do not change order of operations
cipher.decrypt
cipher.key = @key
cipher.iv = nonce
cipher.auth_tag = auth_tag
# From Ruby 2.5.3 OpenSSL::Cipher docs:
# If no associated data shall be used, this method must still be called with a value of ""
# When decrypting, set it only after calling #decrypt, #key=, #iv= and #auth_tag= first.
cipher.auth_data = associated_data || ""
begin
message = String.new
message << cipher.update(ciphertext) unless ciphertext.to_s.empty?
message << cipher.final
message
rescue OpenSSL::Cipher::CipherError
fail_decryption
end
end
def nonce_bytes
12
end
# protect key
def inspect
to_s
end
private
def auth_tag_bytes
16
end
def extract_auth_tag(bytes)
auth_tag = bytes.slice(-auth_tag_bytes..-1)
[auth_tag, bytes.slice(0, bytes.bytesize - auth_tag_bytes)]
end
def fail_decryption
raise DecryptionError, "Decryption failed"
end
end
end
|