Inhaltsverzeichnis
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);
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
IDisposableclasses implementCryptographicOperations.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)
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