Examples

This page provides complete, runnable examples for common HPKE use cases.

Complete Base Mode Example

from rfc9180 import HPKE, KEMID, KDFID, AEADID, create_hpke

# Create HPKE with default ciphersuite (X25519 + HKDF-SHA256 + AES-128-GCM)
hpke = create_hpke()

# Generate recipient key pair
skR, pkR = hpke.generate_key_pair()

# Application context
info = b"my-app-v1"
aad = b"metadata"
plaintext = b"Hello, HPKE!"

# Encrypt
enc, ciphertext = hpke.seal_base(pkR, info, aad, plaintext)

# Decrypt
decrypted = hpke.open_base(enc, skR, info, aad, ciphertext)
assert decrypted == plaintext

print("Encryption successful!")

Auth Mode with Sender Authentication

from rfc9180 import HPKE, KEMID, KDFID, AEADID, create_hpke

hpke = create_hpke()

# Recipient keys
skR, pkR = hpke.generate_key_pair()
# Sender keys (for authentication)
skS, pkS = hpke.generate_key_pair()

info = b"authenticated-channel"
aad = b""
plaintext = b"Sensitive data"

# Sender: encrypt with sender's private key (proves identity)
enc, ct = hpke.seal_auth(pkR, info, aad, plaintext, skS)

# Recipient: decrypt and verify sender's public key
pt = hpke.open_auth(enc, skR, info, aad, ct, pkS)
assert pt == plaintext

PSK Mode with Pre-Shared Key

from rfc9180 import HPKE, create_hpke

hpke = create_hpke()

# PSK must have at least 32 bytes of entropy
psk = b"0" * 32  # In practice, use cryptographically random bytes
psk_id = b"session-12345"

skR, pkR = hpke.generate_key_pair()
info = b"psk-channel"
aad = b""
plaintext = b"Shared secret protects this"

enc, ct = hpke.seal_psk(pkR, info, aad, plaintext, psk, psk_id)
pt = hpke.open_psk(enc, skR, info, aad, ct, psk, psk_id)
assert pt == plaintext

Multi-Message Context (Streaming)

from rfc9180 import create_hpke

hpke = create_hpke()
skR, pkR = hpke.generate_key_pair()

info = b"streaming-session"
aad = b""

# Sender: setup once, encrypt many
enc, ctx_sender = hpke.setup.setup_base_sender(pkR, info)
ct1 = ctx_sender.seal(aad, b"message 1")
ct2 = ctx_sender.seal(aad, b"message 2")
ct3 = ctx_sender.seal(aad, b"message 3")

# Recipient: setup once, decrypt many
ctx_recipient = hpke.setup.setup_base_recipient(enc, skR, info)
assert ctx_recipient.open(aad, ct1) == b"message 1"
assert ctx_recipient.open(aad, ct2) == b"message 2"
assert ctx_recipient.open(aad, ct3) == b"message 3"

Export-Only (Key Agreement)

from rfc9180 import create_hpke

hpke = create_hpke()
skR, pkR = hpke.generate_key_pair()

info = b"key-agreement"
exporter_context = b"shared-secret-context"
length = 32

# Sender derives shared secret
enc, ctx_sender = hpke.setup.setup_base_sender(pkR, info)
secret_sender = ctx_sender.export(exporter_context, length)

# Recipient derives same secret
ctx_recipient = hpke.setup.setup_base_recipient(enc, skR, info)
secret_recipient = ctx_recipient.export(exporter_context, length)

assert secret_sender == secret_recipient
# Use secret_sender/secret_recipient for further crypto (e.g., HMAC, KDF)

Keys from JWK

import json
from rfc9180 import HPKE, KEMKey, create_hpke

hpke = create_hpke()

# Generate keys and convert to JWK-like format for demo
skR, pkR = hpke.generate_key_pair()
pk_bytes = hpke.serialize_public_key(pkR)

# Simulate loading from JWK (in practice, load from JSON/API)
# For X25519, JWK has kty="OKP", crv="X25519", x=<base64url>
from base64 import urlsafe_b64encode
x_b64 = urlsafe_b64encode(pk_bytes).rstrip(b"=").decode("ascii")
jwk = {"kty": "OKP", "crv": "X25519", "x": x_b64}

# Load key from JWK
pk_from_jwk = KEMKey.from_jwk(jwk)
enc, ct = hpke.seal_base(pk_from_jwk, b"info", b"aad", b"secret")
pt = hpke.open_base(enc, skR, b"info", b"aad", ct)
assert pt == b"secret"

Self-Describing Wire Format

from rfc9180 import create_hpke, append_header, parse_header
from rfc9180.constants import HPKEMode

hpke = create_hpke()
skR, pkR = hpke.generate_key_pair()

info = b"app-info"
aad = b""
plaintext = b"Message with self-describing header"

enc, ct = hpke.seal_base(pkR, info, aad, plaintext)

# Encode: prepend HPKE header (kem_id, kdf_id, aead_id, mode)
msg = append_header(
    enc, ct,
    int(hpke.kem_id), int(hpke.kdf_id), int(hpke.aead_id),
    int(HPKEMode.MODE_BASE),
)

# Parse: extract parameters and enc/ct from received message
kem_id, kdf_id, aead_id, mode, enc2, ct2 = parse_header(msg, enc_len=hpke.kem.Nenc)
pt = hpke.open_base(enc2, skR, info, aad, ct2)
assert pt == plaintext

Different Ciphersuites

from rfc9180 import HPKE, KEMID, KDFID, AEADID

# ChaCha20-Poly1305 (no AES, good for constrained environments)
hpke_chacha = HPKE(
    KEMID.DHKEM_X25519_HKDF_SHA256,
    KDFID.HKDF_SHA256,
    AEADID.CHACHA20_POLY1305,
)

# P-256 (NIST curves)
hpke_p256 = HPKE(
    KEMID.DHKEM_P256_HKDF_SHA256,
    KDFID.HKDF_SHA256,
    AEADID.AES_128_GCM,
)

# X448 + SHA-512 (higher security margin)
hpke_x448 = HPKE(
    KEMID.DHKEM_X448_HKDF_SHA512,
    KDFID.HKDF_SHA512,
    AEADID.AES_256_GCM,
)

# Use any of them
sk, pk = hpke_chacha.generate_key_pair()
enc, ct = hpke_chacha.seal_base(pk, b"info", b"", b"test")
pt = hpke_chacha.open_base(enc, sk, b"info", b"", ct)