.NET Standard implementation of HKDF (HMAC-based Key Derivation Function).
- High performance.
Span<byte>
support.- Can be used with
ECDiffieHellman
. - Easy migration to and from the new HKDF primitive introduced in .NET 5.
- One-shot methods for extraction, expansion and key derivation.
- Supported hash functions: SHA-512, SHA-384, SHA-256, SHA-1 and MD5.
- Available in .NET 5 and higher, .NET Core, .NET Framework, Mono, Blazor WebAssembly, Xamarin, UWP and Unity (see platform support).
- Compliant with RFC 5869.
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:
- whether the
Extract
stage can be skipped, please refer to the RFC 5869 section 3.3; - how to use
salt
and when it can be omitted, see the RFC 5869 section 3.1; - how to use
info
and when it can be omitted, see the RFC 5869 section 3.2; - HKDF in general, please refer to the original paper.
Based on the results of key derivation benchmark, HKDF.Standard is:
- 2.7 - 7.4 times faster than NSec
- 1.4 - 5.8 times faster than Bouncy Castle
- on par with .NET 5 – perfomance difference does not exceed ±10%
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
- Methods in the HKDF.Standard library have the same signatures as in the .NET's
HKDF
class, which makes it is simple to migrate from one HKDF implementation to the other. - Microsoft's implementation of HKDF will be available only in .NET 5 and onwards. Consider using HKDF.Standard if your project targets one of the older frameworks. If later you decide to upgrade the project to .NET 5 or higher, it will be relatively easy to swap the implementation of HKDF with the Microsoft's, if necessary.
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[] 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.
-
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:
- .NET 5 and higher
- .NET Core 1.0 and higher
- .NET Framework 4.6 and higher
- Mono 4.6 and higher
- Blazor WebAssembly 3.2.0 and higher, except for 5.x.x and 6.x.x, support was resumed in 7.0.0 (for SHA-family only, MD5 - unsupported)
- Xamarin.iOS 10.0 and higher
- Xamarin.Mac 3.0 and higher
- Xamarin.Android 7.0 and higher
- Unity 2018.1 and higher
- UWP 10.0 and higher
Span<byte>
methods are available on the platforms that support .NET Standard 2.1:
- .NET 5 and higher
- .NET Core 3.0 and higher
- Mono 6.4 and higher
- Blazor WebAssembly 3.2.0 and higher, except for 5.x.x and 6.x.x, support was resumed in 7.0.0 (for SHA-family only, MD5 - unsupported)
- Xamarin.iOS 12.16 and higher
- Xamarin.Mac 5.16 and higher
- Xamarin.Android 10.0 and higher
- Unity 2021.2 and higher
- UWP - currently not supported (expected in the future)