Skip to content

Add OpenSSL::Cipher#gcm_tag and #gcm_tag= for AEAD ciphers#17096

Open
paulocoghi wants to merge 5 commits into
crystal-lang:masterfrom
paulocoghi:feat/openssl-gcm-authentication-tag
Open

Add OpenSSL::Cipher#gcm_tag and #gcm_tag= for AEAD ciphers#17096
paulocoghi wants to merge 5 commits into
crystal-lang:masterfrom
paulocoghi:feat/openssl-gcm-authentication-tag

Conversation

@paulocoghi

Copy link
Copy Markdown

Motivation and context:

I'm implementing a pure Crystal SWIM protocol (Scalable Weakly-consistent Infection-style Process Group Membership) at github.com/alumna/crystal-swim

I tried to use GCM (AES-256-GCM) provided by OpenSSL from the Crystal's OpenSSL module, to implement payload encryption based on AES-GCM here.

But as you can see in the code, I needed to reopen OpenSSL::Cipher and bind the missing EVP_CIPHER_CTX_ctrl C function.

Please inform me in case there is interest in upstream such fix, which is what this PR does. But contrary to my temporary fix in my shard, this PR reuses and respects the existing logic in Crystal's OpenSSL::Cipher module.

I also added a proper spec test by extending spec/std/openssl/cipher_spec.cr, which is fully passing with:

$ ./bin/crystal spec spec/std/openssl/cipher_spec.cr
...

Finished in 1.27 milliseconds
3 examples, 0 failures, 0 errors, 0 pending

What this PR brings: GCM authentication tag support to OpenSSL::Cipher

This PR adds #gcm_tag and #gcm_tag= methods to OpenSSL::Cipher, enabling the use of authenticated cipher modes such as AES-256-GCM.

Opening the door for AEAD support

Beyond GCM, this PR lays groundwork for broader AEAD support.

Binding EVP_CIPHER_CTX_ctrl (OpenSSL's general cipher control interface) makes it easier to extend OpenSSL::Cipher in the future with features such as:

  • variable tag lengths
  • support for other AEAD modes
  • e.g. ChaCha20-Poly1305, which shares the same tag control constants

Additionally, the existing #authenticated? method previously had no actionable counterpart, and #gcm_tag and #gcm_tag= complete that API surface, making authenticated cipher modes fully usable without resorting to raw LibCrypto calls.

Extra Background

GCM (Galois/Counter Mode) is an AEAD (Authenticated Encryption with Associated Data) mode of operation.

Unlike a plain cipher mode such as CBC, GCM simultaneously encrypts the data and produces a short authentication tag over the ciphertext. During decryption, the tag is verified before the plaintext is returned. Thus, if the ciphertext was tampered with, #final raises an OpenSSL::Cipher::Error. This makes GCM the standard choice for secure network protocols and storage formats.

Crystal's OpenSSL::Cipher already exposes #authenticated? to detect AEAD ciphers, but provided no way to actually retrieve or supply the authentication tag, making AES-GCM (and other AEAD modes) unusable in practice.

Changes

[tl;dr]

  • src/openssl/lib_crypto.cr: Adds the EVP_CTRL_GCM_GET_TAG and EVP_CTRL_GCM_SET_TAG constants, and binds the EVP_CIPHER_CTX_ctrl C function.
  • src/openssl/cipher.cr: Adds two methods:
    • #gcm_tag : Bytes: retrieves the 16-byte authentication tag after encryption (must be called after #final).
    • #gcm_tag=(tag : Bytes) : Bytes: supplies the authentication tag before decryption (must be called before #final).
      Both methods raise OpenSSL::Cipher::Error if called on a non-authenticated cipher.
  • spec/std/openssl/cipher_spec.cr: Adds a round-trip encrypt/decrypt spec for AES-256-GCM.

[detailed]

1. EVP_CIPHER_CTX_ctrl is a general-purpose door, not a GCM-only one

The C function EVP_CIPHER_CTX_ctrl is OpenSSL's generic "send a control command to a cipher context" function. GCM tags are just one of many things it can do. Future contributors could use the same binding to, for example, change the IV length for GCM, set tag lengths other than 16 bytes, or support other AEAD modes like ChaCha20-Poly1305 (which uses the same EVP_CTRL_AEAD_GET_TAG / EVP_CTRL_AEAD_SET_TAG constants, which are aliases for the same values 0x10/0x11).

2. authenticated? seemed a dead-end and this PR tried to complete it

The authenticated? method already existed in OpenSSL::Cipher, letting the code to detect whether a cipher is an AEAD mode. But there was nothing we could actually do with that information. You cannot retrieve or supply the tag in the current shape. This PR makes authenticated? meaningful by providing the corresponding tag API.

3. EVP_GCM_TLS_TAG_LEN was already defined but unused in the public API

The constant EVP_GCM_TLS_TAG_LEN = 16 was already present in lib_crypto.cr (used internally for TLS), but no public method referenced it. The new gcm_tag method uses it as the tag size, making the constant earn its place.

Adds support for retrieving and supplying GCM authentication tags,
enabling authenticated cipher modes such as AES-256-GCM.

- `#gcm_tag` retrieves the 16-byte tag after encryption (post `#final`)
- `#gcm_tag=` supplies the tag before decryption (pre `#final`)

Binds `EVP_CIPHER_CTX_ctrl` and the `EVP_CTRL_GCM_GET_TAG` /
`EVP_CTRL_GCM_SET_TAG` constants in `LibCrypto`.
Comment thread spec/std/openssl/cipher_spec.cr Outdated
Comment thread src/openssl/lib_crypto.cr
Co-authored-by: Johannes Müller <straightshoota@gmail.com>
Comment thread src/openssl/lib_crypto.cr Outdated
Co-authored-by: Sijawusz Pur Rahnama <sija@sija.pl>
Comment thread src/openssl/lib_crypto.cr Outdated
@ysbaddaden ysbaddaden added this to the 1.21.0 milestone Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants