dotnet add package HkdfDotNet --version 1.0.1
NuGet\Install-Package HkdfDotNet -Version 1.0.1
<PackageReference Include="HkdfDotNet" Version="1.0.1" />
paket add HkdfDotNet --version 1.0.1
#r "nuget: HkdfDotNet, 1.0.1"
// Install HkdfDotNet as a Cake Addin #addin nuget:?package=HkdfDotNet&version=1.0.1 // Install HkdfDotNet as a Cake Tool #tool nuget:?package=HkdfDotNet&version=1.0.1
You can find the NuGet package here.
- Download the latest release.
- Move the downloaded
.dllfile into your project folder.
- Click on the
Add Project Reference...in Visual Studio.
- Go to
Browse, click the
Browsebutton, and select the downloaded
using HkdfDotNet;to the top of each code file that needs the library.
- HKDF is NOT suitable for passwords. You MUST use a password-based KDF algorithm, such as Argon2 or scrypt, for such purposes. Both algorithms are available in the libsodium library.
- Although the
infoparameter is optional according to the specification, it SHOULD always be used to insert some randomness into HKDF and to bind derived keys to application- and context-specific information. Inadequate context information CAN lead to subtle vulnerabilities. The info SHOULD be independent of the input keying material (IKM).
saltparameter SHOULD NOT be used. You SHOULD randomly generate one 128-bit or 256-bit salt that gets used for all derived subkeys but placed into the
infoparameter alongside other context information using concatenation. Make sure you AVOID canonicalization attacks when doing this.
HKDF consists of three different functions:
DeriveKey(): if the input keying material (IKM) is not distributed uniformly (e.g. a shared secret from a key exchange) and you want to derive one or more keys using the same context information for all of those keys, then you should use this function. This calls the
Extract(): if the input keying material (IKM) is not distributed uniformly (e.g. a shared secret from a key exchange) and you want to derive one or more keys using different context information for different keys, then you should call this function once to derive a pseudorandom key. Then you can call
Expand()on the output to derive multiple subkeys, as explained below.
Expand(): if the input keying material (IKM) is distributed uniformly (e.g. the output of
Extract()or randomly generated using a cryptographically secure random number generator), then you can call this function multiple times to derive separate subkeys using different context information but the same randomly generated salt.
Here are my recommendations:
HashAlgorithmName: use SHA512 in all cases.
inputKeyingMaterial: at least 256-bits long and MUST be high-entropy (e.g. a shared secret, NOT a password).
outputLength: at least 256-bits, which is equivalent to 32 bytes. If deriving multiple keys using one call to
DeriveKey(), then use an output length as long as the required key lengths added together (e.g. a 256-bit encryption key and a 512-bit MAC key means a 96 byte output length).
salt: leave this null to get the standard security definition for HKDF. Randomly generate one 128-bit or 256-bit salt for all subkeys and concatenate it to the
infoparameter instead, as explained below.
info: one randomly generated 128-bit or 256-bit salt used for all subkeys, followed by a different hardcoded, globally unique, application-specific string for each subkey converted into a byte array using UTF8 encoding. The default context string format should be
"[application] [date and time] [purpose]". You can also include things like protocol/version numbers, algorithm identifiers, and user identities. The salt and context information should be concatenated together using Array.Copy() in a way that is resistant to canonicalization attacks (please see that link and the code example below).
Here is an example of how to derive a 256-bit key for an AEAD, followed by how to derive two separate keys for Encrypt-then-MAC, using an X25519 shared secret as the input keying material (IKM), a random 128-bit salt, and hardcoded context strings:
const int saltLength = 16; const string context = "HKDF Demo 15/11/2021 21:52 Deriving an encryption key for ChaCha20-Poly1305"; const int outputLength = 32; // The high-entropy input keying material (NOT a password) that you want to derive subkeys from byte inputKeyingMaterial = GetSharedSecret(senderPrivateKey, recipientPublicKey); // A random 128-bit or 256-bit salt to feed into info byte salt = SecureRandom.GetBytes(saltLength); // The hardcoded, globally unique, and application-specific context information byte info = Encoding.UTF8.GetBytes(context); // Convert the length of each parameter to concatenate in info into bytes byte saltLength = BitConversion.GetBytes(salt.Length); byte infoLength = BitConversion.GetBytes(info.Length); // Concatenate the salt, info, and lengths info = Arrays.Concat(salt, info, saltLength, infoLength); // Derive a single subkey (e.g. for encryption with an AEAD, like ChaCha20-Poly1305 or AES-GCM) byte subkey = Hkdf.DeriveKey(HashAlgorithmName.SHA512, inputKeyingMaterial, outputLength, info); // Or derive multiple subkeys (e.g. for Encrypt-then-MAC) byte pseudorandomKey = Hkdf.Extract(HashAlgorithmName.SHA512, inputKeyingMaterial); byte encryptionInfo = Encoding.UTF8.GetBytes("HKDF Demo 15/11/2021 21:54 ChaCha20 encryption key"); byte encryptionInfoLength = BitConversion.GetBytes(encryptionInfo.Length); encryptionInfo = Arrays.Concat(salt, encryptionInfo, saltLength, encryptionInfoLength); byte encryptionKey = Hkdf.Expand(HashAlgorithmName.SHA512, pseudorandomKey, outputLength, encryptionInfo); byte macInfo = Encoding.UTF8.GetBytes("HKDF Demo 15/11/2021 21:55 BLAKE2b MAC key"); byte macInfoLength = BitConversion.GetBytes(macInfo.Length); macInfo = Arrays.Concat(salt, macInfo, saltLength, macInfoLength); byte macKey = Hkdf.Expand(HashAlgorithmName.SHA512, pseudorandomKey, outputLength * 2, macInfo);
|Product||Versions Compatible and additional computed target framework versions.|
|.NET||net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed.|
|.NET Core||netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed.|
|.NET Standard||netstandard2.0 is compatible. netstandard2.1 was computed.|
|.NET Framework||net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed.|
|MonoAndroid||monoandroid was computed.|
|MonoMac||monomac was computed.|
|MonoTouch||monotouch was computed.|
|Tizen||tizen40 was computed. tizen60 was computed.|
|Xamarin.iOS||xamarinios was computed.|
|Xamarin.Mac||xamarinmac was computed.|
|Xamarin.TVOS||xamarintvos was computed.|
|Xamarin.WatchOS||xamarinwatchos was computed.|
- No dependencies.
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.