KeyDerivationExtensions

Namespace: WvdS.System.Security.Cryptography.KeyDerivation

Static class for Key Derivation Functions with Post-Quantum support. Supports HKDF, PBKDF2 and Argon2id.

Overview

Supported KDF algorithms:

KDF Standard Usage
HKDF RFC 5869 Session keys from shared secrets
PBKDF2 RFC 8018 Password-based keys
Argon2id RFC 9106 Memory-hard KDF (passwords)

HKDF - Hash-based Key Derivation

DeriveKey

Derives keys from a shared secret (HKDF-Extract-then-Expand).

// ML-KEM Shared Secret from Key Exchange
byte[] sharedSecret = session.SharedSecret;
 
// Standard derivation (SHA-256)
byte[] aesKey = KeyDerivationExtensions.DeriveKey(
    sharedSecret,
    outputLength: 32);  // 256 bit
 
// With salt and context info
byte[] salt = RandomNumberGenerator.GetBytes(32);
byte[] info = Encoding.UTF8.GetBytes("MyApp-Session-Key");
 
byte[] sessionKey = KeyDerivationExtensions.DeriveKey(
    sharedSecret,
    outputLength: 32,
    salt: salt,
    info: info,
    hashAlgorithm: HashAlgorithmName.SHA384);  // Optional

HkdfExtract

Extracts PRK (Pseudorandom Key) from Input Key Material.

byte[] inputKeyMaterial = GetSharedSecret();
byte[] salt = RandomNumberGenerator.GetBytes(32);
 
byte[] prk = KeyDerivationExtensions.HkdfExtract(
    inputKeyMaterial,
    salt: salt,
    hashAlgorithm: HashAlgorithmName.SHA256);
 
// PRK has same length as hash output (32 bytes for SHA-256)

HkdfExpand

Expands PRK to Output Key Material.

byte[] prk = GetPrk();
 
// Encryption Key
byte[] encKey = KeyDerivationExtensions.HkdfExpand(
    prk,
    outputLength: 32,
    info: Encoding.UTF8.GetBytes("encryption"));
 
// MAC Key
byte[] macKey = KeyDerivationExtensions.HkdfExpand(
    prk,
    outputLength: 32,
    info: Encoding.UTF8.GetBytes("mac"));

Hybrid Key Derivation

Combines classical (ECDH/DH) and PQ (ML-KEM) shared secrets.

DeriveHybridKey

byte[] ecdhSecret = GetEcdhSharedSecret();
byte[] mlKemSecret = GetMlKemSharedSecret();
 
// Hybrid mode: Both secrets are combined
byte[] hybridKey = KeyDerivationExtensions.DeriveHybridKey(
    classicSecret: ecdhSecret,
    pqSecret: mlKemSecret,
    outputLength: 32,
    mode: CryptoMode.Hybrid);
 
// Classic only
byte[] classicKey = KeyDerivationExtensions.DeriveHybridKey(
    classicSecret: ecdhSecret,
    pqSecret: null,
    outputLength: 32,
    mode: CryptoMode.Classic);
 
// Post-Quantum only
byte[] pqKey = KeyDerivationExtensions.DeriveHybridKey(
    classicSecret: null,
    pqSecret: mlKemSecret,
    outputLength: 32,
    mode: CryptoMode.PostQuantum);
 
// Custom Info
byte[] customKey = KeyDerivationExtensions.DeriveHybridKey(
    classicSecret: ecdhSecret,
    pqSecret: mlKemSecret,
    outputLength: 64,
    mode: CryptoMode.Hybrid,
    info: Encoding.UTF8.GetBytes("MyProtocol-v1"));

Key combination:

Hybrid Mode:
  IKM = classicSecret || pqSecret
  Key = HKDF-SHA256(IKM, info="WvdS-Hybrid-Key")

DeriveHybridKeyMaterial

Derives multiple keys for different purposes.

using HybridKeyMaterial keyMaterial = KeyDerivationExtensions.DeriveHybridKeyMaterial(
    classicSecret: ecdhSecret,
    pqSecret: mlKemSecret,
    mode: CryptoMode.Hybrid);
 
// Usage
byte[] encKey = keyMaterial.EncryptionKey;  // 32 bytes
byte[] macKey = keyMaterial.MacKey;         // 32 bytes
byte[] iv = keyMaterial.Iv;                 // 16 bytes
byte[] authKey = keyMaterial.AuthKey;       // 32 bytes
 
// IDisposable: Keys are securely erased

PBKDF2 - Password-based Derivation

Pbkdf2

string password = "SecurePassword123!";
byte[] salt = RandomNumberGenerator.GetBytes(32);
 
// Standard PBKDF2
byte[] key = KeyDerivationExtensions.Pbkdf2(
    password,
    salt,
    iterations: 100000,
    outputLength: 32);
 
// With PQ entropy (additional protection)
byte[] pqEntropy = GetPqEntropy();
 
byte[] enhancedKey = KeyDerivationExtensions.Pbkdf2(
    password,
    salt,
    iterations: 100000,
    outputLength: 32,
    pqEntropy: pqEntropy,  // Combined with salt
    hashAlgorithm: HashAlgorithmName.SHA512);

Pbkdf2WithPqSalt

PBKDF2 with PQ-enhanced salt (public key is included in salt calculation).

string password = "UserPassword";
byte[] baseSalt = RandomNumberGenerator.GetBytes(16);
byte[] mlKemPublicKey = GetRecipientPublicKey();
 
// Salt = SHA256(baseSalt || pqPublicKey)
byte[] key = KeyDerivationExtensions.Pbkdf2WithPqSalt(
    password,
    baseSalt,
    mlKemPublicKey,
    iterations: 100000,
    outputLength: 32);
Advantage: Even with identical password and base salt, each recipient (different PQ public key) gets a different key.

Argon2id - Memory-Hard KDF

Argon2id via OpenSSL 3.6 - resistant against GPU/ASIC attacks.

Argon2id (Byte Array)

byte[] password = Encoding.UTF8.GetBytes("SecurePassword");
byte[] salt = RandomNumberGenerator.GetBytes(16);  // Minimum 16 bytes
 
byte[] key = KeyDerivationExtensions.Argon2id(
    password,
    salt,
    outputLength: 32,     // Key length
    iterations: 3,        // Time cost (t)
    memoryKiB: 65536,     // Memory: 64 MB
    parallelism: 4);      // Threads (p)

Argon2id (String)

string password = "UserPassword123";
byte[] salt = RandomNumberGenerator.GetBytes(16);
 
byte[] key = KeyDerivationExtensions.Argon2id(
    password,
    salt,
    outputLength: 32,
    iterations: 3,
    memoryKiB: 65536,
    parallelism: 4);

Recommended parameters:

Application Iterations (t) Memory (m) Parallelism (p)
Password hashing 3 64 MB 4
High security 4 256 MB 4
Low memory 4 16 MB 4

TLS Key Derivation

DeriveTlsKeys (TLS 1.2 Style)

byte[] preMasterSecret = GetPreMasterSecret();
byte[] clientRandom = GetClientRandom();
byte[] serverRandom = GetServerRandom();
 
using TlsKeyMaterial keys = KeyDerivationExtensions.DeriveTlsKeys(
    preMasterSecret,
    clientRandom,
    serverRandom,
    mode: CryptoMode.Hybrid);
 
// Usage
var clientKey = keys.ClientWriteKey;   // 32 bytes
var serverKey = keys.ServerWriteKey;   // 32 bytes
var clientIv = keys.ClientWriteIv;     // 12 bytes
var serverIv = keys.ServerWriteIv;     // 12 bytes

DeriveTls13Keys

TLS 1.3 compatible key schedule.

byte[]? pskSecret = null;  // Pre-Shared Key (optional)
byte[] ecdhSecret = GetEcdhSecret();
byte[] pqSecret = GetMlKemSecret();
byte[] clientHello = GetClientHelloBytes();
byte[] serverHello = GetServerHelloBytes();
 
using Tls13KeySchedule schedule = KeyDerivationExtensions.DeriveTls13Keys(
    pskSecret,
    ecdhSecret,
    pqSecret,
    clientHello,
    serverHello,
    mode: CryptoMode.Hybrid);
 
// Handshake Traffic Secrets
var clientHsSecret = schedule.ClientHandshakeTrafficSecret;
var serverHsSecret = schedule.ServerHandshakeTrafficSecret;
 
// Application Traffic Secrets
var clientAppSecret = schedule.ClientApplicationTrafficSecret;
var serverAppSecret = schedule.ServerApplicationTrafficSecret;
 
// Resumption Secret
var resumptionSecret = schedule.ResumptionMasterSecret;

Data Classes

HybridKeyMaterial

Container for derived keys with secure memory cleanup.

Property Type Length Description
EncryptionKey byte[] 32 AES key
MacKey byte[] 32 HMAC key
Iv byte[] 16 Initialization vector
AuthKey byte[] 32 Authentication key
using HybridKeyMaterial keys = DeriveKeys();
 
// Keys are securely erased on Dispose()
// (CryptographicOperations.ZeroMemory)

TlsKeyMaterial

TLS 1.2 style key material.

Property Type Description
MasterSecret byte[] 48 bytes master secret
ClientWriteKey byte[] Client-side encryption key
ServerWriteKey byte[] Server-side encryption key
ClientWriteIv byte[] Client-side IV
ServerWriteIv byte[] Server-side IV
ClientWriteMacKey byte[] Client MAC key (empty for GCM)
ServerWriteMacKey byte[] Server MAC key (empty for GCM)

Tls13KeySchedule

TLS 1.3 key schedule.

Property Type Description
ClientHandshakeTrafficSecret byte[]? Client handshake traffic secret
ServerHandshakeTrafficSecret byte[]? Server handshake traffic secret
ClientApplicationTrafficSecret byte[]? Client application traffic secret
ServerApplicationTrafficSecret byte[]? Server application traffic secret
ResumptionMasterSecret byte[]? Session resumption secret

Methods Overview

HKDF

Method Parameters Return
DeriveKey byte[] sharedSecret, int outputLength, byte[]? salt, byte[]? info, HashAlgorithmName? byte[]
HkdfExtract byte[] ikm, byte[]? salt, HashAlgorithmName? byte[]
HkdfExpand byte[] prk, int outputLength, byte[]? info, HashAlgorithmName? byte[]

Hybrid

Method Parameters Return
DeriveHybridKey byte[]? classicSecret, byte[]? pqSecret, int outputLength, CryptoMode, byte[]? info byte[]
DeriveHybridKeyMaterial byte[]? classicSecret, byte[]? pqSecret, CryptoMode HybridKeyMaterial

PBKDF2

Method Parameters Return
Pbkdf2 string password, byte[] salt, int iterations, int outputLength, byte[]? pqEntropy, HashAlgorithmName? byte[]
Pbkdf2WithPqSalt string password, byte[] baseSalt, byte[] pqPublicKey, int iterations, int outputLength byte[]

Argon2id

Method Parameters Return
Argon2id byte[] password, byte[] salt, int outputLength, int iterations, int memoryKiB, int parallelism byte[]
Argon2id string password, byte[] salt, int outputLength, int iterations, int memoryKiB, int parallelism byte[]

TLS

Method Parameters Return
DeriveTlsKeys byte[] preMasterSecret, byte[] clientRandom, byte[] serverRandom, CryptoMode TlsKeyMaterial
DeriveTls13Keys byte[]? psk, byte[]? ecdh, byte[]? pq, byte[] clientHello, byte[] serverHello, CryptoMode Tls13KeySchedule

Complete Example

using WvdS.System.Security.Cryptography;
using WvdS.System.Security.Cryptography.KeyDerivation;
using WvdS.System.Security.Cryptography.KeyExchange;
 
// 1. Perform key exchange
using var session = new KeyExchangeService();
await session.InitiateKeyExchangeAsync(recipientPublicKey, CryptoMode.Hybrid);
 
// 2. Derive hybrid key material
using HybridKeyMaterial keys = KeyDerivationExtensions.DeriveHybridKeyMaterial(
    classicSecret: session.ClassicSharedSecret,
    pqSecret: session.PqSharedSecret,
    mode: CryptoMode.Hybrid);
 
// 3. Use the keys
using var aes = Aes.Create();
aes.Key = keys.EncryptionKey;
 
using var hmac = new HMACSHA256(keys.MacKey);
 
// 4. Perform encryption
// ...
 
// 5. Keys are automatically securely erased

Security Notes

  • All IDisposable classes implement CryptographicOperations.ZeroMemory
  • Argon2id requires OpenSSL 3.6 (not available in .NET BCL)
  • PBKDF2 with fewer than 100,000 iterations is not recommended
  • Salt must always be random and sufficiently long for PBKDF2/Argon2id (min. 16 bytes)
Hybrid mode security:

In hybrid mode, the final key is only compromised if BOTH secrets (classical AND PQ) are broken. This provides protection against both classical and quantum attacks.

See Also


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional

Zuletzt geändert: on 2026/01/30 at 12:13 AM