Instantly share code, notes, and snippets.

Embed
What would you like to do?
HKDF in C#
class Hkdf
{
Func<byte[],byte[],byte[]> keyedHash;
public Hkdf()
{
var hmac = new HMACSHA256();
keyedHash = (key, message)=>
{
hmac.Key=key;
return hmac.ComputeHash(message);
};
}
public byte[] Extract(byte[] salt, byte[] inputKeyMaterial)
{
return keyedHash(salt, inputKeyMaterial);
}
public 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 = keyedHash(prk, currentInfo);
Array.Copy(resultBlock, 0, result, outputLength - bytesRemaining, Math.Min(resultBlock.Length, bytesRemaining));
bytesRemaining -= resultBlock.Length;
}
return result;
}
public byte[] DeriveKey(byte[] salt, byte[] inputKeyMaterial, byte[] info, int outputLength)
{
var prk = Extract(salt, inputKeyMaterial);
var result = Expand(prk, info, outputLength);
return result;
}
}
var hkdf = new Hkdf();
{
var ikm = StringToByteArray("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
var salt = StringToByteArray("000102030405060708090a0b0c");
var info = StringToByteArray("f0f1f2f3f4f5f6f7f8f9");
var L = 42;
var prk = "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5";
var okm = "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865";
var actualPrk = ByteArrayToString(hkdf.Extract(salt, ikm));
var actualOkm = ByteArrayToString(hkdf.DeriveKey(salt, ikm, info, L));
(prk == actualPrk).Dump();
(okm == actualOkm).Dump();
}
{
var ikm = StringToByteArray("000102030405060708090a0b0c0d0e0f"+
"101112131415161718191a1b1c1d1e1f"+
"202122232425262728292a2b2c2d2e2f"+
"303132333435363738393a3b3c3d3e3f"+
"404142434445464748494a4b4c4d4e4f");
var salt = StringToByteArray("606162636465666768696a6b6c6d6e6f"+
"707172737475767778797a7b7c7d7e7f"+
"808182838485868788898a8b8c8d8e8f"+
"909192939495969798999a9b9c9d9e9f"+
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf");
var info = StringToByteArray("b0b1b2b3b4b5b6b7b8b9babbbcbdbebf"+
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf"+
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf"+
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef"+
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
var L = 82;
var prk = "06a6b88c5853361a06104c9ceb35b45cef760014904671014a193f40c15fc244";
var okm = "b11e398dc80327a1c8e7f78c596a4934"+
"4f012eda2d4efad8a050cc4c19afa97c"+
"59045a99cac7827271cb41c65e590e09"+
"da3275600c2f09b8367793a9aca3db71"+
"cc30c58179ec3e87c14c01d5c1f3434f"+
"1d87";
var actualPrk = ByteArrayToString(hkdf.Extract(salt, ikm));
var actualOkm = ByteArrayToString(hkdf.DeriveKey(salt, ikm, info, L));
(prk == actualPrk).Dump();
(okm == actualOkm).Dump();
}
{
var ikm = StringToByteArray("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
var salt = new byte[0];
var info = new byte[0];
var L = 42;
var prk = "19ef24a32c717b167f33a91d6f648bdf96596776afdb6377ac434c1c293ccb04";
var okm = "8da4e775a563c18f715f802a063c5a31"+
"b8a11f5c5ee1879ec3454e5f3c738d2d"+
"9d201395faa4b61a96c8";
var actualPrk = ByteArrayToString(hkdf.Extract(salt, ikm));
var actualOkm = ByteArrayToString(hkdf.DeriveKey(salt, ikm, info, L));
(prk == actualPrk).Dump();
(okm == actualOkm).Dump();
}
@rclabo

This comment has been minimized.

rclabo commented Feb 17, 2017

Thank you for sharing your code. In the end I decided to write my own HKDF class rather than using yours so that I would more fully understand the https://tools.ietf.org/html/rfc5869 spec and how HKDF works. But seeing that your code was so short definitely encouraged me to try my hand at it and actually read the spec. Thank you.

@rclabo

This comment has been minimized.

rclabo commented Feb 23, 2017

CodesInChaos - Now that I realize you are active on crypto.stackexchange.com and have have a strong reputation, I should have just used this gist you provided for HKDF rather than creating my own. But at the time I didn't realize it, and I guess it was a great learning experience for me that was good. The only thing you might want to change in your code is to use a using statement in var hmac = new HMACSHA256(); since HMACSHA256 implements IDisposable. Personally I wish the rfc provided more test vectors, three just feels so skimpy but I couldn't find any more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment