Skip to content

Instantly share code, notes, and snippets.

@charlesportwoodii
Last active January 21, 2022 22:17
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save charlesportwoodii/09ffd6868c2a6e55826c4d5ebb509651 to your computer and use it in GitHub Desktop.
Save charlesportwoodii/09ffd6868c2a6e55826c4d5ebb509651 to your computer and use it in GitHub Desktop.
RFC 5869, HMAC-based Extract-and-Expand Key Derivation Function (HKDF) in C# for .Net Core

HMAC-based Extract-and-Expand Key Derivation Function (HKDF)

https://tools.ietf.org/html/rfc5869

public static HKDF HKDF(HashAlgorithmName algorithm, byte[] ikm, byte[] info, int outputLength = 0, byte[] salt = null)

Properties

hash

The resulting hash from the HKDF calculation

Parameters

HKDF takes 5 parameters:

  1. A HashAlgorithmName algorithm as defined from https://docs.microsoft.com/en-us/dotnet/core/api/system.security.cryptography.hashalgorithmname.
    HashAlgorithmName.HmacMd5
    HashAlgorithmName.HmacSha1
    HashAlgorithmName.HmacSha256
    HashAlgorithmName.HmacSha384
    HashAlgorithmName.HmacSha512
  2. The initial keying material ikm represented in bytes.
  3. The additional authentication information info.
  4. A byte salt.
  5. The desired outputLength represented as an integer. If 0 is set, the outputLength will be set to the algorithm length.

Example

// This is the hash algorithm to use
var algorithm = HashAlgorithmName.SHA256;

// ikm, info, and salt can be variable length - for an example we're just going to generate some random bytes
var rng = new Random();
var ikm = new byte[32];
var salt = new byte[32];
var info = new byte[4];
rng.NextBytes(ikm);
rng.NextBytes(salt);
rng.NextBytes(info);

// Our HKDF can be calculated as follows.
var hkdf = new HKDF(algorithm, ikm, info, l, salt);
var hash = hkdf.hash;
using System;
using System.Security.Cryptography;
class HKDF
{
public byte[] hash { get; set; }
private readonly HashAlgorithmName algorithm;
private readonly HMAC hmac;
private readonly int digestLength;
public HKDF(HashAlgorithmName algo, byte[] ikm, byte[] info, int outputLength = 0, byte[] salt = null)
{
this.algorithm = algo;
this.hmac = this.determineHMAC();
this.digestLength = this.hmac.ComputeHash(ikm).Length;
if (outputLength == 0) {
outputLength = this.digestLength;
}
if (outputLength < 0 || outputLength > 255 * digestLength) {
throw new Exception("Bad output length requested of HKDF");
}
this.hash = this.deriveKey(salt, ikm, info, outputLength);
}
private HMAC determineHMAC()
{
if (this.algorithm == HashAlgorithmName.MD5) {
return new HMACMD5();
} else if (this.algorithm == HashAlgorithmName.SHA1) {
return new HMACSHA1();
} else if (this.algorithm == HashAlgorithmName.SHA256) {
return new HMACSHA256();
} else if (this.algorithm == HashAlgorithmName.SHA384) {
return new HMACSHA384();
} else if (this.algorithm == HashAlgorithmName.SHA512) {
return new HMACSHA512();
}
return new HMACSHA256();
}
private byte[] extract(byte[] salt, byte[] ikm)
{
return this.HMAC(salt, ikm);
}
private byte[] HMAC(byte[] key, byte[] message)
{
var hmac = this.hmac;
hmac.Key = key;
return hmac.ComputeHash(message);
}
private byte[] expand(byte[] prk, byte[] info, int outputLength)
{
var resultBlock = new byte[0];
var result = new byte[outputLength];
var bytesRemaining = outputLength;
for (int i = 1; bytesRemaining > 0; i++)
{
var currentInfo = new byte[resultBlock.Length + info.Length + 1];
Array.Copy(resultBlock, 0, currentInfo, 0, resultBlock.Length);
Array.Copy(info, 0, currentInfo, resultBlock.Length, info.Length);
currentInfo[currentInfo.Length - 1] = (byte)i;
resultBlock = this.HMAC(prk, currentInfo);
Array.Copy(resultBlock, 0, result, outputLength - bytesRemaining, Math.Min(resultBlock.Length, bytesRemaining));
bytesRemaining -= resultBlock.Length;
}
return result;
}
private byte[] deriveKey(byte[] salt, byte[] ikm, byte[] info, int outputLength)
{
if (info == null) {
info = new byte[0];
}
if (salt == null) {
salt = new byte[this.digestLength];
}
var prk = extract(salt, ikm);
if (prk.Length < digestLength) {
throw new Exception("Psuedo-random key is larger then digest length. Cannot perform operation");
}
var result = expand(prk, info, outputLength);
return result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment