Fast, RFC compliant and easy to use implementation of HMAC-based Extract-and-Expand Key Derivation Function (HKDF) in .NET Standard
.NET Standard implementation of HKDF (HMAC-based Key Derivation Function).
Span<byte>
support.ECDiffieHellman
.Install the NuGet package HKDF.Standard
.
Use the methods of the Hkdf
class to perform extraction, expansion and key derivation:
using HkdfStandard;
using System.Security.Cryptography;
// Input values:
byte[] inputKeyMaterial = ...;
byte[] salt = ...;
byte[] info = ...;
int outputLength = ...;
// Results:
byte[] pseudoRandomKey;
byte[] outputKeyMaterial;
// Perform the Extract stage of HKDF with or without the salt:
pseudoRandomKey = Hkdf.Extract(HashAlgorithmName.SHA256, inputKeyMaterial, salt);
pseudoRandomKey = Hkdf.Extract(HashAlgorithmName.SHA256, inputKeyMaterial);
// Perform the Expand stage of HKDF with or without the context information:
outputKeyMaterial = Hkdf.Expand(HashAlgorithmName.SHA256, pseudoRandomKey, outputLength, info);
outputKeyMaterial = Hkdf.Expand(HashAlgorithmName.SHA256, pseudoRandomKey, outputLength);
// Perform the entire HKDF cycle in one go (Extract + Expand)
// optionally using the salt and/or the context information:
outputKeyMaterial = Hkdf.DeriveKey(HashAlgorithmName.SHA256, inputKeyMaterial, outputLength, salt, info);
outputKeyMaterial = Hkdf.DeriveKey(HashAlgorithmName.SHA256, inputKeyMaterial, outputLength, salt);
outputKeyMaterial = Hkdf.DeriveKey(HashAlgorithmName.SHA256, inputKeyMaterial, outputLength, info: info);
outputKeyMaterial = Hkdf.DeriveKey(HashAlgorithmName.SHA256, inputKeyMaterial, outputLength);
For information about:
Extract
stage can be skipped, please refer to the RFC 5869 section 3.3;salt
and when it can be omitted, see the RFC 5869 section 3.1;info
and when it can be omitted, see the RFC 5869 section 3.2;Based on the results of key derivation benchmark, HKDF.Standard is:
256-bit input key material, 256-bit salt, 256-bit context information
Windows 10 Pro x64, .NET 5.0, AMD Ryzen 7 Pro 1700X, single thread, Portable.BouncyCastle v1.9.0,
NSec v20.2.0
The benchmark source code is available at src/HkdfStandard.Benchmark
HKDF
class, which makes it is simple to migrate from one HKDF implementation to the other.ECDiffieHellman
HKDF is commonly used in conjunction with Diffie-Hellman (finite field or elliptic curve), where the Diffie-Hellman value (shared secret) is passed through HKDF to derive one or more shared keys.
Unfortunately, this scenario cannot be implemented straightforward with the ECDiffieHellman
class because it
doesn’t allow the export of raw shared secret. However, there is a method ECDiffieHellman.DeriveKeyFromHmac
that returns the value of shared secret that was passed through HMAC — this is the same transformation that the input key material undergoes when being passed through the HKDF’s Extract stage. Therefore, the workaround is to skip the Extract stage of HKDF and substitute it with ECDiffieHellman
‘s additional HMAC operation:
using HkdfStandard;
using System.Security.Cryptography;
byte[] salt = ...;
byte[] info = ...;
int outputLength = ...;
// My instance of ECDH, contains a new randomly generated key pair:
using var myEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
// Other party's instance of ECDH, contains a new randomly generated key pair:
using var otherEcdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP256);
// Derive the shared ECDH secret and pass it through HMAC along with the salt (as HMAC's message and key respectively).
// This is equivalent to deriving a raw shared secret and running it through the HKDF Extract, which gives a shared pseudorandom key:
byte[] pseudoRandomKey = myEcdh.DeriveKeyFromHmac(otherEcdh.PublicKey, HashAlgorithmName.SHA256, salt);
// Perform the Expand stage of HKDF as usual:
byte[] outputKeyMaterial = Hkdf.Expand(HashAlgorithmName.SHA256, pseudoRandomKey, outputLength, info);
byte[]
Methods
byte[] Extract(HashAlgorithmName hashAlgorithmName, byte[] ikm, byte[]? salt = null);
Extracts a pseudorandom key from the input key material.
byte[] Expand(HashAlgorithmName hashAlgorithmName, byte[] prk, int outputLength, byte[]? info = null);
Expands the pseudorandom key into an output keying material.
byte[] DeriveKey(HashAlgorithmName hashAlgorithmName, byte[] ikm, int outputLength, byte[]? salt = null, byte[]? info = null);
Derives an output keying material from the input key material (performs extraction and expansion) in one go.
Span<byte>
Methods
int Extract(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> ikm, ReadOnlySpan<byte> salt, Span<byte> prk);
Extracts a pseudorandom key from the input key material.
void Expand(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> prk, Span<byte> output, ReadOnlySpan<byte> info);
Expands the pseudorandom key into an output keying material.
void DeriveKey(HashAlgorithmName hashAlgorithmName, ReadOnlySpan<byte> ikm, Span<byte> output, ReadOnlySpan<byte> salt, ReadOnlySpan<byte> info);
Derives an output keying material from the input key material (performs extraction and expansion) in one go.
byte[]
methods are available on the platforms that support .NET Standard 1.3:
Span<byte>
methods are available on the platforms that support .NET Standard 2.1: