zig-hpke
is an implementation of the Hybrid Public Key Encryption (HPKE) scheme.
This code heavily relies on the std.BoundedArray
type: a type to store small, variable-sized slices whose maximum size is known.
Keys are typically represented using that type, whose raw slice can be accessed with the constSlice()
function (for a constant slice), or slice()
(for a mutable slice).
const suite = try Suite.init(
primitives.Kem.X25519HkdfSha256.id,
primitives.Kdf.HkdfSha256.id,
primitives.Aead.Aes128Gcm.id,
);
const kp = try suite.generateKeyPair();
A client initiates a connexion by sending an encrypted secret; a server accepts an encrypted secret from a client, and decrypts it, so that both parties can eventually agree on a shared secret.
var client_ctx_and_encapsulated_secret =
try suite.createClientContext(server_kp.public_key.slice(), "info", null, null);
var client_ctx = client_ctx_and_encapsulated_secret.client_ctx;
var encapsulated_secret = client_ctx_and_encapsulated_secret.encapsulated_secret;
encapsulated_secret.encapsulated
needs to be sent to the server.encapsulated_secret.encapsulated.secret
must remain secret.client_ctx
can be used to encrypt/decrypt messages exchanged with the server.
To improve misuse resistance, this implementation uses distinct types for the client and the server context: ClientContext
for the client, and ServerContext
for the server.
var server_ctx =
try suite.createServerContext(encapsulated_secret.encapsulated.constSlice(), server_kp, "info", null);
server_ctx
can be used to encrypt/decrypt messages exchanged with the client- The last parameter is an optional pre-shared key.
A message can be encrypted by the client for the server:
client_ctx.encryptToServer(&ciphertext, message, ad);
Nonces are automatically incremented, so it is safe to call this function multiple times within the same context.
Last parameter is optional associated data.
The ciphertext is client_ctx.tagLength()
bytes larger than the message.
The server can decrypt a ciphertext sent by the client:
var message2: [message.len]u8 = undefined;
try server_ctx.decryptFromClient(&message2, &ciphertext, ad);
Last parameter is optional associated data. The message length is server_ctx.tagLength()
bytes shorter than the ciphertext.
A message can also be encrypted by the server for the client:
server_ctx.encryptToClient(&ciphertext, message, ad);
Nonces are automatically incremented, so it is safe to call this function multiple times within the same context.
Last parameter is optional associated data.
The client can decrypt an encrypted response from the server:
try client_ctx.decryptFromServer(&message2, &ciphertext, ad);
Last parameter is optional associated data.
Authenticated modes, with or without a PSK are supported.
See createAuthenticatedClientContext
and createAuthenticatedServerContext
.
The exporter secret can be obtained with the exportedSecret()
function available both in the ServerContext
and ClientContext
structures:
const exporter = client_ctx.exporterSecret().constSlice();
const secret1 = try client_ctx.exportSecret("description 1")
const secret2 = try server_ctx.exportSecret("description 2");
const aead = suite.aead;