Skip to content

Exposing EVP_CIPHER_CTX_ctrl and ECDH_compute_key for AEAD ciphers #19

@russ

Description

@russ

Problem

Crystal's OpenSSL::Cipher doesn't expose methods needed for authenticated encryption modes (AEAD) like AES-GCM. Specifically:

  • No way to set Additional Authenticated Data (AAD)
  • No way to retrieve/set authentication tags
  • No ECDH key agreement support

Use Cases

I've encountered this limitation in two separate production projects:

1. JWE (JSON Web Encryption) with AES-128-GCM

  • Implements RFC 7516 for secure token generation
  • Requires AAD support to authenticate the JWT header alongside the encrypted payload
  • Currently binding LibCrypto directly to call EVP_EncryptUpdate with AAD and retrieve tags via EVP_CIPHER_CTX_ctrl

2. Web Push (RFC 8291) - see github.com/russ/vapid

  • Implements Web Push message encryption
  • Requires ECDH key agreement (ECDH_compute_key) and GCM authentication tags
  • Also binding LibCrypto directly

Current Workaround

Both projects duplicate these LibCrypto bindings:

From internal JWE project:

@[Link("crypto")]
lib LibCrypto
  fun evp_aes_128_gcm = EVP_aes_128_gcm : Void*
  fun evp_cipher_ctx_new = EVP_CIPHER_CTX_new : Void*
  fun evp_cipher_ctx_free = EVP_CIPHER_CTX_free(ctx : Void*)
  fun evp_encryptinit_ex = EVP_EncryptInit_ex(ctx : Void*, type : Void*, impl : Void*, key : UInt8*, iv : UInt8*) : Int32
  fun evp_encryptupdate = EVP_EncryptUpdate(ctx : Void*, out : UInt8*, outl : Int32*, in : UInt8*, inl : Int32) : Int32
  fun evp_encryptfinal_ex = EVP_EncryptFinal_ex(ctx : Void*, out : UInt8*, outl : Int32*) : Int32
  fun evp_cipher_ctx_ctrl = EVP_CIPHER_CTX_ctrl(ctx : Void*, type : Int32, arg : Int32, ptr : Void*) : Int32
  EVP_CTRL_GCM_SET_IVLEN = 0x9
  EVP_CTRL_GCM_GET_TAG   = 0x10
end

From github.com/russ/vapid:

lib LibCrypto
  fun ecdh_compute_key = ECDH_compute_key(out : UInt8*, outlen : LibC::SizeT, pub_key : Void*, ecdh : Void*, kdf : Void*) : LibC::Int
  fun ec_group_get_degree = EC_GROUP_get_degree(group : Void*) : LibC::Int

  # EVP cipher functions
  fun evp_cipher_ctx_new = EVP_CIPHER_CTX_new : Void*
  fun evp_cipher_ctx_free = EVP_CIPHER_CTX_free(ctx : Void*)
  fun evp_get_cipherbyname = EVP_get_cipherbyname(name : LibC::Char*) : Void*
  fun evp_encryptinit_ex = EVP_EncryptInit_ex(ctx : Void*, type : Void*, impl : Void*, key : UInt8*, iv : UInt8*) : LibC::Int
  fun evp_encryptupdate = EVP_EncryptUpdate(ctx : Void*, out : UInt8*, outl : LibC::Int*, in : UInt8*, inl : LibC::Int) : LibC::Int
  fun evp_encryptfinal_ex = EVP_EncryptFinal_ex(ctx : Void*, out : UInt8*, outl : LibC::Int*) : LibC::Int
  fun evp_cipher_ctx_ctrl = EVP_CIPHER_CTX_ctrl(ctx : Void*, type : LibC::Int, arg : LibC::Int, ptr : Void*) : LibC::Int

  # GCM control constants
  EVP_CTRL_GCM_SET_IVLEN = 0x9
  EVP_CTRL_GCM_GET_TAG = 0x10
end

Notes

  • This code is admittedly over my head, but it does work in production environments

Would this be something that makes sense to add to openssl_ext?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions