Ciphersuites
A suite selects a key encapsulation mechanism (KEM), key derivation function (KDF), and authenticated cipher (AEAD). Both peers must use the same suite. Sizes below are bytes; IDs are hexadecimal wire codepoints.
open Hpke
let suite =
Suite.create ~kem:Kem.X25519 ~kdf:Kdf.Hkdf_sha256 ~aead:Aead.Aes_128_gcm
let exporter =
Suite.export_only ~kem:Kem.X25519 ~kdf:Kdf.Hkdf_sha256
Registries use OCaml variants. Convert wire IDs with of_int;
unknown IDs return Unsupported_algorithm.
Hybrid KEMs and Draft_hpke_04 require 0.4.0 or newer.
KEM
| Constructor | Registry name | ID | Public | Enc | Private | Secret |
|---|---|---|---|---|---|---|
Kem.P256 | DHKEM(P-256, HKDF-SHA256) | 0x0010 | 65 | 65 | 32 | 32 |
Kem.P384 | DHKEM(P-384, HKDF-SHA384) | 0x0011 | 97 | 97 | 48 | 48 |
Kem.P521 | DHKEM(P-521, HKDF-SHA512) | 0x0012 | 133 | 133 | 66 | 64 |
Kem.X25519 | DHKEM(X25519, HKDF-SHA256) | 0x0020 | 32 | 32 | 32 | 32 |
Kem.X448 | DHKEM(X448, HKDF-SHA512) | 0x0021 | 56 | 56 | 56 | 64 |
Kem.Mlkem512 | ML-KEM-512 | 0x0040 | 800 | 768 | 64 | 32 |
Kem.Mlkem768 | ML-KEM-768 | 0x0041 | 1,184 | 1,088 | 64 | 32 |
Kem.Mlkem1024 | ML-KEM-1024 | 0x0042 | 1,568 | 1,568 | 64 | 32 |
Kem.Mlkem768_p256 | MLKEM768-P256 | 0x0050 | 1,249 | 1,153 | 32 | 32 |
Kem.Mlkem1024_p384 | MLKEM1024-P384 | 0x0051 | 1,665 | 1,665 | 32 | 32 |
Kem.Mlkem768_x25519 | MLKEM768-X25519 (X-Wing) | 0x647a | 1,216 | 1,120 | 32 | 32 |
Public and Private are serialized key sizes;
Enc is the encapsulated key size; Secret
is the shared secret size. Read them through Kem.public_key_size,
private_key_size, encapsulated_key_size, and
secret_size. Kem.supports_auth reports Auth support.
X448 backends
X448 uses curve448's OCaml backend by default. Add
curve448.c to your executable's libraries to select its C
backend. With ocamlfind, link curve448.ocaml or
curve448.c explicitly.
ML-KEM
These KEMs use FIPS 203 ML-KEM from
mlkem
with the HPKE integration from draft-ietf-hpke-pq-05.
They support Base and PSK; Auth and AuthPSK return Unsupported_mode.
- Private keys serialize as a 64-byte seed (
d || z), not an expanded decapsulation key. - Parsing a private key runs key generation; parsing a public key expands its matrix. Parse once and reuse.
- A tampered encapsulation of the correct length produces an unrelated secret without a setup error (implicit rejection). The first open returns
Open_error; export-only protocols must confirm the secret with the peer. derive_key_pairderives the seed using SHAKE256. Store the private key, not just its derivation input: the draft may change.
The draft prefers ML-KEM-768 and ML-KEM-1024 to ML-KEM-512.
mlkem is unaudited and does not guarantee constant-time
execution; read the dependency limits.
Hybrid KEMs
MLKEM768-P256, MLKEM768-X25519 (X-Wing), and MLKEM1024-P384 combine
ML-KEM with an elliptic-curve exchange using SHA3-256. They follow
draft-ietf-hpke-pq-05 and
draft-irtf-cfrg-concrete-hybrid-kems.
Like ML-KEM, they support Base and PSK only.
- Private keys are 32-byte seeds. SHAKE256 expands each into an ML-KEM seed and a curve scalar.
- A public key is the ML-KEM key followed by a group element. An encapsulated key is the ML-KEM ciphertext followed by an ephemeral group element.
- The ML-KEM half uses implicit rejection. An invalid curve point or low-order X25519 value returns
Invalid_encapsulationat receiver setup.
This implementation rejects all-zero X25519 exchanges, although the concrete hybrid draft leaves them unchecked. Honest senders do not produce them. The draft and key-derivation caveats above also apply.
KDF
| Constructor | Name | ID | Hash size | Longest export |
|---|---|---|---|---|
Kdf.Hkdf_sha256 | HKDF-SHA256 | 0x0001 | 32 | 8,160 |
Kdf.Hkdf_sha384 | HKDF-SHA384 | 0x0002 | 48 | 12,240 |
Kdf.Hkdf_sha512 | HKDF-SHA512 | 0x0003 | 64 | 16,320 |
HKDF exports allow 0 through 255 times the hash size. A negative or
excessive length returns Export_length_out_of_range.
SHAKE KDFs
These KDFs are available only in Hpke.Draft_hpke_04.
Its registry also includes the three HKDFs above.
| Constructor | Name | ID | Nh | Longest export |
|---|---|---|---|---|
Draft_hpke_04.Kdf.Shake128 | SHAKE128 | 0x0010 | 32 | 65,535 |
Draft_hpke_04.Kdf.Shake256 | SHAKE256 | 0x0011 | 64 | 65,535 |
The one-stage schedule derives the key, base nonce, and exporter secret
in one LabeledDerive call. Each of info, the PSK,
and its identifier is limited to 65,535 bytes; longer inputs return
Invalid_length. Exports allow 0 through 65,535 bytes.
Nh is the KDF parameter returned by hash_size.
AEAD
| Constructor | Name | ID | Key | Nonce | Tag | Longest plaintext |
|---|---|---|---|---|---|---|
Aead.Aes_128_gcm | AES-128-GCM | 0x0001 | 16 | 12 | 16 | 236 − 32 |
Aead.Aes_256_gcm | AES-256-GCM | 0x0002 | 32 | 12 | 16 | 236 − 32 |
Aead.Chacha20_poly1305 | ChaCha20-Poly1305 | 0x0003 | 32 | 12 | 16 | 238 − 64 |
Suite.export_only | Export-only | 0xFFFF | — | — | — | — |
Export-only is a separate suite type. It supports context setup and export, but cannot be passed to encryption or decryption functions.
Modes
| Mode | ID | Additional input |
|---|---|---|
| Base | 0x00 | None; does not authenticate the sender. |
| PSK | 0x01 | A shared secret of at least 32 bytes and a non-empty identifier. |
| Auth | 0x02 | The sender's static key pair. DHKEM only. |
| AuthPSK | 0x03 | Both a sender key pair and a PSK. DHKEM only. |
Draft_hpke_04 provides Base and PSK only.
Key validation
- All parsers check the exact encoded length.
- NIST public keys must be uncompressed SEC1 points on the curve. Private scalars must be non-zero and below the curve order.
- X25519 and X448 private keys are clamped on parsing and serialization. Low-order public values are rejected when used.
- ML-KEM public keys must pass the FIPS 203 modulus check. Every 64-byte private seed is accepted.
- Hybrid keys validate both components. P-256 and P-384 seed expansion can fail to find a scalar with negligible probability.
Limits
- Exhausting a context's 96-bit sequence returns
Message_limit_reached. - Plaintexts beyond the AEAD limit return
Plaintext_too_long. - Ciphertexts shorter than the tag return
Open_errorwithout advancing the sequence. - Exports follow the KDF limits above and do not advance the sequence.
Not provided
Transport framing, key management, compressed NIST points, expanded ML-KEM private keys, and TurboSHAKE KDFs. Private keys and PSKs have no printers or equality helpers.
Protocol versions
Hpke.Rfc9180 keeps its RFC wire behavior.
Hpke.Draft_hpke_04 implements
draft-ietf-hpke-hpke-04 with Base, PSK, and the SHAKE KDFs.
With HKDF, it produces the same encapsulations, ciphertexts, and exports
as Rfc9180. Keys, AEADs, PSKs, and context types are shared.
let draft_suite =
Draft_hpke_04.Suite.create ~kem:Kem.Mlkem768_x25519
~kdf:Draft_hpke_04.Kdf.Shake256 ~aead:Aead.Chacha20_poly1305
let send ~rng ~recipient ~plaintext =
Draft_hpke_04.seal_base ~rng draft_suite ~recipient
~info:"example-v1" ~aad:"" ~plaintext
A later HPKE draft that changes wire behavior gets a separate module. The ML-KEM and hybrid KEM integrations also follow drafts; pin versions when testing interoperability.