Skip to content

Instantly share code, notes, and snippets.

@charlesportwoodii
Last active November 9, 2020 17:34
Show Gist options
  • Save charlesportwoodii/7c5cf32e92ee88fec5e8f3270d0b44fc to your computer and use it in GitHub Desktop.
Save charlesportwoodii/7c5cf32e92ee88fec5e8f3270d0b44fc to your computer and use it in GitHub Desktop.
RFC 5869, HMAC-based Extract-and-Expand Key Derivation Function (HKDF) in C# for Universal Windows Platform (UWP)

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

The following gists implement HMAC-based Extract-and-Expand Key Derivation Function (HKDF) in C# for Universal Windows Platform (UWP). The class should be portable to Windows 10, Windows 10 Mobile, and WinRT.

The implementation details are outlined in RFC 5869.

Usage

The HKDF class can be initialized with one of MacAlgorithmNames outlined in https://msdn.microsoft.com/en-us/library/windows/apps/windows.security.cryptography.core.macalgorithmnames.aspx as the first arguement

string algorithm = MacAlgorithmNames.HmacSha256;
var hkdf = new HKDF(algorithm);

An IBuffer containing your key materials that can be used for either SymmetricKeyProvider or MacAlgorithmProvider can then be derived as follows:

// The encoding to use
BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;

// Initial key materials
IBuffer ikm = CryptographicBuffer.ConvertStringToBinary("ikm", encoding);

// The public info
IBuffer info = CryptographicBuffer.ConvertStringToBinary("public_info", encoding);

// The output size
uint keyByteSize = 16; // MacAlgorithmNames.HmacSha256 => 16

// keyMaterials contains the raw data
IBuffer keyMaterials = hkdf.deriveKey(ikm, info, keyByteSize);

The raw key materials can be viewed by outputting the IBuffer

System.Diagnostics.Debug.WriteLine(
    CryptographicBuffer.EncodeToBase64String(keyMaterials);
);

To use for AES-CBC decryption, using with SymmetricKeyProvider as follows:

// AES-CBC
SymmetricKeyAlgorithmProvider provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC");
CryptographicKey key = provider.CreateSymmetricKey(keyMaterials);

IBuffer decryptedBuffer = CryptographicEngine.Decrypt(key, message, iv);
string data = CryptographicBuffer.ConvertBinaryToString(encoding, decryptedBuffer);
/**
* Hash based KDF
* Implements RFC 5869 :: https://tools.ietf.org/html/rfc5869
*/
public class HKDF
{
/**
* The HMAC algorithm provider
* @var MacAlgoritimProvider provided
*/
private readonly MacAlgorithmProvider provider;
/**
* The digest length
* @var int digestLength
*/
private readonly int digestLength;
/**
* Constructor
* @param string algorithm
*/
public HKDF(string algorithm)
{
provider = MacAlgorithmProvider.OpenAlgorithm(algorithm);
// Computer the digest length
digestLength = (int)(provider.MacLength);
}
/**
* Derives a key using the supplied parameters
* @param IBuffer ikm The initial key material
* @param IBuffer info The public info header
* @param int outputLength The length we want to extract
* @param IBuffer salt (default null)
* @return CryptographicKey
*/
public IBuffer deriveKey(IBuffer ikm, IBuffer info, uint outputLength, IBuffer salt = null)
{
// Null check the info buffer
if (info == null)
info = CryptographicBuffer.CreateFromByteArray(new byte[0]);
// "if [salt] not provided, is set to a string of HashLen zeroes."
if (salt == null)
salt = CryptographicBuffer.CreateFromByteArray(new byte[this.digestLength]);
IBuffer prk = extract(salt, ikm);
// This check is uselss, but serves as a remidner to the spec
if (prk.Length < this.digestLength)
throw new Exception("Psuedo-random key is larger then digest length. Cannot perform operation");
IBuffer orm = expand(prk, info, outputLength);
return orm;
}
/**
* Extracts the psuedo-random key
* @param IBuffer salt
* @param IBuffer ikm
* @return IBuffer
*/
private IBuffer extract(IBuffer salt, IBuffer ikm)
{
return HMAC(salt, ikm);
}
/**
* Expands the key to the desired length
* @param IBuffer prk
* @param IBuffer info
* @param int outputLength
* @return CryptographicKey
*/
private IBuffer expand(IBuffer prk, IBuffer infoBuff, uint l)
{
int outputLength = (int)l;
// Sanity check the desired output length
if (l < 0 || l > 255 * this.digestLength)
throw new Exception("Bad output length requested of HKDF");
// Convert IBuffers to byte[]
byte[] info;
CryptographicBuffer.CopyToByteArray(infoBuff, out info);
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;
// Copy out the byte array
IBuffer messageBuff = CryptographicBuffer.CreateFromByteArray(currentInfo);
IBuffer hmac = HMAC(prk, messageBuff);
CryptographicBuffer.CopyToByteArray(hmac, out resultBlock);
Array.Copy(resultBlock, 0, result, outputLength - bytesRemaining, Math.Min(resultBlock.Length, bytesRemaining));
bytesRemaining -= resultBlock.Length;
}
return CryptographicBuffer.CreateFromByteArray(result);
}
/**
* Hashed MAC on the provided buffer
* @param IBuffer key
* @param IBuffer message
* @return IBuffer
*/
private IBuffer HMAC(IBuffer key, IBuffer message)
{
CryptographicKey saltKey = provider.CreateKey(key);
IBuffer data = CryptographicEngine.Sign(saltKey, message);
if (data.Length != provider.MacLength)
throw new Exception("Error computer digest");
return data;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment