Created
January 30, 2014 14:57
-
-
Save CodesInChaos/8710228 to your computer and use it in GitHub Desktop.
HKDF in C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
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
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.