Skip to content

std/hkdf ​

HKDF, the HMAC-based extract-and-expand key derivation function (RFC 5869).

milo
from "std/hkdf" import { Hkdf }

Turns one high-entropy-but-not-uniform secret — an ECDH shared secret, a master key, a token — into any number of independent, uniformly distributed subkeys. Two steps, and the split matters:

extract(salt, ikm) -> prk        concentrate whatever entropy the input has
expand(prk, info, len) -> okm    spread it over len bytes, bound to info

info is the domain separator. Derive your encryption key with info "enc" and your MAC key with info "mac" from the same PRK and the two are computationally unrelated, so leaking one says nothing about the other. Using the same bytes for both is the mistake this module exists to prevent.

HKDF is not a password KDF. Its cost is one HMAC per output block, which is exactly what you do not want against a password guesser — use std/pbkdf2 there.

Example ​

milo
let encKey = Hkdf.sha256(salt, sharedSecret, "encryption", 32)?
let macKey = Hkdf.sha256(salt, sharedSecret, "authentication", 32)?

Hkdf.sha256 is extract then expand in one call. Use extractSha256 and expandSha256 separately when one PRK feeds several subkeys. The SHA-1 variants exist because RFC 5869's own test vectors cover them and older protocols specify them; prefer SHA-256.

Checked against the RFC 5869 test vectors in tests/fixtures/hkdfVectors.milo.

API reference ​

Hkdf ​

milo
pub struct Hkdf

Hkdf namespace — Hkdf.sha256(salt, ikm, info, length) for the common one-shot, or extract/expand separately when the PRK is reused across several subkeys.

Hkdf.expandSha1 ​

milo
fn Hkdf.expandSha1(prk: &string, info: &string, length: i64): Result<string>

HKDF-Expand with SHA-1. length may not exceed 255*20.

Hkdf.expandSha256 ​

milo
fn Hkdf.expandSha256(prk: &string, info: &string, length: i64): Result<string>

HKDF-Expand with SHA-256. length is in bytes and may not exceed 255*32. info binds the output to a purpose; pass "" only when there is exactly one.

Hkdf.expandSha512 ​

milo
fn Hkdf.expandSha512(prk: &string, info: &string, length: i64): Result<string>

HKDF-Expand with SHA-512. length may not exceed 255*64.

Hkdf.extractSha1 ​

milo
fn Hkdf.extractSha1(salt: &string, ikm: &string): string

HKDF-Extract with SHA-1: a 20-byte PRK. Present because RFC 5869's own test vectors cover it and older protocols specify it; prefer the SHA-256 pair.

Hkdf.extractSha256 ​

milo
fn Hkdf.extractSha256(salt: &string, ikm: &string): string

HKDF-Extract with SHA-256: a 32-byte pseudorandom key.

An empty salt is the RFC's default of HashLen zero bytes — HMAC zero-pads a short key to the block size, so the two inputs produce the same PRK. A salt need not be secret, and a random one is what makes the extract step provably a randomness extractor rather than just a hash.

An empty salt is the RFC's default of HashLen zero bytes. A salt need not be secret; a random one is what makes extract a randomness extractor rather than just a hash.

Hkdf.extractSha512 ​

milo
fn Hkdf.extractSha512(salt: &string, ikm: &string): string

HKDF-Extract with SHA-512: a 64-byte pseudorandom key.

Hkdf.sha1 ​

milo
fn Hkdf.sha1(salt: &string, ikm: &string, info: &string, length: i64): Result<string>

Extract then expand, SHA-1.

Hkdf.sha256 ​

milo
fn Hkdf.sha256(salt: &string, ikm: &string, info: &string, length: i64): Result<string>

Extract then expand, SHA-256. The usual entry point.

Hkdf.sha512 ​

milo
fn Hkdf.sha512(salt: &string, ikm: &string, info: &string, length: i64): Result<string>

Extract then expand, SHA-512.