Skip to content

Fast, cross-platform, RFC-compliant implementation of HMAC-based Extract-and-Expand Key Derivation Function (HKDF) in .NET Standard

License

Notifications You must be signed in to change notification settings

andreimilto/HKDF.Standard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Get NuGet Package

AppVeyor Build AppVeyor Tests GitHub License

HKDF.Standard

.NET Standard implementation of HKDF (HMAC-based Key Derivation Function).

Features

Getting Started

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:

Performance

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%

Chart: derivation of 128-bit key Chart: derivation of 4096-bit key

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

Migration to and from .NET's HKDF

  • 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.

Using HKDF.Standard with 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);

Functionality

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.

Platform Support

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)