Key Derivation Functions (KDF) Reference
This page provides detailed documentation for all Key Derivation Functions (KDFs) implemented in the CryptoHives.Foundation.Security.Cryptography package.
Namespace
using CryptoHives.Foundation.Security.Cryptography.Kdf;
Overview
Key Derivation Functions extract cryptographic keys from input keying material (IKM) such as shared secrets from key agreement (e.g., ECDH), pre-shared keys, passwords, or other entropy sources.
| KDF | Source | Primary Use |
|---|---|---|
| HKDF | RFC 5869 | Extract + expand from shared secrets |
| KBKDF Counter Mode | SP 800-108r1 | Key diversification from master keys |
| Concat KDF | SP 800-56A/56C | ECDH key agreement, JOSE/JWE |
| PBKDF2 | RFC 8018 | Password-based key derivation |
| BLAKE3 DeriveKey | BLAKE3 Spec | High-performance custom protocols |
When to Use a KDF
| Scenario | Recommended KDF | Standard |
|---|---|---|
| TLS 1.3 key schedule | HKDF | RFC 5869 |
| HPKE (Hybrid Public Key Encryption) | HKDF | RFC 9180 |
| IKEv2 / IPsec key derivation | HKDF | RFC 7296 |
| Signal / Double Ratchet protocol | HKDF | Signal Spec |
| WireGuard VPN | HKDF / BLAKE3 | WireGuard Spec |
| General key expansion | HKDF | RFC 5869 |
| Application key diversification | HKDF or BLAKE3 | — |
HKDF (HMAC-Based Extract-and-Expand KDF)
HKDF is the HMAC-based Extract-and-Expand Key Derivation Function defined in RFC 5869. It is the most widely used KDF in modern cryptographic protocols.
Design
HKDF consists of two stages:
- Extract — Concentrates the entropy from potentially non-uniform input keying material (IKM) into a fixed-length pseudorandom key (PRK) using HMAC with an optional salt.
- Expand — Derives one or more output keys of arbitrary length from the PRK using HMAC with optional context information.
IKM ──► [ Extract ] ──► PRK ──► [ Expand ] ──► OKM (output keys)
▲ ▲
salt info
HmacFactory Delegate
All HKDF methods accept an HmacFactory delegate that creates IMac instances keyed with a given key. This design allows plugging any HMAC variant without changing the KDF logic.
public delegate IMac HmacFactory(byte[] key);
Built-in factories:
// SHA-256 (TLS 1.3, Signal, HPKE default)
HmacFactory sha256 = key => new HmacSha256(key);
// SHA-384 (TLS 1.3 with P-384)
HmacFactory sha384 = key => new HmacSha384(key);
// SHA-512 (maximum security)
HmacFactory sha512 = key => new HmacSha512(key);
// SHA3-256 (NIST PQC, cross-platform)
HmacFactory sha3_256 = key => new HmacSha3_256(key);
Class Declaration
public static class Hkdf
Methods
| Method | Description |
|---|---|
Extract(HmacFactory, ReadOnlySpan<byte>, ReadOnlySpan<byte>, Span<byte>) |
Extract PRK from IKM and salt (span overload) |
Extract(HmacFactory, byte[], byte[]?) |
Extract PRK from IKM and salt (array overload) |
Expand(HmacFactory, ReadOnlySpan<byte>, Span<byte>, ReadOnlySpan<byte>) |
Expand PRK into output keying material (span overload) |
Expand(HmacFactory, byte[], int, byte[]?) |
Expand PRK into output keying material (array overload) |
DeriveKey(HmacFactory, ReadOnlySpan<byte>, Span<byte>, ReadOnlySpan<byte>, ReadOnlySpan<byte>) |
Combined Extract-then-Expand (span overload) |
DeriveKey(HmacFactory, byte[], int, byte[]?, byte[]?) |
Combined Extract-then-Expand (array overload) |
Usage Examples
Combined Extract-and-Expand (Most Common)
using CryptoHives.Foundation.Security.Cryptography.Kdf;
using CryptoHives.Foundation.Security.Cryptography.Mac;
byte[] inputKeyMaterial = ...; // e.g., ECDH shared secret
byte[] salt = new byte[32];
RandomNumberGenerator.Fill(salt);
byte[] info = Encoding.UTF8.GetBytes("TLS 1.3 key");
// Array API (simple)
byte[] derivedKey = Hkdf.DeriveKey(
key => new HmacSha256(key),
inputKeyMaterial, outputLength: 32, salt, info);
// Span API (zero extra allocations)
Span<byte> output = stackalloc byte[32];
Hkdf.DeriveKey(
key => new HmacSha256(key),
inputKeyMaterial, output, salt, info);
Separate Extract and Expand
When multiple keys are needed from the same IKM, extract once and expand multiple times:
HmacFactory factory = key => new HmacSha256(key);
byte[] sharedSecret = ...; // from ECDH
// Step 1: Extract
byte[] prk = Hkdf.Extract(factory, sharedSecret, salt: null);
// Step 2: Expand for different purposes
byte[] encryptionKey = Hkdf.Expand(factory, prk, outputLength: 32,
info: Encoding.UTF8.GetBytes("encryption"));
byte[] authKey = Hkdf.Expand(factory, prk, outputLength: 32,
info: Encoding.UTF8.GetBytes("authentication"));
byte[] ivKey = Hkdf.Expand(factory, prk, outputLength: 12,
info: Encoding.UTF8.GetBytes("iv"));
TLS 1.3 Key Schedule Pattern
HmacFactory sha256 = key => new HmacSha256(key);
// Early Secret: Extract(salt=0, IKM=PSK or 0)
byte[] earlySecret = Hkdf.Extract(sha256, psk ?? new byte[32], salt: null);
// Handshake Secret: Extract(salt=derived, IKM=ECDHE)
byte[] derivedSalt = Hkdf.Expand(sha256, earlySecret, 32,
Encoding.UTF8.GetBytes("derived"));
byte[] handshakeSecret = Hkdf.Extract(sha256, ecdhSharedSecret, derivedSalt);
// Traffic keys: Expand with labeled info
byte[] clientKey = Hkdf.Expand(sha256, handshakeSecret, 32,
Encoding.UTF8.GetBytes("c hs traffic"));
With SHA-384 or SHA-512
// SHA-384 (48-byte output, used with P-384 curves)
byte[] key384 = Hkdf.DeriveKey(
key => new HmacSha384(key),
ikm, outputLength: 48, salt, info);
// SHA-512 (64-byte output, maximum security)
byte[] key512 = Hkdf.DeriveKey(
key => new HmacSha512(key),
ikm, outputLength: 64, salt, info);
Parameters
Salt
Per RFC 5869 §2.2, salt is optional but strongly recommended. If not provided, it defaults to a zero-filled byte array of length HashLen (e.g., 32 bytes for HMAC-SHA-256).
- Best practice: Use a random salt of at least
HashLenbytes - Acceptable: Use a fixed application-specific salt
- Minimum: Omit salt (uses all-zeros, reduces security margin)
Info
The info parameter binds the derived key to a specific context, preventing key reuse across different purposes. It should include:
- Protocol identifier (e.g., "TLS 1.3")
- Purpose label (e.g., "encryption", "authentication")
- Session identifiers or nonces where appropriate
Output Length
The maximum output length is 255 × HashLen bytes:
| HMAC Variant | HashLen | Max Output |
|---|---|---|
| HMAC-SHA-256 | 32 bytes | 8,160 bytes |
| HMAC-SHA-384 | 48 bytes | 12,240 bytes |
| HMAC-SHA-512 | 64 bytes | 16,320 bytes |
Comparison with .NET Built-in
| Feature | CryptoHives HKDF | System.Security.Cryptography.HKDF |
|---|---|---|
| Availability | All TFMs (.NET Framework 4.6.2+) | .NET Core 3.0+ only |
| HMAC variants | Any IMac (SHA-256/384/512, SHA3-256, etc.) |
SHA-1, SHA-256, SHA-384, SHA-512 only |
| SHA-3 support | ✅ via HmacSha3_256 | ❌ |
| API style | Instance + static, span + array | Static only, span-based |
| OS dependency | None (fully managed) | Uses CNG/OpenSSL |
Standards Compliance
- RFC 5869: HKDF specification — all 7 Appendix A test vectors verified
- RFC 8446: TLS 1.3 key schedule uses HKDF-Extract and HKDF-Expand
- RFC 9180: HPKE uses HKDF with labeled Extract/Expand
- RFC 7296: IKEv2 uses HKDF for key derivation
KBKDF — Counter Mode (SP 800-108r1)
KBKDF (Key-Based Key Derivation Function) in Counter Mode derives keying material from a key derivation key (KI) using a pseudorandom function iterated with an incrementing counter, as defined in NIST SP 800-108r1 §4.1.
Design
Counter Mode iterates a PRF (HMAC or CMAC) with a 32-bit counter prepended to fixed input data:
K(i) = PRF(KI, [i]₄ ‖ Label ‖ 0x00 ‖ Context ‖ [L]₄)
Where:
[i]₄= 32-bit big-endian counter (1-based)Label= purpose identifier0x00= separator byteContext= session/application context[L]₄= output length in bits (32-bit big-endian)
Class Declaration
public static class Kbkdf
Methods
| Method | Description |
|---|---|
DeriveKey(HmacFactory, ReadOnlySpan<byte>, Span<byte>, ReadOnlySpan<byte>, ReadOnlySpan<byte>) |
Derive key with label and context (span overload) |
DeriveKey(HmacFactory, byte[], int, byte[]?, byte[]?) |
Derive key with label and context (array overload) |
Usage Examples
Basic Key Derivation
using CryptoHives.Foundation.Security.Cryptography.Kdf;
using CryptoHives.Foundation.Security.Cryptography.Mac;
byte[] masterKey = ...; // key derivation key
byte[] label = Encoding.UTF8.GetBytes("encryption");
byte[] context = Encoding.UTF8.GetBytes("session-001");
// Array API (simple)
byte[] derivedKey = Kbkdf.DeriveKey(
key => new HmacSha256(key),
masterKey, outputLength: 32, label, context);
// Span API (zero extra allocations)
Span<byte> output = stackalloc byte[32];
Kbkdf.DeriveKey(
key => new HmacSha256(key),
masterKey, output, label, context);
Multiple Keys from Same Master Key
HmacFactory sha256 = key => new HmacSha256(key);
byte[] context = Encoding.UTF8.GetBytes("session-001");
// Derive different keys by varying the label
byte[] encKey = Kbkdf.DeriveKey(sha256, masterKey, 32,
Encoding.UTF8.GetBytes("encryption"), context);
byte[] authKey = Kbkdf.DeriveKey(sha256, masterKey, 32,
Encoding.UTF8.GetBytes("authentication"), context);
byte[] ivKey = Kbkdf.DeriveKey(sha256, masterKey, 12,
Encoding.UTF8.GetBytes("iv"), context);
With AES-CMAC as PRF
// AES-128 CMAC produces 16-byte MACs
byte[] aesKey = ...; // 16, 24, or 32 bytes
byte[] derived = Kbkdf.DeriveKey(
key => AesCmac.Create(key),
aesKey, outputLength: 32, label, context);
KBKDF vs HKDF
| Aspect | KBKDF (SP 800-108r1) | HKDF (RFC 5869) |
|---|---|---|
| Design | Single-phase counter loop | Two-phase Extract + Expand |
| Input key | Must be uniformly random | Can be non-uniform (Extract handles it) |
| Salt | No salt parameter | Optional salt for extraction |
| PRF support | HMAC and CMAC | HMAC only |
| Label/Context | Built into the construction | Via info parameter |
| Best for | Session key derivation from master key | Key extraction from shared secrets |
| Standards | NIST SP 800-108r1, CNG, DPAPI | RFC 5869, TLS 1.3, HPKE |
Standards Compliance
- NIST SP 800-108r1: Counter Mode KDF — verified against independently computed test vectors
- Compatible with .NET
SP800108DeriveBytes(same PRF input format)
Concat KDF — One-Step (SP 800-56A / SP 800-56C)
The Concatenation Key Derivation Function derives keying material from a shared secret using a hash function or HMAC iterated with an incrementing counter. It is defined in NIST SP 800-56A §5.8.1 (hash-based) and NIST SP 800-56C rev2 Option 1 (HMAC-based).
Design
Each iteration computes:
- Hash-based:
Hash(counter ‖ Z ‖ OtherInfo) - HMAC-based:
HMAC-Hash(salt, counter ‖ Z ‖ OtherInfo)
Where counter is a 32-bit big-endian counter starting at 1, Z is the shared secret, and OtherInfo is supplementary public information.
Class Declaration
public static class ConcatKdf
Methods
| Method | Description |
|---|---|
DeriveKey(HashAlgorithm, ReadOnlySpan<byte>, Span<byte>, ReadOnlySpan<byte>) |
Hash-based KDF (span overload) |
DeriveKey(HashAlgorithm, byte[], int, byte[]?) |
Hash-based KDF (array overload) |
DeriveKey(HmacFactory, ReadOnlySpan<byte>, Span<byte>, ReadOnlySpan<byte>, ReadOnlySpan<byte>) |
HMAC-based KDF with salt (span overload) |
DeriveKey(HmacFactory, byte[], int, byte[]?, byte[]?) |
HMAC-based KDF with salt (array overload) |
Usage Examples
Hash-Based (SP 800-56A §5.8.1)
using CryptoHives.Foundation.Security.Cryptography.Hash;
using CryptoHives.Foundation.Security.Cryptography.Kdf;
byte[] sharedSecret = ...; // e.g., from ECDH key agreement
byte[] otherInfo = ...; // algorithm ID, party info, etc.
using var sha256 = SHA256.Create();
byte[] derivedKey = ConcatKdf.DeriveKey(sha256, sharedSecret, 32, otherInfo);
HMAC-Based (SP 800-56C rev2 Option 1)
using CryptoHives.Foundation.Security.Cryptography.Kdf;
using CryptoHives.Foundation.Security.Cryptography.Mac;
byte[] salt = ...; // optional salt (used as HMAC key)
byte[] derivedKey = ConcatKdf.DeriveKey(
key => new HmacSha256(key),
sharedSecret, 32, otherInfo, salt);
JOSE/JWE Key Agreement (RFC 7518)
// Compose OtherInfo per RFC 7518 §4.6.2
byte[] algId = Encoding.UTF8.GetBytes("A256GCM");
byte[] otherInfo = ComposeJweOtherInfo(algId, apu, apv, keyLength: 256);
using var sha256 = SHA256.Create();
byte[] cek = ConcatKdf.DeriveKey(sha256, ecdhSharedSecret, 32, otherInfo);
Concat KDF vs HKDF
| Aspect | Concat KDF | HKDF |
|---|---|---|
| Input | Shared secret + OtherInfo | IKM + salt + info |
| Phases | Single step | Extract + Expand |
| Hash function | Direct or HMAC | HMAC only |
| Salt | Optional (HMAC variant only) | Optional (Extract phase) |
| Best for | ECDH key agreement, JOSE/JWE | TLS 1.3, HPKE, protocol key schedules |
| Standards | SP 800-56A, SP 800-56C, RFC 7518 | RFC 5869, RFC 8446 |
Standards Compliance
- NIST SP 800-56A §5.8.1: Hash-based one-step KDF — verified against BouncyCastle test vectors
- NIST SP 800-56C rev2 Option 1: HMAC-based one-step KDF
- Compatible with BouncyCastle
ConcatenationKdfGenerator
PBKDF2 (Password-Based KDF)
Standard: RFC 8018 §5.2 (formerly RFC 2898)
PBKDF2 derives keying material from a password by applying a pseudorandom function (PRF) — typically HMAC — iteratively. The iteration count slows down brute-force attacks, making it suitable for password hashing and password-based encryption.
Algorithm
DK = T₁ ‖ T₂ ‖ ... ‖ T_⌈dkLen/hLen⌉
Tᵢ = U₁ ⊕ U₂ ⊕ ... ⊕ Uₓ
U₁ = PRF(Password, Salt ‖ INT(i)) — i is 32-bit big-endian, 1-based
Uⱼ = PRF(Password, U_{j-1}) — for j ≥ 2
Usage
using CryptoHives.Foundation.Security.Cryptography.Kdf;
using CryptoHives.Foundation.Security.Cryptography.Mac;
// Derive a 32-byte key from a password using HMAC-SHA-256
byte[] password = Encoding.UTF8.GetBytes("my-password");
byte[] salt = RandomNumberGenerator.GetBytes(16);
byte[] derivedKey = Pbkdf2.DeriveKey(
key => new HmacSha256(key),
password, salt, iterations: 600_000, outputLength: 32);
Span-Based API
// Zero-copy derivation into pre-allocated buffer
Span<byte> output = stackalloc byte[32];
Pbkdf2.DeriveKey(
key => new HmacSha256(key),
password, salt, iterations: 600_000, output);
Comparison with .NET Rfc2898DeriveBytes
| Feature | CryptoHives Pbkdf2 |
.NET Rfc2898DeriveBytes |
|---|---|---|
| Any HMAC variant | ✅ via HmacFactory |
✅ via HashAlgorithmName |
| Span overloads | ✅ | ✅ (static Pbkdf2 method) |
| .NET Framework 4.6.2 | ✅ | ⚠️ SHA-1 only (pre-.NET Core 3.0) |
| .NET Standard 2.0 | ✅ | ⚠️ Limited hash support |
| Fully managed | ✅ | ❌ OS-dependent |
| Custom PRF (CMAC, etc.) | ✅ | ❌ HMAC only |
OWASP Iteration Count Recommendations (2024)
| HMAC Variant | Minimum Iterations |
|---|---|
| HMAC-SHA-256 | 600,000 |
| HMAC-SHA-384 | 600,000 |
| HMAC-SHA-512 | 210,000 |
| HMAC-SHA-1 | 1,300,000 |
Warning
PBKDF2 is not memory-hard and is vulnerable to GPU/ASIC attacks for password hashing. For new password storage, consider Argon2id or scrypt. PBKDF2 remains appropriate for key derivation in protocols like PKCS#12, WPA2, and S/MIME.
Use Cases
- WPA/WPA2 — Wi-Fi key derivation (HMAC-SHA-1, 4096 iterations)
- PKCS#12 — Certificate container encryption
- S/MIME — Email encryption key derivation
- Password storage — When Argon2 is not available
- PEM encryption — PKCS#5 encrypted private keys
BLAKE3 Key Derivation
BLAKE3 provides a built-in key derivation mode that uses a context string for domain separation. Unlike HKDF, it does not require a separate HMAC; the KDF is integrated into the hash function.
See Hash Algorithms — BLAKE3 for full documentation.
using CryptoHives.Foundation.Security.Cryptography.Hash;
string context = "MyApp 2025-01-01 session key";
byte[] ikm = ...; // input key material
using var blake3 = Blake3.CreateDeriveKey(context);
Span<byte> derivedKey = stackalloc byte[32];
blake3.TryComputeHash(ikm, derivedKey, out _);
KDF Selection Guide
By Protocol
| Protocol | KDF | HMAC Variant | Notes |
|---|---|---|---|
| TLS 1.3 | HKDF | SHA-256 or SHA-384 | RFC 8446 key schedule |
| HPKE | HKDF | SHA-256 | RFC 9180 labeled Extract/Expand |
| IKEv2 / IPsec | HKDF or KBKDF | SHA-256 or SHA-512 | RFC 7296 |
| Signal Protocol | HKDF | SHA-256 | Double Ratchet algorithm |
| WireGuard | HKDF / BLAKE3 | SHA-256 | Noise framework |
| SSH | HKDF | SHA-256 or SHA-512 | Key exchange |
| OPC UA | HKDF | SHA-256 | Secure channel key derivation |
| Microsoft CNG / DPAPI | KBKDF | SHA-256 or AES-CMAC | SP 800-108 Counter Mode |
| Kerberos | KBKDF | SHA-256 | Session key derivation |
| 802.11i / EAP | KBKDF | AES-CMAC | Key hierarchy derivation |
| WPA/WPA2 | PBKDF2 | SHA-1 | 4096 iterations (Wi-Fi passphrase) |
| PKCS#12 / S/MIME | PBKDF2 | SHA-256 | Certificate/key encryption |
By Use Case
| Use Case | Recommended | Alternative |
|---|---|---|
| Extract + expand from shared secret | HKDF | — |
| Multiple keys from one secret | HKDF (Extract once, Expand many) | KBKDF (vary label) |
| Session key from master key | KBKDF | HKDF |
| Context-bound key derivation | HKDF with info or KBKDF with label/context |
BLAKE3 DeriveKey |
| High-performance key derivation | BLAKE3 DeriveKey | HKDF |
| Password-based key derivation | PBKDF2 | — |
| Password storage | PBKDF2 (if Argon2 unavailable) | — |
| NIST compliance required | HKDF or KBKDF | PBKDF2 |
| Cross-platform SHA-3 KDF | HKDF with HMAC-SHA3-256 | — |
| CMAC-based PRF needed | KBKDF with AES-CMAC | — |
KDF Roadmap
The following KDFs are commonly used with ECC, asymmetric encryption, and post-quantum cryptography. They are candidates for future implementation:
| KDF | Standard | Used With | Status |
|---|---|---|---|
| HKDF | RFC 5869 | TLS 1.3, HPKE, Signal, WireGuard | ✅ Implemented |
| KBKDF Counter Mode | NIST SP 800-108r1 | CNG, DPAPI, IPsec, Kerberos | ✅ Implemented |
| Concat KDF (One-Step) | NIST SP 800-56A/56C | ECDH key agreement, JOSE/JWE | ✅ Implemented |
| BLAKE3 DeriveKey | BLAKE3 Spec | Custom protocols | ✅ Implemented |
| X9.63 KDF | ANSI X9.63 / SEC 1 | Legacy ECC (IEEE P1363) | 🔲 Under review |
| PBKDF2 | RFC 8018 | Password-based key derivation | ✅ Implemented |
Security Best Practices
Key Material Handling
// Clear sensitive key material after use
byte[] prk = Hkdf.Extract(factory, ikm, salt);
try
{
byte[] derivedKey = Hkdf.Expand(factory, prk, 32, info);
// Use derivedKey...
}
finally
{
Array.Clear(prk, 0, prk.Length);
}
Note: The span-based
DeriveKeymethod automatically clears the intermediate PRK.
Nonce/Salt Reuse
- Salt reuse is acceptable — HKDF tolerates salt reuse without security degradation (it merely reduces the security margin).
- Info reuse is dangerous — The same
(PRK, info)pair always produces the same output. Use uniqueinfovalues for each derived key.
Domain Separation
Always include distinct info values to prevent key reuse across different contexts:
// GOOD: Different info for different keys
byte[] encKey = Hkdf.Expand(factory, prk, 32, "enc"u8.ToArray());
byte[] macKey = Hkdf.Expand(factory, prk, 32, "mac"u8.ToArray());
// BAD: Same info reused — produces identical keys!
// byte[] key1 = Hkdf.Expand(factory, prk, 32, info);
// byte[] key2 = Hkdf.Expand(factory, prk, 32, info); // same as key1!
See Also
- MAC Algorithms — HMAC variants used as the underlying PRF
- Hash Algorithms — BLAKE3 key derivation mode
- Cipher Algorithms — Algorithms that consume derived keys
- Cryptography Package Overview
- HMAC Test Vectors
© 2026 The Keepers of the CryptoHives