File: System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Binary;
using System.Diagnostics;
 
namespace System.Security.Cryptography
{
    internal sealed partial class SP800108HmacCounterKdfImplementationManaged
    {
        private const int CharToBytesStackBufferSize = 256;
 
        public SP800108HmacCounterKdfImplementationManaged(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
        {
            // Use the POH if we can so the key doesn't get moved around by the GC.
            _key = GC.AllocateArray<byte>(key.Length, pinned: true);
            key.CopyTo(_key);
            _hashAlgorithm = hashAlgorithm;
        }
 
        internal static void DeriveBytesOneShot(
            ReadOnlySpan<byte> key,
            HashAlgorithmName hashAlgorithm,
            ReadOnlySpan<byte> label,
            ReadOnlySpan<byte> context,
            Span<byte> destination)
        {
            if (destination.Length == 0)
            {
                return;
            }
 
            Debug.Assert(destination.Length <= 0x1FFFFFFF);
 
            // Do everything as checked. Over/underflows are never expected.
            checked
            {
                // The KDF is defined as K(i) := PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)
                // We know L is already less than 0x1FFFFFFF. h = ceil(L / h) where H is the hash length in bits.
                // So we don't expect i to overflow.
                using (IncrementalHash hash = IncrementalHash.CreateHMAC(hashAlgorithm, key))
                {
                    Span<byte> iBuffer = stackalloc byte[sizeof(uint)];
                    Span<byte> lBuffer = stackalloc byte[sizeof(uint)];
                    ReadOnlySpan<byte> zero = [0];
                    Span<byte> hmacBuffer = stackalloc byte[512 / 8]; // Largest HMAC supported is SHA512
                    int hmacBufferWritten = 0;
 
                    BinaryPrimitives.WriteUInt32BigEndian(lBuffer, (uint)destination.Length * 8U);
 
                    for (uint i = 1; !destination.IsEmpty; i++)
                    {
                        BinaryPrimitives.WriteUInt32BigEndian(iBuffer, i);
                        hash.AppendData(iBuffer);
                        hash.AppendData(label);
                        hash.AppendData(zero);
                        hash.AppendData(context);
                        hash.AppendData(lBuffer);
 
                        if (destination.Length >= hash.HashLengthInBytes)
                        {
                            int written = hash.GetHashAndReset(destination);
                            destination = destination.Slice(written);
                        }
                        else
                        {
                            hmacBufferWritten = hash.GetHashAndReset(hmacBuffer);
                            Debug.Assert(hmacBufferWritten > destination.Length);
                            hmacBuffer.Slice(0, destination.Length).CopyTo(destination);
                            destination = default;
                        }
                    }
 
                    // Get derived key material off the stack, if any.
                    CryptographicOperations.ZeroMemory(hmacBuffer.Slice(0, hmacBufferWritten));
                }
            }
        }
 
         internal static void DeriveBytesOneShot(
            ReadOnlySpan<byte> key,
            HashAlgorithmName hashAlgorithm,
            ReadOnlySpan<char> label,
            ReadOnlySpan<char> context,
            Span<byte> destination)
        {
            if (destination.Length == 0)
            {
                return;
            }
 
            using (Utf8DataEncoding labelData = new Utf8DataEncoding(label, stackalloc byte[CharToBytesStackBufferSize]))
            using (Utf8DataEncoding contextData = new Utf8DataEncoding(context, stackalloc byte[CharToBytesStackBufferSize]))
            {
                DeriveBytesOneShot(key, hashAlgorithm, labelData.Utf8Bytes, contextData.Utf8Bytes, destination);
            }
        }
    }
}