Skip to content

Instantly share code, notes, and snippets.

@sdrapkin
Created January 6, 2024 20:47
Show Gist options
  • Save sdrapkin/ed38c62c926990f73f99609b103c1279 to your computer and use it in GitHub Desktop.
Save sdrapkin/ed38c62c926990f73f99609b103c1279 to your computer and use it in GitHub Desktop.
Dangers of HMAC, HKDF.Expand, and SP800_108_Ctr
void Main() //LINQPad
{
var key1 = RandomNumberGenerator.GetBytes(128 + 1);
var hash_of_key1 = SHA512.HashData(key1);
var empty = Array.Empty<byte>();
(Enumerable.SequenceEqual(key1, hash_of_key1)).Dump("key1 == hash_of_key1 ?");
{
var r1 = Convert.ToHexString(HMACSHA512.HashData(key: key1, source: empty));
var r2 = Convert.ToHexString(HMACSHA512.HashData(key: hash_of_key1, source: empty));
$"key1: {r1}\nkey2: {r2}".Dump(nameof(HMACSHA512));
}
{
var r1 = Convert.ToHexString(
HKDF.Expand(HashAlgorithmName.SHA512, prk: key1, outputLength: 64, info: empty));
var r2 = Convert.ToHexString(
HKDF.Expand(HashAlgorithmName.SHA512, prk: hash_of_key1, outputLength: 64, info: empty));
$"key1: {r1}\nkey2: {r2}".Dump(nameof(HKDF) + "." + nameof(HKDF.Expand));
}
{
var buffer = new byte[64];
SP800_108_Ctr.DeriveKey(key: key1, label: empty, context: empty, derivedOutput: buffer);
var r1 = Convert.ToHexString(buffer); buffer.AsSpan().Clear();
SP800_108_Ctr.DeriveKey(key: hash_of_key1, label: empty, context: empty, derivedOutput: buffer);
var r2 = Convert.ToHexString(buffer);
$"key1: {r1}\nkey2: {r2}".Dump(nameof(SP800_108_Ctr));
}
}//Main()
public static class SP800_108_Ctr
{
const int COUNTER_LENGTH = sizeof(uint), DERIVED_KEY_LENGTH_LENGTH = sizeof(uint);
static void FillBuffer(Span<byte> buffer, Span<byte> label, Span<byte> context, uint keyLengthInBits)
{
int labelLength = label.Length;
int contextLength = context.Length;
// store label, if any
if (labelLength > 0)
{
label.CopyTo(buffer.Slice(COUNTER_LENGTH));
}
// store context, if any
if (contextLength > 0)
{
context.CopyTo(buffer.Slice(COUNTER_LENGTH + labelLength + 1));
}
// store key length
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(buffer.Length - DERIVED_KEY_LENGTH_LENGTH), keyLengthInBits);
}// CreateBuffer()
public static void DeriveKey(byte[] key, Span<byte> label, Span<byte> context, Span<byte> derivedOutput, uint counter = 1)
{
int bufferLength = (COUNTER_LENGTH /* counter */) + (label.Length + 1 /* label + 0x00 */) + (context.Length /* context */) + (DERIVED_KEY_LENGTH_LENGTH /* [L]_2 */);
Span<byte> buffer = (bufferLength > 1024) ? new byte[bufferLength] : stackalloc byte[bufferLength];
FillBuffer(buffer, label: label, context: context, keyLengthInBits: checked((uint)(derivedOutput.Length << 3)));
DeriveKeyInternal(key, buffer, derivedOutput, counter);
}// DeriveKey()
static void DeriveKeyInternal(byte[] key, Span<byte> buffer, Span<byte> derivedOutput, uint counter = 1)
{
int derivedOutputCount = derivedOutput.Length, derivedOutputOffset = 0;
const int K_i_LENGTH = 64;
Span<byte> K_i = stackalloc byte[K_i_LENGTH];
checked
{
// Calculate each K_i value and copy the leftmost bits to the output buffer as appropriate.
while (derivedOutputCount > 0)
{
BinaryPrimitives.WriteUInt32BigEndian(buffer, counter); // update the counter within the buffer
HMACSHA512.TryHashData(key, buffer, K_i, out int _);
// copy the leftmost bits of K_i into the output buffer
int numBytesToCopy = derivedOutputCount > K_i_LENGTH ? K_i_LENGTH : derivedOutputCount;
K_i.Slice(0, numBytesToCopy).CopyTo(derivedOutput.Slice(derivedOutputOffset));
derivedOutputOffset += numBytesToCopy;
derivedOutputCount -= numBytesToCopy;
++counter;
}//while
}//checked
K_i.Clear();
}// DeriveKeyInternal()
}// class SP800_108_Ctr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment