Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Key Derivation

Overview

Key derivation uses the OpenSSL EVP_KDF API. Five typed builders cover the most common cases; raw KdfAlg + KdfCtx covers everything else.

  • HkdfBuilder — HKDF (RFC 5869)
  • Pbkdf2Builder — PBKDF2 (PKCS #5 / RFC 8018)
  • ScryptBuilder — scrypt (RFC 7914)
  • SshkdfBuilder — SSH key derivation (RFC 4253 §7.2)
  • KbkdfBuilder — KBKDF counter/feedback mode (NIST SP 800-108)
  • KdfAlg + KdfCtx — raw access to any EVP_KDF algorithm

Builders hold borrowed slices with lifetimes — no data is copied until the final derive() call. derive() writes directly into a caller-provided buffer.

Typed Builders

HkdfBuilder<'a>

#![allow(unused)]
fn main() {
pub struct HkdfBuilder<'a> { /* borrowed slices */ }

impl<'a> HkdfBuilder<'a> {
    pub fn new(digest: &'a DigestAlg) -> Self;   // no allocation

    pub fn key(self,  key:  &'a [u8]) -> Self;   // IKM (input key material)
    pub fn salt(self, salt: &'a [u8]) -> Self;   // optional
    pub fn info(self, info: &'a [u8]) -> Self;   // optional context info
    pub fn mode(self, mode: HkdfMode) -> Self;

    /// Derive into caller-provided buffer.
    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;

    /// Derive `len` bytes into a freshly allocated Vec.
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

HkdfMode

#![allow(unused)]
fn main() {
pub enum HkdfMode {
    ExtractAndExpand,  // RFC 5869 §2 default — extract then expand
    ExtractOnly,       // output is the PRK (pseudorandom key)
    ExpandOnly,        // input must already be a PRK
}
}

Pbkdf2Builder<'a>

#![allow(unused)]
fn main() {
pub struct Pbkdf2Builder<'a> { /* borrowed slices */ }

impl<'a> Pbkdf2Builder<'a> {
    /// Password and salt are required at construction; digest selects the PRF.
    /// Default iteration count: 600 000 (NIST SP 800-132 minimum for SHA-256).
    pub fn new(digest: &'a DigestAlg, password: &'a [u8], salt: &'a [u8]) -> Self;

    pub fn iterations(self, n: u32) -> Self;

    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

ScryptBuilder<'a>

#![allow(unused)]
fn main() {
pub struct ScryptBuilder<'a> { /* borrowed slices */ }

pub struct ScryptParams {
    pub n: u64,  // CPU/memory cost; must be a power of 2
    pub r: u32,  // block size factor
    pub p: u32,  // parallelization factor
}

impl Default for ScryptParams {
    fn default() -> Self {
        ScryptParams { n: 16384, r: 8, p: 1 }  // OWASP-recommended minimums
    }
}

impl<'a> ScryptBuilder<'a> {
    /// Password and salt are required; uses ScryptParams::default().
    pub fn new(password: &'a [u8], salt: &'a [u8]) -> Self;

    pub fn params(self, params: ScryptParams) -> Self;

    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

SshkdfBuilder<'a>

SSH key derivation as specified in RFC 4253 §7.2. Derives the six key components (initial IVs, encryption keys, and integrity keys for each direction) from the shared secret, exchange hash, and session identifier produced by the SSH handshake.

#![allow(unused)]
fn main() {
pub struct SshkdfBuilder<'a> { /* borrowed slices */ }

impl<'a> SshkdfBuilder<'a> {
    /// Create an SSH-KDF builder.
    ///
    /// - digest     — hash algorithm (e.g. SHA-256).
    /// - key        — shared secret K from the DH exchange.
    /// - xcghash    — exchange hash H.
    /// - session_id — session identifier (equals the first H for the session).
    /// - key_type   — which key/IV component to derive (A–F).
    pub fn new(
        digest:     &'a DigestAlg,
        key:        &'a [u8],
        xcghash:    &'a [u8],
        session_id: &'a [u8],
        key_type:   SshkdfKeyType,
    ) -> Self;

    /// Derive into caller-provided buffer.
    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;

    /// Derive `len` bytes into a freshly allocated Vec.
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

SshkdfKeyType

Each variant corresponds to one of the six letter codes from RFC 4253 §7.2:

#![allow(unused)]
fn main() {
pub enum SshkdfKeyType {
    InitialIvClientToServer,       // "A"
    InitialIvServerToClient,       // "B"
    EncryptionKeyClientToServer,   // "C"
    EncryptionKeyServerToClient,   // "D"
    IntegrityKeyClientToServer,    // "E"
    IntegrityKeyServerToClient,    // "F"
}
}

Derive all six components by calling SshkdfBuilder::new once per variant with the same key, xcghash, and session_id inputs.

KbkdfBuilder<'a>

KBKDF (key-based key derivation function) as specified in NIST SP 800-108. Supports both counter mode and feedback mode with HMAC or CMAC as the PRF.

#![allow(unused)]
fn main() {
pub struct KbkdfBuilder<'a> { /* borrowed slices */ }

impl<'a> KbkdfBuilder<'a> {
    /// Create a KBKDF builder.
    ///
    /// - mode — counter or feedback.
    /// - mac  — MAC algorithm (fetch c"HMAC" or c"CMAC" via MacAlg::fetch).
    /// - key  — the key derivation key (KDK).
    pub fn new(mode: KbkdfMode, mac: &'a MacAlg, key: &'a [u8]) -> Self;

    /// Hash digest for HMAC-based derivation (required when mac is HMAC).
    pub fn digest(self, digest: &'a DigestAlg) -> Self;

    /// Label: identifies the purpose of the derived key.
    pub fn label(self, label: &'a [u8]) -> Self;

    /// Context: caller-specific data bound into the derivation.
    pub fn context(self, context: &'a [u8]) -> Self;

    /// Salt / feedback IV (feedback mode only).
    pub fn salt(self, salt: &'a [u8]) -> Self;

    /// Counter field length (default: Bits32).
    pub fn counter_len(self, len: KbkdfCounterLen) -> Self;

    /// Include the length field L in the PRF input (default: true).
    pub fn use_l(self, enabled: bool) -> Self;

    /// Include the zero-byte separator in the PRF input (default: true).
    pub fn use_separator(self, enabled: bool) -> Self;

    /// Derive into caller-provided buffer.
    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;

    /// Derive `len` bytes into a freshly allocated Vec.
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

KbkdfMode

#![allow(unused)]
fn main() {
pub enum KbkdfMode {
    /// Counter mode — a monotonically incrementing counter is appended to each
    /// PRF input block. This is the most common SP 800-108 construction.
    Counter,

    /// Feedback mode — the output of each PRF invocation is fed as input to
    /// the next. An initial IV/salt is required for the first block.
    Feedback,
}
}

KbkdfCounterLen

Controls the bit-width of the counter field in counter mode. Defaults to Bits32.

#![allow(unused)]
fn main() {
pub enum KbkdfCounterLen {
    Bits8  = 8,
    Bits16 = 16,
    Bits24 = 24,
    Bits32 = 32,   // default
}
}

Pkcs12KdfBuilder<'a>

PKCS#12 (RFC 7292 Appendix B) legacy KDF. Needed only for interoperability with PKCS#12 files encrypted with deprecated algorithms such as PBEWithSHAAnd3-KeyTripleDES-CBC. New PKCS#12 files should use PBES2/PBKDF2 instead.

#![allow(unused)]
fn main() {
pub enum Pkcs12KdfId {
    Key = 1,  // cipher key bytes
    Iv  = 2,  // cipher IV bytes
    Mac = 3,  // MAC key bytes
}

pub struct Pkcs12KdfBuilder<'a> { /* borrowed slices */ }

impl<'a> Pkcs12KdfBuilder<'a> {
    /// Create a PKCS#12 KDF builder.
    ///
    /// - `md`       — hash algorithm (SHA-1 for legacy 3DES; SHA-256 for PBES2).
    /// - `password` — UTF-8 passphrase bytes.
    /// - `salt`     — random salt (RFC 7292 recommends 8 bytes).
    /// - `id`       — which component to derive: Key, Iv, or Mac.
    ///
    /// Default iteration count: 2048.
    pub fn new(md: &'a DigestAlg, password: &'a [u8], salt: &'a [u8], id: Pkcs12KdfId) -> Self;

    /// Override the iteration count.
    pub fn iterations(self, n: u32) -> Self;

    pub fn derive(self, out: &mut [u8]) -> Result<(), ErrorStack>;
    pub fn derive_to_vec(self, len: usize) -> Result<Vec<u8>, ErrorStack>;
}
}

Raw KDF Access

For algorithms without a typed builder (TLS13-KDF, SSKDF, X963KDF, etc.):

#![allow(unused)]
fn main() {
pub struct KdfAlg { /* EVP_KDF* */ }

impl KdfAlg {
    pub fn fetch(name: &CStr) -> Result<Self, ErrorStack>;
}

pub struct KdfCtx { /* EVP_KDF_CTX* */ }

impl KdfCtx {
    pub fn new(alg: &KdfAlg) -> Result<Self, ErrorStack>;

    /// Derive key material. Parameters are supplied at derive time.
    pub fn derive(&mut self, out: &mut [u8], params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Update parameters on this context without destroying and recreating it.
    ///
    /// Wraps `EVP_KDF_CTX_set_params`. Useful for changing HKDF mode (e.g.
    /// extract-only vs. expand-only) or updating salt/key between derivations.
    pub fn set_params(&mut self, params: &Params<'_>) -> Result<(), ErrorStack>;

    /// Retrieve current parameter values from this context.
    ///
    /// Wraps `EVP_KDF_CTX_get_params`. Build a `Params` with placeholder
    /// values for the keys you want, call this, then read back with `params.get_*`.
    pub fn get_params(&self, params: &mut Params<'_>) -> Result<(), ErrorStack>;

    /// Return the output length of this KDF (if fixed), or `usize::MAX` if variable.
    ///
    /// Wraps `EVP_KDF_CTX_get_kdf_size`. HKDF and most stream KDFs return
    /// `usize::MAX`; fixed-output KDFs such as SSKDF with a set output length
    /// return that fixed length.
    pub fn size(&self) -> usize;
}
}

Algorithm Reference

Builder / APIEVP_KDF nameStandard
HkdfBuilderc"HKDF"RFC 5869
Pbkdf2Builderc"PBKDF2"PKCS #5 / RFC 8018
ScryptBuilderc"SCRYPT"RFC 7914
SshkdfBuilderc"SSHKDF"RFC 4253 §7.2
KbkdfBuilderc"KBKDF"NIST SP 800-108
Pkcs12KdfBuilderc"PKCS12KDF"RFC 7292 Appendix B
KdfCtx directc"TLS13-KDF"RFC 8446
KdfCtx directc"SSKDF"NIST SP 800-56Cr2
KdfCtx directc"X963KDF"ANSI X9.63

Examples

HKDF (Expand from existing PRK)

#![allow(unused)]
fn main() {
use native_ossl::kdf::{HkdfBuilder, HkdfMode};
use native_ossl::digest::DigestAlg;

let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;

let ikm  = b"input key material";
let salt = b"random salt";
let info = b"context info";

let mut okm = [0u8; 32];
HkdfBuilder::new(&sha256)
    .key(ikm)
    .salt(salt)
    .info(info)
    .derive(&mut okm)?;
}

PBKDF2 (Password Hashing)

#![allow(unused)]
fn main() {
use native_ossl::kdf::Pbkdf2Builder;
use native_ossl::digest::DigestAlg;

let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;

let mut key = [0u8; 32];
Pbkdf2Builder::new(&sha256, b"my password", b"random salt")
    .iterations(100_000)
    .derive(&mut key)?;
}

scrypt (Memory-Hard Password Hashing)

#![allow(unused)]
fn main() {
use native_ossl::kdf::{ScryptBuilder, ScryptParams};

let mut key = [0u8; 32];
ScryptBuilder::new(b"my password", b"random salt")
    .params(ScryptParams { n: 32768, r: 8, p: 1 })
    .derive(&mut key)?;
}

SSH Key Derivation (RFC 4253)

Derive the client-to-server encryption key and IV after completing an SSH Diffie-Hellman key exchange:

use native_ossl::kdf::{SshkdfBuilder, SshkdfKeyType};
use native_ossl::digest::DigestAlg;

let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;

// shared_secret, exchange_hash, and session_id come from the DH handshake.
let shared_secret = /* K from ECDH / DH group exchange */;
let exchange_hash = /* H = hash of handshake transcript */;
let session_id    = /* first H for this session */;

// Derive the client-to-server IV (16 bytes for AES-128).
let mut iv = [0u8; 16];
SshkdfBuilder::new(
    &sha256,
    &shared_secret,
    &exchange_hash,
    &session_id,
    SshkdfKeyType::InitialIvClientToServer,
).derive(&mut iv)?;

// Derive the client-to-server encryption key (16 bytes for AES-128).
let mut enc_key = [0u8; 16];
SshkdfBuilder::new(
    &sha256,
    &shared_secret,
    &exchange_hash,
    &session_id,
    SshkdfKeyType::EncryptionKeyClientToServer,
).derive(&mut enc_key)?;

// Derive the client-to-server integrity key (32 bytes for HMAC-SHA-256).
let mut mac_key = [0u8; 32];
SshkdfBuilder::new(
    &sha256,
    &shared_secret,
    &exchange_hash,
    &session_id,
    SshkdfKeyType::IntegrityKeyClientToServer,
).derive(&mut mac_key)?;

KBKDF Counter Mode (HMAC-SHA-256 PRF)

use native_ossl::kdf::{KbkdfBuilder, KbkdfMode, KbkdfCounterLen};
use native_ossl::mac::MacAlg;
use native_ossl::digest::DigestAlg;

let hmac = MacAlg::fetch(c"HMAC", None)?;
let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;

let master_key = b"my 32-byte key derivation key!!!";

let derived = KbkdfBuilder::new(KbkdfMode::Counter, &hmac, master_key)
    .digest(&sha256)
    .label(b"session key")
    .context(b"connection id 42")
    .counter_len(KbkdfCounterLen::Bits32)
    .derive_to_vec(32)?;

KBKDF Feedback Mode (HMAC-SHA-256 PRF)

use native_ossl::kdf::{KbkdfBuilder, KbkdfMode};
use native_ossl::mac::MacAlg;
use native_ossl::digest::DigestAlg;

let hmac = MacAlg::fetch(c"HMAC", None)?;
let sha256 = DigestAlg::fetch(c"SHA2-256", None)?;

let master_key = b"my 32-byte key derivation key!!!";
let iv = b"initial feedback value (16 bytes)";

let derived = KbkdfBuilder::new(KbkdfMode::Feedback, &hmac, master_key)
    .digest(&sha256)
    .label(b"session key")
    .salt(iv)    // feedback mode requires an IV/salt for the first block
    .derive_to_vec(32)?;

Raw KdfCtx (TLS 1.3 KDF)

#![allow(unused)]
fn main() {
use native_ossl::kdf::{KdfAlg, KdfCtx};
use native_ossl::params::ParamBuilder;

let alg = KdfAlg::fetch(c"TLS13-KDF")?;
let mut ctx = KdfCtx::new(&alg)?;

let params = ParamBuilder::new()?
    .push_utf8_string(c"digest", c"SHA2-256")?
    .push_utf8_string(c"mode",   c"EXTRACT_ONLY")?
    .push_octet_slice(c"key",    b"my ikm")?
    .build()?;

let mut out = [0u8; 32];
ctx.derive(&mut out, &params)?;
}

Design Notes

  • EVP_KDF, not EVP_PKEY_CTX — earlier versions of OpenSSL used EVP_PKEY_CTX in derive mode for KDFs. OpenSSL 3.x provides the dedicated EVP_KDF API, which has a cleaner parameter interface.
  • Re-parameterisation vs. rebuildKdfCtx::set_params wraps EVP_KDF_CTX_set_params, allowing algorithm-specific parameters (e.g. HKDF mode, salt, key) to be updated on an existing context without destroying and recreating it. This avoids the overhead of a KdfAlg::fetch + KdfCtx::new cycle and is the correct approach when performing multiple derivations with the same algorithm but different parameters. The typed builders (HkdfBuilder, etc.) always create a fresh context; use KdfCtx directly when re-parameterisation matters.
  • get_params query patternKdfCtx::get_params follows the OpenSSL query-array pattern: build a Params with placeholder values (e.g. push_uint(c"mode", 0)) for the keys you want, pass it by &mut, and read the filled-in values with params.get_uint(c"mode") etc. Only keys present in the query array are filled; unknown keys are silently skipped by OpenSSL.
  • Builders are consumed by derive — create a new builder for each derivation. This prevents accidental reuse of parameters from a previous call.
  • No context stored in builderKdfAlg and KdfCtx are created inside derive() and freed when it returns. There is no lifetime entanglement between the builder and the EVP context.
  • ScryptParams struct — scrypt has three interdependent cost parameters. A struct prevents passing them in the wrong order.
  • PBKDF2 iteration count — the default of 600 000 follows NIST SP 800-132 (2023 revision) for SHA-256. Override with .iterations() when compatibility with a specific value is required.
  • SshkdfBuilder is one-shot per component — each of the six RFC 4253 key components requires a separate builder instance with the same inputs but a different SshkdfKeyType. There is no loop API; construct each builder explicitly.
  • KbkdfBuilder::context maps to the "data" parameter — OpenSSL’s KBKDF provider uses "data" for the SP 800-108 context field. The method is named .context() to match the specification’s terminology.
  • KbkdfBuilder::digest is required for HMAC — when the MAC algorithm is HMAC, a digest must be set. For CMAC the digest is ignored (the cipher is bound to the MacAlg at fetch time, not at derive time).