sender pkR encap shared secret key schedule seal recipient public key enc ciphertext recipient skR decap same secret key schedule open recipient private key sender pkR encap key schedule seal recipient public key enc ciphertext recipient skR decap key schedule open recipient private key

hpke

An idiomatic OCaml implementation of Hybrid Public Key Encryption, RFC 9180. The Base, PSK, Auth, and AuthPSK modes live under the explicitly versioned Hpke.Rfc9180 module; elliptic-curve, hash, and AEAD primitives are delegated to Mirage Crypto, curve448, Digestif, and kdf.

Registry implemented
Component Algorithms Count
KEMDHKEM over P-256, P-384, P-521, X25519, X4485
KDFHKDF-SHA256, HKDF-SHA384, HKDF-SHA5123
AEADAES-128-GCM, AES-256-GCM, ChaCha20-Poly13053
ModesBase, PSK, Auth, AuthPSK4

Every KEM/KDF/AEAD combination is exercised by the test suite, and export-only operation is a separate suite type rather than a special AEAD value. Codepoints, sizes, and deliberate omissions: suites.

Release 0.2.0 is for interoperability review. It has not received an independent cryptographic audit. Read the security model before using it with sensitive data.

Current state
Release0.2.0
OCaml4.14 or newer
Dune3.12 or newer
ModesBase, PSK, Auth, and AuthPSK
Ciphersuites45, plus 15 export-only
LicenseISC

Quick start

opam install hpke

opam-repository takes a new release only after review, so it can trail the newest tag. Pinning a release from source until it catches up: install.

Depend on the library, and on a Unix RNG entry point for applications:

(executable
 (name main)
 (libraries hpke mirage-crypto-rng.unix))

Seal one message and open it again:

open Hpke

let () =
  Mirage_crypto_rng_unix.use_default ();
  let rng = Mirage_crypto_rng.default_generator () in
  let suite =
    Suite.create ~kem:Kem.X25519 ~kdf:Kdf.Hkdf_sha256
      ~aead:Aead.Chacha20_poly1305
  in
  match generate_key_pair ~rng Kem.X25519 with
  | Error error -> Format.eprintf "keygen: %a@." Error.pp error
  | Ok (private_key, public_key) -> (
      match
        Rfc9180.seal_base ~rng suite ~recipient:public_key
          ~info:"application-v1" ~aad:"message-metadata"
          ~plaintext:"secret payload"
      with
      | Error error -> Format.eprintf "seal: %a@." Error.pp error
      | Ok ciphertext -> (
          match
            Rfc9180.open_base suite ~recipient:private_key
              ~info:"application-v1" ~aad:"message-metadata"
              ~ciphertext
          with
          | Ok text -> assert (String.equal text "secret payload")
          | Error error ->
              Format.eprintf "open: %a@." Error.pp error))

The result carries encapsulated_key and ciphertext as separate fields. The library does not invent an application wire format: framing those two strings, and binding them to the surrounding protocol, is the application's job.

Choose an API

Single-shot
Best default. seal_base and open_base establish a context, move one message, and discard it. Peer-controlled failures come back as Open_error.
Stateful context
Use setup_base_sender and setup_base_receiver when one encapsulation carries many messages. Each successful operation consumes exactly one nonce.
Export-only
Build a suite with Suite.export_only to derive keys for another protocol. Such a suite cannot be passed to seal — the type checker rejects it.

Continue with usage, or read the security model for what this library does and does not defend against.