Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions spec/std/openssl/cipher_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,27 @@ describe OpenSSL::Cipher do
cipher = OpenSSL::Cipher.new("aes-128-cbc")
cipher.authenticated?.should be_false
end

it "encrypts/decrypts with GCM authentication tag" do
key = Random::Secure.random_bytes(32)
iv = Random::Secure.random_bytes(12)
plaintext = "Hello, GCM!"

enc = OpenSSL::Cipher.new("aes-256-gcm")
enc.encrypt
enc.key = key
enc.iv = iv
ciphertext = enc.update(plaintext)
ciphertext += enc.final
tag = enc.gcm_tag

dec = OpenSSL::Cipher.new("aes-256-gcm")
dec.decrypt
dec.key = key
dec.iv = iv
dec.gcm_tag = tag
result = dec.update(ciphertext)
result += dec.final
String.new(result).should eq(plaintext)
end
end
23 changes: 23 additions & 0 deletions src/openssl/cipher.cr
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,29 @@ class OpenSSL::Cipher
LibCrypto.evp_cipher_flags(cipher).includes?(LibCrypto::CipherFlags::EVP_CIPH_FLAG_AEAD_CIPHER)
end

# Returns the authentication tag for an AEAD cipher (e.g. AES-GCM).
# Must be called after `#final` during encryption.
# Raises `Error` if the cipher is not authenticated.
def gcm_tag : Bytes
raise Error.new("Cipher is not authenticated") unless authenticated?
tag = Bytes.new(LibCrypto::EVP_GCM_TLS_TAG_LEN)
if LibCrypto.evp_cipher_ctx_ctrl(@ctx, LibCrypto::EVP_CTRL_GCM_GET_TAG, tag.size, tag.to_unsafe.as(Void*)) != 1
raise Error.new "EVP_CIPHER_CTX_ctrl GET_TAG"
end
tag
end

# Sets the authentication tag for an AEAD cipher (e.g. AES-GCM).
# Must be called before `#final` during decryption.
# Raises `Error` if the cipher is not authenticated.
def gcm_tag=(tag : Bytes) : Bytes
raise Error.new("Cipher is not authenticated") unless authenticated?
if LibCrypto.evp_cipher_ctx_ctrl(@ctx, LibCrypto::EVP_CTRL_GCM_SET_TAG, tag.size, tag.to_unsafe.as(Void*)) != 1
raise Error.new "EVP_CIPHER_CTX_ctrl SET_TAG"
end
tag
end

private def cipherinit(cipher = nil, engine = nil, key = nil, iv = nil, enc = -1)
if LibCrypto.evp_cipherinit_ex(@ctx, cipher, engine, key, iv, enc) != 1
raise Error.new "EVP_CipherInit_ex"
Expand Down
4 changes: 4 additions & 0 deletions src/openssl/lib_crypto.cr
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ lib LibCrypto
EVP_MAX_IV_LENGTH = 16
EVP_GCM_TLS_TAG_LEN = 16

EVP_CTRL_GCM_GET_TAG = 0x10
EVP_CTRL_GCM_SET_TAG = 0x11
Comment thread
paulocoghi marked this conversation as resolved.

SSL3_RT_HEADER_LENGTH = 5

TLS1_2_VERSION_MAJOR = 0x03
Expand Down Expand Up @@ -258,6 +261,7 @@ lib LibCrypto
fun evp_cipherfinal_ex = EVP_CipherFinal_ex(ctx : EVP_CIPHER_CTX, out : UInt8*, outl : Int32*) : Int32
fun evp_cipher_ctx_set_padding = EVP_CIPHER_CTX_set_padding(ctx : EVP_CIPHER_CTX, padding : Int32) : Int32
fun evp_cipher_ctx_cipher = EVP_CIPHER_CTX_cipher(ctx : EVP_CIPHER_CTX) : EVP_CIPHER
fun evp_cipher_ctx_ctrl = EVP_CIPHER_CTX_ctrl(ctx : EVP_CIPHER_CTX, type : Int32, arg : Int32, ptr : Void*) : Int32

@[Flags]
enum CipherFlags : ULong
Expand Down
Loading