This introduces a class for producing values per the "KDF in Counter Mode" section in NIST Special Publication 800-108r1 (Recommendation for Key Derivation Using Pseudorandom Functions).
The `SP800108HmacCounterKdf` class is part of the inbox cryptography library on .NET 8. Based on demonstrated need for older TFMs, the type is also being exposed via a NuGet package: Microsoft.Bcl.Cryptography. This package may, in the future, contain other types that belong as part of inbox cryptography but have a demonstrated need to be available to older TFMs.
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
}
- internal static bool PseudoHandlesSupported { get; } = OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
+ internal static bool PseudoHandlesSupported { get; } =
+#if NET
+ OperatingSystem.IsWindowsVersionAtLeast(10, 0, 0);
+#elif NETSTANDARD2_0_OR_GREATER
+ RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && Environment.OSVersion.Version.Major >= 10;
+#elif NETFRAMEWORK
+ Environment.OSVersion.Version.Major >= 10;
+#else
+#error Unhandled platform targets
+#endif
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+#pragma warning disable CA1510
+
+namespace System.Security.Cryptography
+{
+ /// <summary>
+ /// NIST SP 800-108 HMAC CTR Key-Based Key Derivation (KBKDF)
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// This implements NIST SP 800-108 HMAC in counter mode. The implemented KDF assumes the form of
+ /// <c>PRF (KI, [i]2 || Label || 0x00 || Context || [L]2)</c> where <c>[i]2</c> and <c>[L]2</c> are encoded as
+ /// unsigned 32-bit integers, big endian.
+ /// </para>
+ /// <para>
+ /// All members of this class are thread safe. If the instance is disposed of while other threads are using
+ /// the instance, those threads will either receive an <see cref="ObjectDisposedException" /> or produce a valid
+ /// derived key.
+ /// </para>
+ /// </remarks>
+ public sealed partial class SP800108HmacCounterKdf : IDisposable
+ {
+ // The maximum amount of data that we can produce with the PRF is 0x1FFFFFFF.
+ // This is because of L[2]. From SP 800-108 r1:
+ // L – An integer specifying the requested length (in bits) of the derived keying material KOUT.
+ // As an unsigned 32-bit interger (see r), L needs to become L[2] by multiplying by 8 (bytes to bits).
+ // We can't encode more than 0x1FFFFFFF as bits without overflowing.
+ // Windows' BCryptKeyDerivation cannot fullfill a request larger than 0x1FFFFFFF, either.
+ private const int MaxPrfOutputSize = (int)(uint.MaxValue / 8);
+
+ private readonly SP800108HmacCounterKdfImplementationBase _implementation;
+
+ private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm);
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="SP800108HmacCounterKdf" /> using a specified key and HMAC algorithm.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public SP800108HmacCounterKdf(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ _implementation = CreateImplementation(key, hashAlgorithm);
+ }
+
+ /// <summary>
+ /// Initializes a new instance of <see cref="SP800108HmacCounterKdf" /> using a specified key and HMAC algorithm.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="key" /> is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public SP800108HmacCounterKdf(byte[] key, HashAlgorithmName hashAlgorithm)
+ {
+ // This constructor doesn't defer to the span constructor because SP800108HmacCounterKdfImplementationCng
+ // has a constructor for byte[] key to avoid a byte[]->span->byte[] conversion.
+
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ CheckHashAlgorithm(hashAlgorithm);
+ _implementation = CreateImplementation(key, hashAlgorithm);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="key" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="label" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="context" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public static byte[] DeriveBytes(byte[] key, HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes)
+ {
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+ CheckHashAlgorithm(hashAlgorithm);
+
+ // Don't call to the Span overload so that we don't go from array->span->array for the key in the .NET Standard
+ // build, which prefers to use arrays for the key.
+ return DeriveBytesCore(key, hashAlgorithm, label, context, derivedKeyLengthInBytes);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="key" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="label" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="context" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public static byte[] DeriveBytes(byte[] key, HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes)
+ {
+ if (key is null)
+ throw new ArgumentNullException(nameof(key));
+
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+ CheckHashAlgorithm(hashAlgorithm);
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytesCore(key, hashAlgorithm, label.AsSpan(), context.AsSpan(), result);
+ return result;
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public static byte[] DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytes(key, hashAlgorithm, label, context, result);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a derived key.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="destination">The buffer which will receive the derived key.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ public static void DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ public static byte[] DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<char> context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveBytes(key, hashAlgorithm, label, context, result);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a derived key.
+ /// </summary>
+ /// <param name="key">The key-derivation key.</param>
+ /// <param name="hashAlgorithm">The HMAC algorithm.</param>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="destination">The buffer which will receive the derived key.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// <paramref name="hashAlgorithm" /> has a <see cref="HashAlgorithmName.Name" /> which is empty.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <paramref name="hashAlgorithm"/> is not a known or supported hash algorithm.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <exception cref="PlatformNotSupportedException">
+ /// The current platform does not have a supported implementation of HMAC.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ public static void DeriveBytes(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm, ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+ {
+ CheckHashAlgorithm(hashAlgorithm);
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveBytesCore(key, hashAlgorithm, label, context, destination);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="label" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="context" /> is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes)
+ {
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKeyCore(label, context, result);
+ return result;
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ public byte[] DeriveKey(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKey(label, context, result);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a derived key.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="destination">The buffer which will receive the derived key.</param>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="label" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="context" /> is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+ /// </exception>
+ public void DeriveKey(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+ {
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveKeyCore(label, context, destination);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ public byte[] DeriveKey(ReadOnlySpan<char> label, ReadOnlySpan<char> context, int derivedKeyLengthInBytes)
+ {
+ CheckPrfOutputLength(derivedKeyLengthInBytes, nameof(derivedKeyLengthInBytes));
+
+ byte[] result = new byte[derivedKeyLengthInBytes];
+ DeriveKeyCore(label, context, result);
+ return result;
+ }
+
+ /// <summary>
+ /// Fills a buffer with a derived key.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="destination">The buffer which will receive the derived key.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="destination" /> is larger than the maximum number of bytes that can be derived.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ public void DeriveKey(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+ {
+ CheckPrfOutputLength(destination.Length, nameof(destination));
+ DeriveKeyCore(label, context, destination);
+ }
+
+ /// <summary>
+ /// Derives a key of a specified length.
+ /// </summary>
+ /// <param name="label">The label that identifies the purpose for the derived key.</param>
+ /// <param name="context">The context containing information related to the derived key.</param>
+ /// <param name="derivedKeyLengthInBytes">The length of the derived key, in bytes.</param>
+ /// <returns>An array containing the derived key.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <para>
+ /// <paramref name="label" /> is <see langword="null" />.
+ /// </para>
+ /// <para> -or- </para>
+ /// <para>
+ /// <paramref name="context" /> is <see langword="null" />.
+ /// </para>
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="derivedKeyLengthInBytes" /> is negative or larger than the maximum number of bytes
+ /// that can be derived.
+ /// </exception>
+ /// <exception cref="EncoderFallbackException">
+ /// <paramref name="label" /> or <paramref name="context" /> contains text that cannot be converted to UTF8.
+ /// </exception>
+ /// <remarks>
+ /// <paramref name="label" /> and <paramref name="context" /> will be converted to bytes using the UTF8 encoding.
+ /// for other encodings, perform the conversion using the desired encoding and use an overload which accepts the
+ /// label and context as a sequence of bytes.
+ /// </remarks>
+ public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes)
+ {
+ if (label is null)
+ throw new ArgumentNullException(nameof(label));
+
+ if (context is null)
+ throw new ArgumentNullException(nameof(context));
+
+ return DeriveKey(label.AsSpan(), context.AsSpan(), derivedKeyLengthInBytes);
+ }
+
+ /// <summary>
+ /// Releases all resources used by the current instance of <see cref="SP800108HmacCounterKdf"/>.
+ /// </summary>
+ public void Dispose()
+ {
+ _implementation.Dispose();
+ }
+
+ private static void CheckHashAlgorithm(HashAlgorithmName hashAlgorithm)
+ {
+ string? hashAlgorithmName = hashAlgorithm.Name;
+
+ switch (hashAlgorithmName)
+ {
+ case null:
+ throw new ArgumentNullException(nameof(hashAlgorithm));
+ case "":
+ throw new ArgumentException(SR.Argument_EmptyString, nameof(hashAlgorithm));
+ case HashAlgorithmNames.SHA1:
+ case HashAlgorithmNames.SHA256:
+ case HashAlgorithmNames.SHA384:
+ case HashAlgorithmNames.SHA512:
+ break;
+ default:
+ throw new CryptographicException(SR.Format(SR.Cryptography_UnknownHashAlgorithm, hashAlgorithmName));
+ }
+ }
+
+ private static partial byte[] DeriveBytesCore(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ int derivedKeyLengthInBytes);
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination);
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<char> label,
+ ReadOnlySpan<char> context,
+ Span<byte> destination);
+
+ private void DeriveKeyCore(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+ {
+ _implementation.DeriveBytes(label, context, destination);
+ }
+
+ private void DeriveKeyCore(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+ {
+ _implementation.DeriveBytes(label, context, destination);
+ }
+
+ private static void CheckPrfOutputLength(int length, string paramName)
+ {
+ if (length > MaxPrfOutputSize)
+ {
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_KOut_Too_Large);
+ }
+
+ if (length < 0)
+ {
+ throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_NeedNonNegNum);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Security.Cryptography
+{
+ internal abstract class SP800108HmacCounterKdfImplementationBase : IDisposable
+ {
+ internal abstract void DeriveBytes(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination);
+ internal abstract void DeriveBytes(byte[] label, byte[] context, Span<byte> destination);
+ internal abstract void DeriveBytes(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination);
+
+ public abstract void Dispose();
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+
+using BCryptBuffer = Interop.BCrypt.BCryptBuffer;
+using CngBufferDescriptors = Interop.BCrypt.CngBufferDescriptors;
+using NTSTATUS = Interop.BCrypt.NTSTATUS;
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class SP800108HmacCounterKdfImplementationCng : SP800108HmacCounterKdfImplementationBase
+ {
+ private const string BCRYPT_SP800108_CTR_HMAC_ALGORITHM = "SP800_108_CTR_HMAC";
+ private const nuint BCRYPT_SP800108_CTR_HMAC_ALG_HANDLE = 0x00000341;
+ private const int CharToBytesStackBufferSize = 256;
+
+ // A cached algorithm handle. On Windows 10 this is null if we are using a psuedo handle.
+ private static readonly SafeBCryptAlgorithmHandle? s_sp800108CtrHmacAlgorithmHandle = OpenAlgorithmHandle();
+
+ private readonly SafeBCryptKeyHandle _keyHandle;
+ private readonly HashAlgorithmName _hashAlgorithm;
+
+ public override void Dispose()
+ {
+ _keyHandle.Dispose();
+ }
+
+ internal override void DeriveBytes(byte[] label, byte[] context, Span<byte> destination)
+ {
+ DeriveBytes(new ReadOnlySpan<byte>(label), new ReadOnlySpan<byte>(context), destination);
+ }
+
+ internal override unsafe void DeriveBytes(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+ {
+ if (destination.Length == 0)
+ {
+ return;
+ }
+
+ Debug.Assert(destination.Length <= 0x1FFFFFFF);
+ Debug.Assert(_hashAlgorithm.Name is not null);
+
+ fixed (byte* pLabel = label)
+ fixed (byte* pContext = context)
+ fixed (byte* pDestination = destination)
+ fixed (char* pHashAlgorithm = _hashAlgorithm.Name)
+ {
+ const int BCryptBufferLength = 3;
+ BCryptBuffer* buffers = stackalloc BCryptBuffer[BCryptBufferLength];
+
+ buffers[0].BufferType = CngBufferDescriptors.KDF_LABEL;
+ buffers[0].pvBuffer = (IntPtr)pLabel;
+ buffers[0].cbBuffer = label.Length;
+ buffers[1].BufferType = CngBufferDescriptors.KDF_CONTEXT;
+ buffers[1].pvBuffer = (IntPtr)pContext;
+ buffers[1].cbBuffer = context.Length;
+ buffers[2].BufferType = CngBufferDescriptors.KDF_HASH_ALGORITHM;
+ buffers[2].pvBuffer = (IntPtr)pHashAlgorithm;
+ buffers[2].cbBuffer = (_hashAlgorithm.Name.Length + 1) * 2; // +1 for the null terminator.
+
+ Interop.BCrypt.BCryptBufferDesc bufferDesc;
+ bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
+ bufferDesc.cBuffers = BCryptBufferLength;
+ bufferDesc.pBuffers = (IntPtr)buffers;
+
+ NTSTATUS deriveStatus = Interop.BCrypt.BCryptKeyDerivation(
+ _keyHandle,
+ &bufferDesc,
+ pDestination,
+ destination.Length,
+ out uint resultLength,
+ dwFlags: 0);
+
+ if (deriveStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(deriveStatus);
+ }
+
+ if (destination.Length != resultLength)
+ {
+ Debug.Fail("BCryptKeyDerivation resultLength != destination.Length");
+ throw new CryptographicException();
+ }
+ }
+ }
+
+ internal override void DeriveBytes(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+ {
+ using (Utf8DataEncoding labelData = new Utf8DataEncoding(label, stackalloc byte[CharToBytesStackBufferSize]))
+ using (Utf8DataEncoding contextData = new Utf8DataEncoding(context, stackalloc byte[CharToBytesStackBufferSize]))
+ {
+ DeriveBytes(labelData.Utf8Bytes, contextData.Utf8Bytes, destination);
+ }
+ }
+
+ internal static void DeriveBytesOneShot(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination)
+ {
+ Debug.Assert(destination.Length <= 0x1FFFFFFF);
+
+ using (SP800108HmacCounterKdfImplementationCng kdf = new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm))
+ {
+ kdf.DeriveBytes(label, context, destination);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private static unsafe SafeBCryptKeyHandle CreateSymmetricKey(byte* symmetricKey, int symmetricKeyLength)
+ {
+ NTSTATUS generateKeyStatus;
+ SafeBCryptKeyHandle keyHandle;
+
+ if (s_sp800108CtrHmacAlgorithmHandle is not null)
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ s_sp800108CtrHmacAlgorithmHandle,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ symmetricKey,
+ symmetricKeyLength,
+ dwFlags: 0);
+ }
+ else
+ {
+ generateKeyStatus = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ BCRYPT_SP800108_CTR_HMAC_ALG_HANDLE,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ symmetricKey,
+ symmetricKeyLength,
+ dwFlags: 0);
+ }
+
+ if (generateKeyStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ keyHandle.Dispose();
+ throw Interop.BCrypt.CreateCryptographicException(generateKeyStatus);
+ }
+
+ Debug.Assert(!keyHandle.IsInvalid);
+
+ return keyHandle;
+ }
+
+ // Returns null if the platform is Windows 10+ and psuedo handles should be used.
+ private static SafeBCryptAlgorithmHandle? OpenAlgorithmHandle()
+ {
+ if (!Interop.BCrypt.PseudoHandlesSupported)
+ {
+ NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
+ out SafeBCryptAlgorithmHandle sp800108CtrHmacAlgorithmHandle,
+ BCRYPT_SP800108_CTR_HMAC_ALGORITHM,
+ null,
+ Interop.BCrypt.BCryptOpenAlgorithmProviderFlags.None);
+
+ if (openStatus != NTSTATUS.STATUS_SUCCESS)
+ {
+ sp800108CtrHmacAlgorithmHandle.Dispose();
+ throw Interop.BCrypt.CreateCryptographicException(openStatus);
+ }
+
+ return sp800108CtrHmacAlgorithmHandle;
+ }
+
+ return null;
+ }
+
+ private static int GetHashBlockSize(string hashAlgorithmName)
+ {
+ // Block sizes per NIST FIPS pub 180-4.
+ switch (hashAlgorithmName)
+ {
+ case HashAlgorithmNames.SHA1:
+ case HashAlgorithmNames.SHA256:
+ return 512 / 8;
+ case HashAlgorithmNames.SHA384:
+ case HashAlgorithmNames.SHA512:
+ return 1024 / 8;
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithmName}'");
+ throw new CryptographicException();
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+using System.Threading;
+using System.Runtime.Versioning;
+
+#pragma warning disable CA1513
+
+namespace System.Security.Cryptography
+{
+#if !NET7_0_OR_GREATER && NET
+ [UnsupportedOSPlatform("browser")]
+#endif
+ internal sealed partial class SP800108HmacCounterKdfImplementationManaged : SP800108HmacCounterKdfImplementationBase
+ {
+ private byte[] _key;
+ private int _keyReferenceCount = 1;
+ private int _disposed;
+ private readonly HashAlgorithmName _hashAlgorithm;
+
+ internal override void DeriveBytes(ReadOnlySpan<byte> label, ReadOnlySpan<byte> context, Span<byte> destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ internal override void DeriveBytes(ReadOnlySpan<char> label, ReadOnlySpan<char> context, Span<byte> destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ internal override void DeriveBytes(byte[] label, byte[] context, Span<byte> destination)
+ {
+ byte[] key = IncrementAndAcquireKey();
+
+ try
+ {
+ DeriveBytesOneShot(key, _hashAlgorithm, label, context, destination);
+ }
+ finally
+ {
+ ReleaseKey();
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (Interlocked.Exchange(ref _disposed, 1) == 0)
+ {
+ ReleaseKey();
+ }
+ }
+
+ private byte[] IncrementAndAcquireKey()
+ {
+ while (true)
+ {
+ int current = Volatile.Read(ref _keyReferenceCount);
+
+ if (current == 0)
+ {
+ throw new ObjectDisposedException(nameof(SP800108HmacCounterKdfImplementationManaged));
+ }
+
+ Debug.Assert(current > 0);
+ int incrementedCount = checked(current + 1);
+
+ if (Interlocked.CompareExchange(ref _keyReferenceCount, incrementedCount, current) == current)
+ {
+ return _key;
+ }
+ }
+ }
+
+ public void ReleaseKey()
+ {
+ int newReferenceCount = Interlocked.Decrement(ref _keyReferenceCount);
+ Debug.Assert(newReferenceCount >= 0, newReferenceCount.ToString());
+
+ if (newReferenceCount == 0)
+ {
+ ZeroKey();
+ }
+ }
+
+ private void ZeroKey()
+ {
+ CryptographicOperations.ZeroMemory(_key);
+ _key = null!;
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ internal readonly ref struct Utf8DataEncoding
+ {
+ internal static Encoding ThrowingUtf8Encoding { get; } = new UTF8Encoding(false, true);
+
+ private readonly byte[]? _rented;
+ private readonly Span<byte> _buffer;
+
+ internal Utf8DataEncoding(ReadOnlySpan<char> data, Span<byte> stackBuffer)
+ {
+ int maxLength = ThrowingUtf8Encoding.GetMaxByteCount(data.Length);
+ _buffer = (uint)maxLength <= stackBuffer.Length ?
+ stackBuffer :
+ (_rented = CryptoPool.Rent(maxLength));
+
+ int written = ThrowingUtf8Encoding.GetBytes(data, _buffer);
+ _buffer = _buffer.Slice(0, written);
+ }
+
+ internal ReadOnlySpan<byte> Utf8Bytes => _buffer;
+
+ internal void Dispose()
+ {
+ CryptographicOperations.ZeroMemory(_buffer);
+
+ if (_rented is not null)
+ {
+ CryptoPool.Return(_rented, clearSize: 0);
+ }
+ }
+ }
+
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ [Fact]
+ public static void DeriveBytes_Allocating_ArrayBytes_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("key", () =>
+ SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 42));
+
+ Assert.Throws<ArgumentNullException>("label", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (byte[])null, s_contextBytes, 42));
+
+ Assert.Throws<ArgumentNullException>("context", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, context: (byte[])null, 42));
+
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, 42));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, 42));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_String_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("key", () =>
+ SP800108HmacCounterKdf.DeriveBytes(key: (byte[])null, HashAlgorithmName.SHA256, Label, Context, 42));
+
+ Assert.Throws<ArgumentNullException>("label", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, label: (string)null, Context, 42));
+
+ Assert.Throws<ArgumentNullException>("context", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, context: (string)null, 42));
+
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label, Context, 42));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label, Context, 42));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label, Context, 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, Context, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanBytes_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_nullHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_emptyHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), s_unknownHash, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), HashAlgorithmName.SHA256, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk.AsSpan(), HashAlgorithmName.SHA256, s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanBytes_ArgValidation()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, s_labelBytes, s_contextBytes, destination));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, s_labelBytes, s_contextBytes, destination));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, s_labelBytes, s_contextBytes, destination));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, s_labelBytes, s_contextBytes, GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanChars_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), 42));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), 42));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanChars_ArgValidation()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_nullHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_emptyHash, Label.AsSpan(), Context.AsSpan(), destination));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, s_unknownHash, Label.AsSpan(), Context.AsSpan(), destination));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+
+ Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), Context.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void Ctor_KeyArray_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("key", () =>
+ new SP800108HmacCounterKdf((byte[])null, HashAlgorithmName.SHA256));
+
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk, s_nullHash));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk, s_emptyHash));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ new SP800108HmacCounterKdf(s_kdk, s_unknownHash));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+ }
+
+ [Fact]
+ public static void Ctor_KeySpan_ArgValidation()
+ {
+ Assert.Throws<ArgumentNullException>("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_nullHash));
+
+ Assert.Throws<ArgumentException>("hashAlgorithm", () =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_emptyHash));
+
+ CryptographicException ex = Assert.Throws<CryptographicException>(() =>
+ new SP800108HmacCounterKdf(s_kdk.AsSpan(), s_unknownHash));
+ Assert.Contains(s_unknownHash.Name, ex.Message);
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_ArrayBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentNullException>("label", () =>
+ kdf.DeriveKey((byte[])null, s_contextBytes, 42));
+
+ Assert.Throws<ArgumentNullException>("context", () =>
+ kdf.DeriveKey(s_labelBytes, (byte[])null, 42));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes, s_contextBytes, -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes, s_contextBytes, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanBytes_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+ kdf.DeriveKey(s_labelBytes.AsSpan(), s_contextBytes.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanChars_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanChars_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentOutOfRangeException>("destination", () =>
+ kdf.DeriveKey(Label.AsSpan(), Context.AsSpan(), GetOversizedSpan()));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_String_ArgValidation()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label, Context, -1));
+
+ Assert.Throws<ArgumentOutOfRangeException>("derivedKeyLengthInBytes", () =>
+ kdf.DeriveKey(Label, Context, 0x20000000));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_String_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey("\uD800", Context, 42));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey(Label, "\uD800", 42));
+ }
+
+ [Fact]
+ public static void DeriveKey_BufferFill_SpanChars_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+ byte[] derivedKey = new byte[42];
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), derivedKey));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey(Label.AsSpan(), "\uD800".AsSpan(), derivedKey));
+ }
+
+ [Fact]
+ public static void DeriveKey_Allocating_SpanChars_InvalidUTF8()
+ {
+ using SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey("\uD800".AsSpan(), Context.AsSpan(), 42));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ kdf.DeriveKey(Label.AsSpan(), "\uD800".AsSpan(), 42));
+ }
+
+ [Fact]
+ public static void DeriveBytes_BufferFill_SpanChars_InvalidUTF8()
+ {
+ byte[] destination = new byte[42];
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), destination));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), destination));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_SpanChars_InvalidUTF8()
+ {
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label.AsSpan(), "\uD800".AsSpan(), 42));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800".AsSpan(), Context.AsSpan(), 42));
+ }
+
+ [Fact]
+ public static void DeriveBytes_Allocating_String_InvalidUTF8()
+ {
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, Label, "\uD800", 42));
+
+ Assert.Throws<EncoderFallbackException>(() =>
+ SP800108HmacCounterKdf.DeriveBytes(s_kdk, HashAlgorithmName.SHA256, "\uD800", Context, 42));
+ }
+
+ private unsafe static Span<byte> GetOversizedSpan()
+ {
+ // This creates an very large span over some address space. The memory in this span should never be read
+ // or written to; it should only be used for length checking of the span for argument validation.
+ return new Span<byte>((void*)0, 0x20000000);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Globalization;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ [Theory]
+ [InlineData("V47WmHzPSkdC2vkLAomIjCzZlDOAetll3yJLcSvon7LJFjJpEN+KnSNp+gIpeydKMsENkflbrIZ/3s6GkEaH")]
+ [InlineData("mVaFM4deXLl610CmnCteNzxgbM/VkmKznAlPauHcDBn0le06uOjAKLHx0LfoU2/Ttq9nd78Y6Nk6wArmdwJgJg==")]
+ [InlineData("GaHPeqdUxriFpjRtkYQYWr5/iqneD/+hPhVJQt4rXblxSpB1UUqGqL00DMU/FJkX0iMCfqUjQXtXyfks+p++Ev4=")]
+ public static void AspNetCoreTestVectors_Basic(string expectedBase64)
+ {
+ // These tests are from the dotnet/aspnetcore repo.
+ byte[] expected = Convert.FromBase64String(expectedBase64);
+ VerifyKbkdf(expected, s_kdk, HashAlgorithmName.SHA512, Label.ToCharArray(), Context.ToCharArray());
+ }
+
+ [Theory]
+ [InlineData("rt2hM6kkQ8hAXmkHx0TU4o3Q+S7fie6b3S1LAq107k++P9v8uSYA2G+WX3pJf9ZkpYrTKD7WUIoLkgA1R9lk")]
+ [InlineData("RKiXmHSrWq5gkiRSyNZWNJrMR0jDyYHJMt9odOayRAE5wLSX2caINpQmfzTH7voJQi3tbn5MmD//dcspghfBiw==")]
+ [InlineData("KedXO0zAIZ3AfnPqY1NnXxpC3HDHIxefG4bwD3g6nWYEc5+q7pjbam71Yqj0zgHMNC9Z7BX3wS1/tajFocRWZUk=")]
+ public static void AspNetCoreTestVectors_LargeKdk(string expectedBase64)
+ {
+ // These tests are from the dotnet/aspnetcore repo.
+ // Win32 BCryptKeyDerivation doesn't perform RFC 2104, section 2 key adjustment for the KDK.
+ // We do this for CNG so that there is no functional limit on the KDK size.
+ byte[] kdk = new byte[50000];
+
+ for (int i = 0; i < kdk.Length; i++)
+ {
+ kdk[i] = (byte)i;
+ }
+
+ byte[] expected = Convert.FromBase64String(expectedBase64);
+ VerifyKbkdf(expected, kdk, HashAlgorithmName.SHA512, Label.ToCharArray(), Context.ToCharArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetRfc8009TestVectors))]
+ public static void Rfc8009Tests(byte[] kdk, byte[] expected, HashAlgorithmName hashAlgorithm)
+ {
+ VerifyKbkdf(expected, kdk, hashAlgorithm, "prf".ToCharArray(), "test".ToCharArray());
+ }
+
+ [Theory]
+ [InlineData(new byte[] { 0xcf, 0x4b, 0xfe, 0x4f, 0x85, 0xa1, 0x0b, 0xad }, nameof(HashAlgorithmName.SHA1))]
+ [InlineData(new byte[] { 0x00, 0x26, 0x4b, 0xbb, 0x14, 0x97, 0x40, 0x54 }, nameof(HashAlgorithmName.SHA256))]
+ [InlineData(new byte[] { 0xc7, 0x10, 0x27, 0x87, 0xd8, 0x96, 0xbc, 0x89 }, nameof(HashAlgorithmName.SHA384))]
+ [InlineData(new byte[] { 0xdb, 0x3a, 0x18, 0xd9, 0x6c, 0x4a, 0xd4, 0x1e }, nameof(HashAlgorithmName.SHA512))]
+ public static void SymCryptTestVectors(byte[] expected, string hashAlgorithm)
+ {
+ // These test vectors come from https://github.com/microsoft/SymCrypt.
+ // See sp800_108_hmacsha1.c, sp800_108_hmacsha256.c, and sp800_108_hmacsha512.c.
+ byte[] symCryptKey = new byte[]
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ };
+
+ byte[] key = symCryptKey.AsSpan(0, 8).ToArray();
+ byte[] label = "Label"u8.ToArray();
+ byte[] context = symCryptKey.AsSpan(16, 16).ToArray();
+
+ HashAlgorithmName hashAlgorithmName = new HashAlgorithmName(hashAlgorithm);
+ VerifyKbkdfBytes(expected, key, hashAlgorithmName, label, context);
+ }
+
+ [Theory]
+ [InlineData(new byte[] { }, "label!", "context?", new byte[] { 0xb6, 0xff, 0x26, 0x61, 0xea, 0x43, 0x76, 0xd2 })]
+ [InlineData(new byte[] { 0xFE }, "", "context?", new byte[] { 0xed, 0xdf, 0x50, 0x06, 0x3c, 0x26, 0x3e, 0xd9 })]
+ [InlineData(new byte[] { 0xFE }, "label!", "", new byte[] { 0x98, 0x83, 0x67, 0x41, 0x3f, 0x2d, 0x90, 0x72 })]
+ [InlineData(new byte[] { }, "", "", new byte[] { 0x18, 0x0f, 0xf7, 0xa2, 0xbc, 0x8d, 0x6e, 0x98 })]
+ [InlineData(new byte[] { }, "", "", new byte[] { })]
+ public static void EmptyTests(byte[] key, string label, string context, byte[] expected)
+ {
+ VerifyKbkdf(expected, key, HashAlgorithmName.SHA256, label.ToCharArray(), context.ToCharArray());
+ }
+
+ [Theory]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8 - 1, new byte[] { 0xc9, 0x0f, 0x9d, 0x91, 0x85, 0xe5, 0xeb, 0x9b })]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8, new byte[] { 0x7b, 0xdb, 0x38, 0x28, 0xc0, 0x9f, 0x49, 0x05 })]
+ [InlineData(nameof(HashAlgorithmName.SHA1), 512 / 8 + 1, new byte[] { 0x6c, 0x3a, 0xba, 0x28, 0x38, 0xad, 0x51, 0x2c })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8 - 1, new byte[] { 0x88, 0xaa, 0xc7, 0xee, 0x05, 0x65, 0xfd, 0xda })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8, new byte[] { 0x3d, 0xdc, 0x7d, 0xec, 0x0a, 0xfd, 0x7a, 0xc0 })]
+ [InlineData(nameof(HashAlgorithmName.SHA256), 512 / 8 + 1, new byte[] { 0x47, 0x95, 0x00, 0xd5, 0x55, 0x1f, 0xb3, 0x85 })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8 - 1, new byte[] { 0x84, 0xd8, 0xfd, 0x33, 0x4f, 0x07, 0x81, 0x9b })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8, new byte[] { 0x6c, 0xa2, 0x5d, 0x4f, 0x61, 0x2d, 0x0f, 0x20 })]
+ [InlineData(nameof(HashAlgorithmName.SHA384), 1024 / 8 + 1, new byte[] { 0xe4, 0x0e, 0xbd, 0x41, 0x14, 0xe6, 0x80, 0x59 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8 - 1, new byte[] { 0xa4, 0xe5, 0x24, 0xe8, 0x56, 0x2b, 0x48, 0xa4 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8, new byte[] { 0xba, 0xf6, 0xed, 0xa7, 0x3a, 0xf7, 0x12, 0x27 })]
+ [InlineData(nameof(HashAlgorithmName.SHA512), 1024 / 8 + 1, new byte[] { 0x34, 0xdf, 0x2d, 0x21, 0xfd, 0xf1, 0x0e, 0x13 })]
+ public static void Kdk_HmacBlockBoundarySizes(string hashAlgorithmName, int kdkSize, byte[] expected)
+ {
+ // We do HMAC key adjust for the CNG implementation when the kdk exceeds the block size of the HMAC algorithm.
+ // This tests one byte below, at, and above the block size for each HMAC algorithm.
+ // Verified against OpenSSL 3. Example command used below. Adjust the digest and the seq upper boundary as needed.
+ // Note that OpenSSL calls the label "salt" and the context "info".
+ //
+ // openssl kdf -keylen 8 -kdfopt mac:HMAC -kdfopt digest:SHA1 \
+ // -kdfopt hexkey:$(seq 1 63 | awk '{ printf "%02x", $1 }') -kdfopt salt:icecream \
+ // -kdfopt info:sandwiches -binary KBKDF | xxd -i
+
+ byte[] kdk = new byte[kdkSize];
+
+ for (int i = 0; i < kdkSize; i++)
+ {
+ kdk[i] = (byte)checked(i + 1);
+ }
+
+ HashAlgorithmName alg = new HashAlgorithmName(hashAlgorithmName);
+ VerifyKbkdf(expected, kdk, alg, "icecream".ToCharArray(), "sandwiches".ToCharArray());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetOutputLengthBoundaries))]
+ public static void OutputLength_AroundPrfOutputBoundaries(string hashAlgorithmName, byte[] expected)
+ {
+ byte[] kdk = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ HashAlgorithmName alg = new HashAlgorithmName(hashAlgorithmName);
+ VerifyKbkdf(expected, kdk, alg, "mustard".ToCharArray(), "ketchup".ToCharArray());
+ }
+
+ [Fact]
+ public static void MultipleDisposes_NoThrow()
+ {
+ SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(s_kdk, HashAlgorithmName.SHA256);
+ kdf.Dispose();
+
+ Assert.Throws<ObjectDisposedException>(() => kdf.DeriveKey(s_labelBytes, s_contextBytes, 42));
+ kdf.Dispose();
+ }
+
+ public static IEnumerable<object[]> GetOutputLengthBoundaries()
+ {
+ // All outputs assume a kdk of 1, 2, 3, 4, 5, 6, 7, 8 with a label of "mustard" and context of "ketchup".
+ // Outputs were generated from the "openssl kdf" command.
+
+ // HMACSHA1 output size is 20 bytes
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA1),
+ new byte[39]
+ {
+ 0x05, 0xed, 0x32, 0x78, 0xfe, 0x72, 0x9e, 0x7a, 0x1b, 0x49, 0x2c, 0x1b, 0x40, 0x3b, 0x31, 0xd8,
+ 0xef, 0x74, 0xfc, 0xe6, 0x1f, 0x75, 0x4b, 0xa5, 0x47, 0x8e, 0xb9, 0x1d, 0x9e, 0x0c, 0x4f, 0x03,
+ 0xf7, 0x92, 0x68, 0x8a, 0x94, 0x0c, 0xea,
+ },
+ };
+
+ yield return new object[]
+ {
+ nameof (HashAlgorithmName.SHA1),
+ new byte[40]
+ {
+ 0xa5, 0x4a, 0x74, 0xe5, 0x01, 0x0c, 0x69, 0x0e, 0xc8, 0x9e, 0x24, 0x95, 0x94, 0x6a, 0x57, 0x06,
+ 0xe8, 0x88, 0x63, 0x3b, 0xae, 0x1d, 0x13, 0xd8, 0x9d, 0x80, 0x4c, 0xcd, 0x60, 0x2b, 0x15, 0x3a,
+ 0xb9, 0xd3, 0x77, 0xc7, 0xc4, 0xb7, 0x2a, 0xe6,
+ }
+ };
+
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA1),
+ new byte[41]
+ {
+ 0x44, 0xdc, 0x8a, 0x94, 0x18, 0xd1, 0x3a, 0x35, 0x39, 0xb8, 0xfb, 0x33, 0x0d, 0xf8, 0x1d, 0x3d,
+ 0x95, 0xa3, 0x3a, 0xfb, 0x0c, 0x7f, 0x54, 0x20, 0x32, 0x75, 0x3c, 0x9c, 0xf0, 0xe2, 0xce, 0x4c,
+ 0xe0, 0x98, 0xbe, 0x6b, 0x61, 0x19, 0xd0, 0x79, 0xea,
+ }
+ };
+
+ // HMACSHA256 output size is 32 bytes
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA256),
+ new byte[63]
+ {
+ 0x86, 0x02, 0xc7, 0x0a, 0x80, 0xf3, 0xc9, 0x46, 0xb4, 0x10, 0xa8, 0xde, 0x36, 0x03, 0x5a, 0xb2,
+ 0x29, 0x48, 0x16, 0xc6, 0x97, 0x99, 0xa4, 0x48, 0x85, 0x55, 0x34, 0xbc, 0xa6, 0x29, 0x68, 0x88,
+ 0x8a, 0xd3, 0x42, 0x42, 0x22, 0x55, 0xa1, 0xf9, 0xaf, 0x1b, 0xb4, 0xfb, 0x69, 0xd3, 0x9d, 0x2e,
+ 0x89, 0x4f, 0x0e, 0x19, 0x5a, 0x98, 0xce, 0x5c, 0x7e, 0xfd, 0xba, 0xc7, 0x51, 0x18, 0xdf,
+ },
+ };
+
+ yield return new object[]
+ {
+ nameof (HashAlgorithmName.SHA256),
+ new byte[64]
+ {
+ 0xcc, 0x06, 0x77, 0xaf, 0x45, 0xae, 0x66, 0x3f, 0x58, 0x8f, 0xd4, 0xa5, 0x6f, 0x31, 0xef, 0xd7,
+ 0x29, 0x9e, 0x45, 0xb1, 0xac, 0x5b, 0x27, 0xb4, 0x10, 0xc1, 0xaf, 0x63, 0xe9, 0xbb, 0xb2, 0xe6,
+ 0x76, 0x65, 0xbf, 0x2d, 0xd3, 0x14, 0xfb, 0xdf, 0xab, 0x6b, 0x95, 0xf4, 0x67, 0x53, 0xe0, 0xd7,
+ 0x46, 0xd3, 0x4c, 0x72, 0xf3, 0x08, 0x95, 0x37, 0xbf, 0xa4, 0x67, 0xb0, 0xe0, 0x00, 0x46, 0x79,
+ }
+ };
+
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA256),
+ new byte[65]
+ {
+ 0x2b, 0x57, 0x00, 0xe5, 0xef, 0xda, 0x41, 0x7b, 0x75, 0x04, 0xe8, 0x37, 0x7a, 0x7f, 0xfd, 0x30,
+ 0xd6, 0x56, 0x07, 0x69, 0x00, 0x75, 0x7b, 0xb9, 0x64, 0x15, 0x51, 0xac, 0x88, 0x55, 0x87, 0x24,
+ 0xcc, 0xb9, 0x8b, 0xb2, 0x55, 0xcc, 0x02, 0xda, 0xf1, 0x4e, 0xc9, 0xa2, 0x40, 0x95, 0xfb, 0xff,
+ 0xa0, 0x57, 0x73, 0x51, 0x66, 0x4d, 0x65, 0xd4, 0xc9, 0xc0, 0xe6, 0xf4, 0x40, 0xf4, 0x30, 0x17,
+ 0x7b,
+ }
+ };
+
+ // HMACSHA384 output size is 48 bytes
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA384),
+ new byte[95]
+ {
+ 0x82, 0x1d, 0x9b, 0x3c, 0x7f, 0xad, 0xd4, 0x1b, 0x91, 0xdc, 0x6e, 0x4f, 0xf5, 0xd8, 0xf7, 0xc8,
+ 0x33, 0x18, 0xc8, 0xf8, 0x23, 0x3f, 0x5d, 0xf4, 0x95, 0x32, 0x81, 0x72, 0x96, 0xbd, 0xb9, 0xcc,
+ 0xc1, 0x91, 0x0c, 0x5b, 0x5c, 0x86, 0x2c, 0x0d, 0x5b, 0xe4, 0xfb, 0xc6, 0x70, 0xc4, 0x20, 0xd6,
+ 0x9c, 0xfd, 0x67, 0x56, 0x86, 0x16, 0xd6, 0xf8, 0x05, 0x86, 0x5c, 0xa0, 0x64, 0x5f, 0x72, 0xe0,
+ 0xa5, 0x52, 0x5d, 0x72, 0xe8, 0x5e, 0x07, 0xf1, 0xf5, 0xcf, 0xf9, 0x63, 0x85, 0xc2, 0x77, 0x87,
+ 0x89, 0x75, 0x9d, 0xd2, 0xc6, 0x2b, 0xf3, 0x23, 0x73, 0xd9, 0x1d, 0x01, 0x17, 0x9c, 0x01,
+ },
+ };
+
+ yield return new object[]
+ {
+ nameof (HashAlgorithmName.SHA384),
+ new byte[96]
+ {
+ 0xe4, 0x30, 0x8b, 0x7e, 0x5b, 0x64, 0xcd, 0xd7, 0x3d, 0x27, 0xd9, 0x3a, 0x9e, 0xee, 0xcc, 0xc6,
+ 0x79, 0xa7, 0x39, 0xca, 0x91, 0xb8, 0x93, 0xcd, 0xe8, 0xb8, 0xb7, 0x8a, 0x48, 0xad, 0xb4, 0x3d,
+ 0x3a, 0x02, 0xb9, 0xba, 0x81, 0x81, 0x01, 0x5f, 0xef, 0x8a, 0xc1, 0xcd, 0x6b, 0xae, 0x99, 0xb9,
+ 0xfd, 0xaf, 0x28, 0x18, 0xcf, 0x48, 0xa1, 0xfa, 0x57, 0xce, 0x0a, 0x79, 0x1f, 0xbf, 0xc8, 0x7f,
+ 0xd8, 0x34, 0x34, 0x16, 0x27, 0xc1, 0x12, 0x6e, 0x4c, 0x8c, 0x62, 0xc6, 0x11, 0x01, 0xb8, 0xb8,
+ 0xa5, 0x06, 0xc8, 0x4a, 0x2f, 0xf2, 0x91, 0x08, 0x1a, 0x02, 0x5e, 0x72, 0x48, 0xd1, 0x11, 0x6a,
+
+ }
+ };
+
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA384),
+ new byte[97]
+ {
+ 0xa1, 0x01, 0x9f, 0x83, 0x55, 0x8f, 0x4a, 0x67, 0x2f, 0xbf, 0xf7, 0x7b, 0xbb, 0xfd, 0x22, 0x35,
+ 0x35, 0x97, 0x01, 0x53, 0x5d, 0x9f, 0x7e, 0xa4, 0xbf, 0xb9, 0xfd, 0xdf, 0x7d, 0x86, 0x8f, 0x86,
+ 0x98, 0x9d, 0x87, 0x21, 0x41, 0x75, 0x4e, 0x2c, 0xaf, 0x10, 0x25, 0x40, 0xe8, 0x26, 0xb8, 0x4d,
+ 0x37, 0xe2, 0x43, 0x5f, 0x2b, 0x30, 0x03, 0xde, 0xb3, 0x1e, 0x31, 0x8c, 0x59, 0x2d, 0x26, 0xd5,
+ 0x08, 0xe4, 0x55, 0x46, 0x0d, 0x76, 0xe7, 0x58, 0xe5, 0xa9, 0xaf, 0xe8, 0xe7, 0x7c, 0xa3, 0x05,
+ 0x61, 0x7b, 0xcb, 0x98, 0x6e, 0xaa, 0xea, 0xd0, 0x7e, 0x52, 0x20, 0xf8, 0xfe, 0x49, 0x02, 0x08,
+ 0xee,
+ }
+ };
+
+ // HMACSHA512 output size is 64 bytes
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA512),
+ new byte[127]
+ {
+ 0x3d, 0x72, 0x04, 0x07, 0x9c, 0x28, 0x34, 0x33, 0x7a, 0x2f, 0x8e, 0x22, 0x00, 0x1f, 0x1f, 0x22,
+ 0xd9, 0x24, 0x38, 0xd1, 0x56, 0xa4, 0x03, 0x46, 0x89, 0x09, 0xde, 0x13, 0xb3, 0x9c, 0xa9, 0x24,
+ 0x0d, 0x3e, 0x4c, 0x97, 0x7f, 0xd5, 0x9f, 0x86, 0x45, 0x36, 0xcd, 0xc8, 0x0f, 0x44, 0x9a, 0xad,
+ 0xe3, 0xba, 0xfc, 0x93, 0x08, 0x21, 0xa8, 0xfd, 0xde, 0xef, 0x4f, 0xe3, 0xaa, 0xb3, 0xcf, 0xc1,
+ 0x81, 0x1b, 0x44, 0xf9, 0xae, 0xf6, 0x73, 0xc7, 0xf0, 0x71, 0xd6, 0x14, 0x8e, 0x18, 0x5d, 0x43,
+ 0xa1, 0xfb, 0x09, 0x11, 0x24, 0x84, 0x56, 0x9c, 0x97, 0x6e, 0x2e, 0x5a, 0xcd, 0xd3, 0xa5, 0xfb,
+ 0x81, 0x33, 0x6a, 0x3d, 0x95, 0xa5, 0xd9, 0xcd, 0x04, 0x36, 0x76, 0xc2, 0x4c, 0xed, 0x65, 0x81,
+ 0x6f, 0x8c, 0xec, 0xfd, 0xde, 0xdd, 0x3c, 0xd9, 0x1a, 0xe1, 0xf1, 0x02, 0x7e, 0xb8, 0x3a,
+ },
+ };
+
+ yield return new object[]
+ {
+ nameof (HashAlgorithmName.SHA512),
+ new byte[128]
+ {
+ 0x0e, 0x6d, 0x21, 0x43, 0xca, 0xa7, 0x88, 0x13, 0x70, 0x0e, 0xc5, 0x7b, 0x5e, 0x5a, 0x41, 0x21,
+ 0x03, 0x07, 0x30, 0x35, 0x10, 0xe9, 0x42, 0x12, 0x80, 0x64, 0x10, 0x71, 0x5d, 0x41, 0xb9, 0xf5,
+ 0x3a, 0xb2, 0xcd, 0xf8, 0x71, 0x52, 0x01, 0xf8, 0xc5, 0x27, 0x65, 0xc0, 0x6b, 0x31, 0x35, 0xfc,
+ 0x0d, 0x38, 0xbb, 0xf4, 0xc2, 0xeb, 0x9a, 0x85, 0x3f, 0x16, 0xf0, 0x25, 0x40, 0x33, 0x57, 0xc1,
+ 0x08, 0x25, 0xcf, 0x31, 0x10, 0xbf, 0x78, 0x3a, 0x37, 0x64, 0x13, 0xa7, 0x2f, 0xd8, 0x32, 0x2b,
+ 0x93, 0x1f, 0x0b, 0x6d, 0x6c, 0x6c, 0x45, 0x9f, 0x6a, 0xdb, 0x97, 0x8b, 0x33, 0x7d, 0x31, 0xa8,
+ 0xd9, 0x92, 0xe4, 0x50, 0x38, 0x25, 0x22, 0x09, 0x98, 0x11, 0xce, 0x55, 0xf8, 0x6d, 0x27, 0x36,
+ 0x5f, 0xab, 0xca, 0x4b, 0x54, 0x78, 0x11, 0xf9, 0xaf, 0x57, 0xfa, 0x02, 0x31, 0x27, 0x52, 0x69,
+ }
+ };
+
+ yield return new object[]
+ {
+ nameof(HashAlgorithmName.SHA512),
+ new byte[129]
+ {
+ 0x9e, 0xd0, 0xad, 0x78, 0x5d, 0xb3, 0x50, 0xce, 0x0a, 0x1b, 0xa2, 0xd1, 0x1d, 0xb8, 0x43, 0xc0,
+ 0xc1, 0x0f, 0x2f, 0x13, 0xbe, 0x6b, 0x56, 0x53, 0x4e, 0x9f, 0x18, 0x76, 0xcf, 0xf2, 0xf0, 0xa1,
+ 0xa6, 0xe8, 0x57, 0x2b, 0x05, 0xf2, 0x2e, 0x3e, 0xbf, 0xfe, 0x5c, 0x20, 0x93, 0x5e, 0x73, 0xca,
+ 0x23, 0xda, 0x63, 0x24, 0xdf, 0x6c, 0xb7, 0x5c, 0xe7, 0xe9, 0x6e, 0x2a, 0x0c, 0x3a, 0xa9, 0xb7,
+ 0x65, 0x25, 0x8a, 0x8c, 0xf0, 0xae, 0x0e, 0x53, 0x84, 0x82, 0x8b, 0xa6, 0xd9, 0xed, 0x3d, 0xfe,
+ 0x6f, 0x1d, 0xbf, 0x36, 0x7c, 0xfd, 0xa2, 0xf9, 0x22, 0x79, 0x79, 0x54, 0x5f, 0xed, 0x3b, 0xb9,
+ 0xab, 0x84, 0x75, 0xba, 0xb0, 0x12, 0x22, 0x6a, 0x07, 0xee, 0x35, 0xc4, 0x9a, 0xca, 0xd8, 0x28,
+ 0x3c, 0x0b, 0xcd, 0x85, 0xbb, 0x6e, 0x7b, 0x0e, 0xaa, 0xcb, 0xf9, 0xb1, 0xa4, 0xcd, 0x65, 0x87,
+ 0x61,
+ }
+ };
+ }
+
+ public static IEnumerable<object[]> GetRfc8009TestVectors()
+ {
+ // See RFC 8009 Appendix A "Sample pseudorandom function (PRF) invocations"
+ // section. These vectors are also used by OpenSSL 3 for KBKDF.
+ // Section 5 of RFC 8009 defines KDF-HMAC-SHA2 as always using "prf" as the label.
+ // The appendix defines the context as "test".
+ // (kdk, expected, hmac algorithm)
+ yield return new object[]
+ {
+ new byte[]
+ {
+ 0x37, 0x05, 0xD9, 0x60, 0x80, 0xC1, 0x77, 0x28,
+ 0xA0, 0xE8, 0x00, 0xEA, 0xB6, 0xE0, 0xD2, 0x3C,
+ },
+ new byte[]
+ {
+ 0x9D, 0x18, 0x86, 0x16, 0xF6, 0x38, 0x52, 0xFE,
+ 0x86, 0x91, 0x5B, 0xB8, 0x40, 0xB4, 0xA8, 0x86,
+ 0xFF, 0x3E, 0x6B, 0xB0, 0xF8, 0x19, 0xB4, 0x9B,
+ 0x89, 0x33, 0x93, 0xD3, 0x93, 0x85, 0x42, 0x95,
+ },
+ HashAlgorithmName.SHA256,
+ };
+
+ yield return new object[]
+ {
+ new byte[]
+ {
+ 0x6D, 0x40, 0x4D, 0x37, 0xFA, 0xF7, 0x9F, 0x9D,
+ 0xF0, 0xD3, 0x35, 0x68, 0xD3, 0x20, 0x66, 0x98,
+ 0x00, 0xEB, 0x48, 0x36, 0x47, 0x2E, 0xA8, 0xA0,
+ 0x26, 0xD1, 0x6B, 0x71, 0x82, 0x46, 0x0C, 0x52,
+ },
+ new byte[]
+ {
+ 0x98, 0x01, 0xF6, 0x9A, 0x36, 0x8C, 0x2B, 0xF6,
+ 0x75, 0xE5, 0x95, 0x21, 0xE1, 0x77, 0xD9, 0xA0,
+ 0x7F, 0x67, 0xEF, 0xE1, 0xCF, 0xDE, 0x8D, 0x3C,
+ 0x8D, 0x6F, 0x6A, 0x02, 0x56, 0xE3, 0xB1, 0x7D,
+ 0xB3, 0xC1, 0xB6, 0x2A, 0xD1, 0xB8, 0x55, 0x33,
+ 0x60, 0xD1, 0x73, 0x67, 0xEB, 0x15, 0x14, 0xD2,
+ },
+ HashAlgorithmName.SHA384,
+ };
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ private const string Label = "label";
+ private const string Context = "contextHeadercontext";
+ private static readonly byte[] s_labelBytes = "label"u8.ToArray();
+ private static readonly byte[] s_kdk = "kdk"u8.ToArray();
+ private static readonly byte[] s_contextBytes = "contextHeadercontext"u8.ToArray();
+ private static readonly HashAlgorithmName s_unknownHash = HashAlgorithmName.MD5;
+ private static readonly HashAlgorithmName s_nullHash = new HashAlgorithmName(null);
+ private static readonly HashAlgorithmName s_emptyHash = new HashAlgorithmName("");
+
+ private static void VerifyKbkdfBytes(byte[] expected, byte[] key, HashAlgorithmName hashAlgorithm, byte[] labelBytes, byte[] contextBytes)
+ {
+ byte[] result;
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = kdf.DeriveKey(labelBytes, contextBytes, expected.Length);
+ Assert.Equal(expected, result);
+ }
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = kdf.DeriveKey(new ReadOnlySpan<byte>(labelBytes), new ReadOnlySpan<byte>(contextBytes), expected.Length);
+ Assert.Equal(expected, result);
+ }
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = new byte[expected.Length];
+ kdf.DeriveKey(new ReadOnlySpan<byte>(labelBytes), new ReadOnlySpan<byte>(contextBytes), result);
+ Assert.Equal(expected, result);
+ }
+
+ result = SP800108HmacCounterKdf.DeriveBytes(
+ key,
+ hashAlgorithm,
+ labelBytes,
+ contextBytes,
+ expected.Length);
+ Assert.Equal(expected, result);
+
+ result = SP800108HmacCounterKdf.DeriveBytes(
+ new ReadOnlySpan<byte>(key),
+ hashAlgorithm,
+ new ReadOnlySpan<byte>(labelBytes),
+ new ReadOnlySpan<byte>(contextBytes),
+ expected.Length);
+ Assert.Equal(expected, result);
+
+ result = new byte[expected.Length];
+ SP800108HmacCounterKdf.DeriveBytes(
+ new ReadOnlySpan<byte>(key),
+ hashAlgorithm,
+ new ReadOnlySpan<byte>(labelBytes),
+ new ReadOnlySpan<byte>(contextBytes),
+ result);
+ Assert.Equal(expected, result);
+ }
+
+ private static void VerifyKbkdf(byte[] expected, byte[] key, HashAlgorithmName hashAlgorithm, char[] label, char[] context)
+ {
+ // The actual implementation uses a stricter UTF8 encoding/decoding but we know our test data does not contain
+ // invalid UTF8.
+ byte[] labelBytes = System.Text.Encoding.UTF8.GetBytes(label);
+ byte[] contextBytes = System.Text.Encoding.UTF8.GetBytes(context);
+ byte[] result;
+
+ VerifyKbkdfBytes(expected, key, hashAlgorithm, labelBytes, contextBytes);
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = kdf.DeriveKey(new ReadOnlySpan<char>(label), new ReadOnlySpan<char>(context), expected.Length);
+ Assert.Equal(expected, result);
+ }
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = new byte[expected.Length];
+ kdf.DeriveKey(new ReadOnlySpan<char>(label), new ReadOnlySpan<char>(context), result);
+ Assert.Equal(expected, result);
+ }
+
+ using (SP800108HmacCounterKdf kdf = new SP800108HmacCounterKdf(key, hashAlgorithm))
+ {
+ result = kdf.DeriveKey(new string(label), new string(context), expected.Length);
+ Assert.Equal(expected, result);
+ }
+
+ result = SP800108HmacCounterKdf.DeriveBytes(
+ key,
+ hashAlgorithm,
+ new string(label),
+ new string(context),
+ expected.Length);
+ Assert.Equal(expected, result);
+
+ result = SP800108HmacCounterKdf.DeriveBytes(
+ new ReadOnlySpan<byte>(key),
+ hashAlgorithm,
+ new ReadOnlySpan<char>(label),
+ new ReadOnlySpan<char>(context),
+ expected.Length);
+ Assert.Equal(expected, result);
+
+ result = new byte[expected.Length];
+ SP800108HmacCounterKdf.DeriveBytes(
+ new ReadOnlySpan<byte>(key),
+ hashAlgorithm,
+ new ReadOnlySpan<char>(label),
+ new ReadOnlySpan<char>(context),
+ result);
+ Assert.Equal(expected, result);
+ }
+
+ private static void RaceCalls(byte[] expected1, byte[] expected2, bool isDisposing, Func<int, byte[]> call1, Func<int, byte[]> call2)
+ {
+ const int Iterations = 1_000;
+
+ void ThreadCallback(object state)
+ {
+ (Func<int, byte[]> act, byte[] expected) = ((Func<int, byte[]>, byte[]))state;
+ byte[][] results = new byte[Iterations][];
+
+ for (int i = 0; i < Iterations; i++)
+ {
+ // defer asserting until after the loop so that the assert doesn't dominate the work of the
+ // threads interacting.
+ try
+ {
+ results[i] = act(i);
+ }
+ catch (ObjectDisposedException) when (isDisposing)
+ {
+ results[i] = null;
+ }
+ }
+
+ for (int i = 0; i < Iterations; i++)
+ {
+ byte[] result = results[i];
+
+ if (result is not null)
+ {
+ Assert.Equal(expected, result);
+ }
+ }
+ }
+
+ Thread t1 = new Thread(ThreadCallback);
+ Thread t2 = new Thread(ThreadCallback);
+
+ t1.Start((call1, expected1));
+ t2.Start((call2, expected2));
+ t1.Join();
+ t2.Join();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Security.Cryptography.Tests
+{
+ public static partial class SP800108HmacCounterKdfTests
+ {
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+ public static void Race_ReusingOneInstance_Allocating()
+ {
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => kdf.DeriveKey("label"u8, "context"u8, derivedKeyLengthInBytes: 16),
+ _ => kdf.DeriveKey("label"u8, "bananas"u8, derivedKeyLengthInBytes: 16));
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), derivedKeyLengthInBytes: 16),
+ _ => kdf.DeriveKey("label"u8.ToArray(), "bananas"u8.ToArray(), derivedKeyLengthInBytes: 16));
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => kdf.DeriveKey("label", "context", derivedKeyLengthInBytes: 16),
+ _ => kdf.DeriveKey("label", "bananas", derivedKeyLengthInBytes: 16));
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), derivedKeyLengthInBytes: 16),
+ _ => kdf.DeriveKey("label".AsSpan(), "bananas".AsSpan(), derivedKeyLengthInBytes: 16));
+ }
+ }
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+ public static void Race_ReusingOneInstance_Buffering()
+ {
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label"u8, "context"u8, result);
+ return result;
+ },
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label"u8, "bananas"u8, result);
+ return result;
+ });
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), result);
+ return result;
+ },
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label"u8.ToArray(), "bananas"u8.ToArray(), result);
+ return result;
+ });
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => {
+ return kdf.DeriveKey("label", "context", 16);
+ },
+ _ => {
+ return kdf.DeriveKey("label", "bananas", 16);
+ });
+ }
+
+ using (SP800108HmacCounterKdf kdf = new("kdf"u8, HashAlgorithmName.SHA256))
+ {
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: false,
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), result);
+ return result;
+ },
+ _ => {
+ byte[] result = new byte[16];
+ kdf.DeriveKey("label".AsSpan(), "bananas".AsSpan(), result);
+ return result;
+ });
+ }
+ }
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/68162", TestPlatforms.Browser)] // wasm threading support
+ public static void Race_UseAndDisposeOneInstance_Allocating()
+ {
+ SP800108HmacCounterKdf kdf;
+
+ kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: true,
+ _ => kdf.DeriveKey("label"u8, "context"u8, derivedKeyLengthInBytes: 16),
+ i => {
+ if (i == 50)
+ {
+ kdf.Dispose();
+ kdf.Dispose();
+ }
+
+ return null;
+ });
+
+ kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: true,
+ _ => kdf.DeriveKey("label"u8.ToArray(), "context"u8.ToArray(), derivedKeyLengthInBytes: 16),
+ i => {
+ if (i == 50)
+ {
+ kdf.Dispose();
+ kdf.Dispose();
+ }
+
+ return null;
+ });
+
+ kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: true,
+ _ => kdf.DeriveKey("label", "context", derivedKeyLengthInBytes: 16),
+ i => {
+ if (i == 50)
+ {
+ kdf.Dispose();
+ kdf.Dispose();
+ }
+
+ return null;
+ });
+
+ kdf = new SP800108HmacCounterKdf("kdf"u8, HashAlgorithmName.SHA256);
+ RaceCalls(
+ new byte[] { 0xDC, 0xD6, 0x23, 0xE8, 0x59, 0xB8, 0x4B, 0x95, 0xBF, 0x44, 0x32, 0x6E, 0x2B, 0xA6, 0x34, 0xF0 },
+ new byte[] { 0x92, 0xB0, 0xD7, 0xDA, 0x2C, 0xB1, 0xAA, 0x8C, 0xD5, 0xDF, 0x97, 0x9E, 0x61, 0xA3, 0x57, 0xD6 },
+ isDisposing: true,
+ _ => kdf.DeriveKey("label".AsSpan(), "context".AsSpan(), derivedKeyLengthInBytes: 16),
+ i => {
+ if (i == 50)
+ {
+ kdf.Dispose();
+ kdf.Dispose();
+ }
+
+ return null;
+ });
+ }
+ }
+}
--- /dev/null
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ref", "ref", "{7E9F6DE1-771B-4E25-A603-EC43D0291C8B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography", "ref\Microsoft.Bcl.Cryptography.csproj", "{63655B2E-6A06-4E48-9F01-D0B910063165}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{98708A22-7268-4EDB-AE37-70AA958A772A}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography", "src\Microsoft.Bcl.Cryptography.csproj", "{B0716D7E-B824-4866-A1ED-DF31BA2970B9}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8C3BD4AD-1A56-4204-9826-F8B74251D19F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Bcl.Cryptography.Tests", "tests\Microsoft.Bcl.Cryptography.Tests.csproj", "{E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{34897637-11A1-48A4-AF1F-E11463A61D0B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Interop.SourceGeneration", "..\System.Runtime.InteropServices\gen\Microsoft.Interop.SourceGeneration\Microsoft.Interop.SourceGeneration.csproj", "{A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibraryImportGenerator", "..\System.Runtime.InteropServices\gen\LibraryImportGenerator\LibraryImportGenerator.csproj", "{E9271403-BEF5-46E9-B68B-16EF69AA7149}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {63655B2E-6A06-4E48-9F01-D0B910063165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {63655B2E-6A06-4E48-9F01-D0B910063165}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {63655B2E-6A06-4E48-9F01-D0B910063165}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {63655B2E-6A06-4E48-9F01-D0B910063165}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B0716D7E-B824-4866-A1ED-DF31BA2970B9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E9271403-BEF5-46E9-B68B-16EF69AA7149}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {63655B2E-6A06-4E48-9F01-D0B910063165} = {7E9F6DE1-771B-4E25-A603-EC43D0291C8B}
+ {B0716D7E-B824-4866-A1ED-DF31BA2970B9} = {98708A22-7268-4EDB-AE37-70AA958A772A}
+ {1AEF7C7B-5A86-4A5E-9F8B-3933F7624751} = {8C3BD4AD-1A56-4204-9826-F8B74251D19F}
+ {E66D17AB-BBAF-4F2B-AC9C-8E89BDCC6191} = {8C3BD4AD-1A56-4204-9826-F8B74251D19F}
+ {A4C2BDDC-1AFB-45A8-9E9B-4AD7396A4DF2} = {34897637-11A1-48A4-AF1F-E11463A61D0B}
+ {E9271403-BEF5-46E9-B68B-16EF69AA7149} = {34897637-11A1-48A4-AF1F-E11463A61D0B}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {EAA35B12-9858-4428-8510-F09B19933FB9}
+ EndGlobalSection
+EndGlobal
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Security.Cryptography.SP800108HmacCounterKdf))]
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the https://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
+
+namespace System.Security.Cryptography
+{
+ public sealed partial class SP800108HmacCounterKdf : System.IDisposable
+ {
+ public SP800108HmacCounterKdf(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public SP800108HmacCounterKdf(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+ public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes) { throw null; }
+ public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+ public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+ public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+ public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+ public byte[] DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+ public void DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+ public void DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes) { throw null; }
+ public void Dispose() { }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+ <Compile Include="Microsoft.Bcl.Cryptography.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
+ <Compile Include="Microsoft.Bcl.Cryptography.Forwards.cs" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+ <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
+ </ItemGroup>
+
+</Project>
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <TargetFrameworks>$(NetCoreAppCurrent);netstandard2.0;$(NetFrameworkMinimum)</TargetFrameworks>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <IsPackable>true</IsPackable>
+ <!-- Disabling baseline validation since this is a brand new package.
+ Once this package has shipped a stable version, the following line
+ should be removed in order to re-enable validation. -->
+ <DisablePackageBaselineValidation>true</DisablePackageBaselineValidation>
+ <PackageDescription>Provides support for some cryptographic primitives for .NET Framework and .NET Standard.
+
+Commonly Used Types:
+System.Security.Cryptography.SP800108HmacCounterKdf</PackageDescription>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <IsPartialFacadeAssembly Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsPartialFacadeAssembly>
+ <OmitResources Condition="'$(IsPartialFacadeAssembly)' == 'true'">true</OmitResources>
+ </PropertyGroup>
+
+ <ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptAlgorithmHandle.cs"
+ Link="Microsoft\Win32\SafeHandles\SafeBCryptAlgorithmHandle.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptHandle.cs"
+ Link="Common\Microsoft\Win32\SafeHandles\SafeBCryptHandle.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptCloseAlgorithmProvider.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptCloseAlgorithmProvider.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs"
+ Link="Common\Interop\Windows\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.CreateCryptographicException.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.CreateCryptographicException.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.NTSTATUS.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.NTSTATUS.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoThrowHelper.Windows.cs"
+ Link="Common\System\Security\Cryptography\CryptoThrowHelper.Windows.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.FormatMessage.cs"
+ Link="Common\Internal\Windows\Kernel32\Interop.FormatMessage.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptOpenAlgorithmProvider.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptGenerateSymmetricKey.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs"
+ Link="Microsoft\Win32\SafeHandles\SafeBCryptKeyHandle.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptDestroyKey.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.Blobs.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.Blobs.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs"
+ Link="Common\Interop\Windows\BCrypt\Interop.BCryptKeyDerivation.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\CryptoPool.cs"
+ Link="Common\System\Security\Cryptography\CryptoPool.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdf.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\Utf8DataEncoding.cs"
+ Link="Common\System\Security\Cryptography\Utf8DataEncoding.cs" />
+
+ <Compile Include="System\Security\Cryptography\HashAlgorithmNames.cs" />
+ <Compile Include="System\Security\Cryptography\NetStandardShims.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'">
+ <PackageReference Include="System.Memory" Version="$(SystemMemoryVersion)" />
+ </ItemGroup>
+
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+ <PackageReference Include="System.Security.Cryptography.Algorithms" Version="$(SystemSecurityCryptographyAlgorithmsVersion)" />
+ </ItemGroup>
+</Project>
--- /dev/null
+<root>
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
+ <value>Non-negative number required.</value>
+ </data>
+ <data name="ArgumentOutOfRange_KOut_Too_Large" xml:space="preserve">
+ <value>The number of bytes requested is too large. The number of bytes produced by SP800108HmacCounterKdf cannot exceed 536,870,911 bytes.</value>
+ </data>
+ <data name="Argument_EmptyString" xml:space="preserve">
+ <value>The value cannot be an empty string.</value>
+ </data>
+ <data name="Cryptography_UnknownHashAlgorithm" xml:space="preserve">
+ <value>'{0}' is not a known hash algorithm.</value>
+ </data>
+</root>
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Security.Cryptography
+{
+ internal static class HashAlgorithmNames
+ {
+ internal const string SHA1 = nameof(SHA1);
+ internal const string SHA256 = nameof(SHA256);
+ internal const string SHA384 = nameof(SHA384);
+ internal const string SHA512 = nameof(SHA512);
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Text;
+using System.Runtime.CompilerServices;
+
+namespace System.Security.Cryptography
+{
+ internal static class NetStandardShims
+ {
+ internal static unsafe int GetBytes(this Encoding encoding, ReadOnlySpan<char> str, Span<byte> destination)
+ {
+ if (str.IsEmpty)
+ {
+ return 0;
+ }
+
+ fixed (char* pStr = str)
+ fixed (byte* pDestination = destination)
+ {
+ return encoding.GetBytes(pStr, str.Length, pDestination, destination.Length);
+ }
+ }
+ }
+
+ internal static class CryptographicOperations
+ {
+ [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)]
+ internal static void ZeroMemory(Span<byte> buffer)
+ {
+ buffer.Clear();
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ public sealed partial class SP800108HmacCounterKdf : IDisposable
+ {
+ private static readonly bool s_useCngKeyDerivation = IsWindows8OrGreater();
+
+ private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm)
+ {
+ if (s_useCngKeyDerivation)
+ {
+ return new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm);
+ }
+ else
+ {
+ return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+ }
+ }
+
+ private static partial byte[] DeriveBytesCore(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ int derivedKeyLengthInBytes)
+ {
+ byte[] result = new byte[derivedKeyLengthInBytes];
+
+ if (s_useCngKeyDerivation)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+ }
+
+ return result;
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination)
+ {
+ if (s_useCngKeyDerivation)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<char> label,
+ ReadOnlySpan<char> context,
+ Span<byte> destination)
+ {
+ if (s_useCngKeyDerivation)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ }
+
+ private static bool IsWindows8OrGreater()
+ {
+#if NET
+ return OperatingSystem.IsWindowsVersionAtLeast(6, 2);
+#elif NETSTANDARD
+ bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ Version version = Environment.OSVersion.Version;
+ return isWindows && (version.Major > 6 || (version.Major == 6 && version.Minor >= 2));
+#elif NETFRAMEWORK
+ Version version = Environment.OSVersion.Version;
+ return version.Major > 6 || (version.Major == 6 && version.Minor >= 2);
+#else
+#error Unhandled platform target
+#endif
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class SP800108HmacCounterKdfImplementationCng
+ {
+ internal unsafe SP800108HmacCounterKdfImplementationCng(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+
+ scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+ scoped Span<byte> clearSpan = default;
+ int symmetricKeyMaterialLength;
+ int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+ if (key.Length > hashAlgorithmBlockSize)
+ {
+ byte[] keyArray = new byte[key.Length];
+
+ fixed (byte* pKeyArray = keyArray)
+ {
+ key.CopyTo(keyArray);
+ clearSpan = HashOneShot(hashAlgorithm, keyArray);
+ CryptographicOperations.ZeroMemory(keyArray);
+ }
+
+ symmetricKeyMaterial = clearSpan;
+ symmetricKeyMaterialLength = symmetricKeyMaterial.Length;
+ }
+ else if (!key.IsEmpty)
+ {
+ symmetricKeyMaterial = key;
+ symmetricKeyMaterialLength = key.Length;
+ }
+ else
+ {
+ // CNG requires a non-null pointer even when the length is zero.
+ symmetricKeyMaterial = stackalloc byte[] { 0 };
+ symmetricKeyMaterialLength = 0;
+ }
+
+ try
+ {
+ fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+ {
+ _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+ }
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(clearSpan);
+ }
+
+ _hashAlgorithm = hashAlgorithm;
+ }
+
+ // For .NET Standard / .NET Framework, provide a byte overload so that we don't go from array->span->array
+ // when we need to adjust keys that are too large.
+ internal unsafe SP800108HmacCounterKdfImplementationCng(byte[] key, HashAlgorithmName hashAlgorithm)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+
+ scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+ scoped Span<byte> clearSpan = default;
+ int symmetricKeyMaterialLength;
+ int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+ if (key.Length > hashAlgorithmBlockSize)
+ {
+ clearSpan = HashOneShot(hashAlgorithm, key);
+ symmetricKeyMaterial = clearSpan;
+ symmetricKeyMaterialLength = symmetricKeyMaterial.Length;
+ }
+ else if (key.Length > 0)
+ {
+ symmetricKeyMaterial = key;
+ symmetricKeyMaterialLength = key.Length;
+ }
+ else
+ {
+ // CNG requires a non-null pointer even when the length is zero.
+ symmetricKeyMaterial = stackalloc byte[] { 0 };
+ symmetricKeyMaterialLength = 0;
+ }
+
+ try
+ {
+ fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+ {
+ _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+ }
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(clearSpan);
+ }
+
+ _hashAlgorithm = hashAlgorithm;
+ }
+
+ private static byte[] HashOneShot(HashAlgorithmName hashAlgorithm, byte[] data)
+ {
+ using (IncrementalHash hash = IncrementalHash.CreateHash(hashAlgorithm))
+ {
+ hash.AppendData(data);
+ return hash.GetHashAndReset();
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class SP800108HmacCounterKdfImplementationManaged
+ {
+ public SP800108HmacCounterKdfImplementationManaged(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+ {
+ _key = key.ToArray();
+ _hashAlgorithm = hashAlgorithm;
+ }
+
+ internal static unsafe void DeriveBytesOneShot(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination)
+ {
+ if (destination.IsEmpty)
+ {
+ return;
+ }
+
+ // IncrementalHash needs an array of the correct size, so we can't rent for the key.
+ byte[] keyBuffer = new byte[key.Length];
+ byte[] labelBuffer = CryptoPool.Rent(label.Length);
+ byte[] contextBuffer = CryptoPool.Rent(context.Length);
+
+ // Fixed to prevent GC moves.
+ fixed (byte* pKeyBuffer = keyBuffer)
+ {
+ try
+ {
+ key.CopyTo(keyBuffer);
+ label.CopyTo(labelBuffer);
+ context.CopyTo(contextBuffer);
+
+ DeriveBytesOneShot(
+ keyBuffer,
+ hashAlgorithm,
+ labelBuffer,
+ label.Length,
+ contextBuffer,
+ context.Length,
+ destination);
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(keyBuffer);
+ CryptoPool.Return(labelBuffer, clearSize: label.Length);
+ CryptoPool.Return(contextBuffer, clearSize: context.Length);
+ }
+ }
+ }
+
+ internal static void DeriveBytesOneShot(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ Span<byte> destination)
+ {
+ DeriveBytesOneShot(key, hashAlgorithm, label, label.Length, context, context.Length, destination);
+ }
+
+ internal static unsafe void DeriveBytesOneShot(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<char> label,
+ ReadOnlySpan<char> context,
+ Span<byte> destination)
+ {
+ if (destination.Length == 0)
+ {
+ return;
+ }
+
+ // The netstandard implementation needs arrays for all inputs, so always rent and don't perform
+ // the UTF8 encoding on the stack since that will just end up renting again anyway.
+
+ Encoding utf8ThrowingEncoding = Utf8DataEncoding.ThrowingUtf8Encoding;
+ byte[] labelBuffer = CryptoPool.Rent(utf8ThrowingEncoding.GetMaxByteCount(label.Length));
+ byte[] contextBuffer = CryptoPool.Rent(utf8ThrowingEncoding.GetMaxByteCount(context.Length));
+ int labelWritten = 0;
+ int contextWritten = 0;
+
+ byte[] keyBuffer = new byte[key.Length];
+
+ fixed (byte* pKeyBuffer = keyBuffer)
+ {
+ try
+ {
+ labelWritten = utf8ThrowingEncoding.GetBytes(label, labelBuffer);
+ contextWritten = utf8ThrowingEncoding.GetBytes(context, contextBuffer);
+ key.CopyTo(keyBuffer);
+
+ DeriveBytesOneShot(
+ keyBuffer,
+ hashAlgorithm,
+ labelBuffer,
+ labelWritten,
+ contextBuffer,
+ contextWritten,
+ destination);
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(keyBuffer);
+ CryptoPool.Return(labelBuffer, labelWritten);
+ CryptoPool.Return(contextBuffer, contextWritten);
+ }
+ }
+ }
+
+ private static void DeriveBytesOneShot(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ int labelLength,
+ byte[] context,
+ int contextLength,
+ 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))
+ {
+ // We use this rented buffer for three things. The first two uints for i and L, and last byte
+ // for the zero separator. So this is
+ // uint(L) | uint(i) | byte(0)
+ // This lets us use a single rent for the L, i, and 0x00.
+ const int LOffset = 0;
+ const int LLength = sizeof(uint);
+ const int IOffset = LLength + LOffset;
+ const int ILength = sizeof(uint);
+ const int ZeroOffset = IOffset + ILength;
+ const int ZeroLength = sizeof(byte);
+ const int RentSize = ZeroOffset + ZeroLength;
+
+ byte[]? rentedBuffer = null;
+
+ try
+ {
+ rentedBuffer = CryptoPool.Rent(RentSize);
+
+ WriteUInt32BigEndian((uint)destination.Length * 8U, rentedBuffer.AsSpan(LOffset, LLength));
+ rentedBuffer[ZeroOffset] = 0;
+
+ for (uint i = 1; !destination.IsEmpty; i++)
+ {
+ WriteUInt32BigEndian(i, rentedBuffer.AsSpan(IOffset, ILength));
+ hash.AppendData(rentedBuffer, IOffset, ILength);
+ hash.AppendData(label, 0, labelLength);
+ hash.AppendData(rentedBuffer, ZeroOffset, ZeroLength);
+ hash.AppendData(context, 0, contextLength);
+ hash.AppendData(rentedBuffer, LOffset, LLength);
+
+ byte[] hmac = hash.GetHashAndReset();
+ int needed = Math.Min(destination.Length, hmac.Length);
+ hmac.AsSpan(0, needed).CopyTo(destination);
+ destination = destination.Slice(needed);
+
+ // Best effort to zero out the key material.
+ CryptographicOperations.ZeroMemory(hmac);
+ }
+ }
+ finally
+ {
+ if (rentedBuffer is not null)
+ {
+ CryptoPool.Return(rentedBuffer, clearSize: RentSize);
+ }
+ }
+ }
+ }
+ }
+
+ private static void WriteUInt32BigEndian(uint value, Span<byte> destination)
+ {
+ Debug.Assert(destination.Length == sizeof(uint));
+ destination[0] = (byte)(value >> 24);
+ destination[1] = (byte)(value >> 16);
+ destination[2] = (byte)(value >> 8);
+ destination[3] = (byte)(value);
+ }
+ }
+}
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <TargetFrameworks>$(NetCoreAppCurrent);$(NetFrameworkMinimum)</TargetFrameworks>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\src\Microsoft.Bcl.Cryptography.csproj" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETFramework'">
+ <PackageReference Include="System.ValueTuple" Version="$(SystemValueTupleVersion)" />
+ </ItemGroup>
+</Project>
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("CreateFormatter is not trim compatible because the algorithm implementation referenced by FormatterAlgorithm might be removed.")]
public virtual System.Security.Cryptography.AsymmetricSignatureFormatter CreateFormatter(System.Security.Cryptography.AsymmetricAlgorithm key) { throw null; }
}
+ public sealed partial class SP800108HmacCounterKdf : System.IDisposable
+ {
+ public SP800108HmacCounterKdf(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public SP800108HmacCounterKdf(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm) { }
+ public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+ public static byte[] DeriveBytes(byte[] key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, string label, string context, int derivedKeyLengthInBytes) { throw null; }
+ public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+ public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+ public static byte[] DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+ public static void DeriveBytes(System.ReadOnlySpan<byte> key, System.Security.Cryptography.HashAlgorithmName hashAlgorithm, System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(byte[] label, byte[] context, int derivedKeyLengthInBytes) { throw null; }
+ public byte[] DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, int derivedKeyLengthInBytes) { throw null; }
+ public void DeriveKey(System.ReadOnlySpan<byte> label, System.ReadOnlySpan<byte> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, int derivedKeyLengthInBytes) { throw null; }
+ public void DeriveKey(System.ReadOnlySpan<char> label, System.ReadOnlySpan<char> context, System.Span<byte> destination) { }
+ public byte[] DeriveKey(string label, string context, int derivedKeyLengthInBytes) { throw null; }
+ public void Dispose() { }
+ }
public abstract partial class SymmetricAlgorithm : System.IDisposable
{
protected int BlockSizeValue;
<data name="Argument_DestinationTooShort" xml:space="preserve">
<value>Destination is too short.</value>
</data>
+ <data name="Argument_EmptyString" xml:space="preserve">
+ <value>The value cannot be an empty string.</value>
+ </data>
<data name="Argument_Invalid_SafeHandleInvalidOrClosed" xml:space="preserve">
<value>The method cannot be called with an invalid or closed SafeHandle.</value>
</data>
<data name="ArgumentOutOfRange_IndexMustBeLessOrEqual" xml:space="preserve">
<value>Index was out of range. Must be non-negative and less than or equal to the size of the collection.</value>
</data>
+ <data name="ArgumentOutOfRange_KOut_Too_Large" xml:space="preserve">
+ <value>The number of bytes requested is too large. The number of bytes produced by SP800108HmacCounterKdf cannot exceed 536,870,911 bytes.</value>
+ </data>
<data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
<value>Non-negative number required.</value>
</data>
Link="Common\System\Security\Cryptography\RSAKeyFormatHelper.Encrypted.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RsaPaddingProcessor.cs"
Link="Common\System\Security\Cryptography\RsaPaddingProcessor.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdf.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdf.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationBase.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\Utf8DataEncoding.cs"
+ Link="Common\System\Security\Cryptography\Utf8DataEncoding.cs" />
<Compile Include="$(CommonPath)System\Text\ValueStringBuilder.cs"
Link="Common\System\Text\ValueStringBuilder.cs" />
<Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs"
Link="Common\Interop\Browser\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.cs"
Link="Common\System\Sha1ForNonSecretPurposes.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesGcm.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\AesImplementation.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.NotSupported.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\SHAHashProvider.Browser.Managed.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\TripleDesImplementation.NotSupported.cs" />
Link="Common\System\Security\Cryptography\ECOpenSsl.ImportExport.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RSAOpenSsl.cs"
Link="Common\System\Security\Cryptography\RSAOpenSsl.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\DigestInfoAsn.xml">
<Link>Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml</Link>
</AsnXml>
<Compile Include="System\Security\Cryptography\RSAOpenSsl.cs" />
<Compile Include="System\Security\Cryptography\RSAWrapper.cs" />
<Compile Include="System\Security\Cryptography\SafeEvpPKeyHandle.OpenSsl.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
<Compile Include="System\Security\Cryptography\TripleDesImplementation.OpenSsl.cs" />
<AsnXml Include="System\Security\Cryptography\X509Certificates\Asn1\DistributionPointAsn.xml" />
Link="Common\System\Security\Cryptography\ECDsaAndroid.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RSAAndroid.cs"
Link="Common\System\Security\Cryptography\RSAAndroid.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<AsnXml Include="$(CommonPath)System\Security\Cryptography\Asn1\DigestInfoAsn.xml">
<Link>Common\System\Security\Cryptography\Asn1\DigestInfoAsn.xml</Link>
</AsnXml>
<Compile Include="System\Security\Cryptography\RC2Implementation.NotSupported.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.Android.cs" />
<Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.Unix.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+<Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
<Compile Include="System\Security\Cryptography\TripleDesImplementation.OpenSsl.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\AndroidCertificatePal.cs" />
Link="Common\System\Security\Cryptography\ECDsaSecurityTransforms.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RSASecurityTransforms.cs"
Link="Common\System\Security\Cryptography\RSASecurityTransforms.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\SecKeyPair.cs"
Link="Common\System\Security\Cryptography\SecKeyPair.cs" />
<Compile Include="System\Security\Cryptography\AesImplementation.Apple.cs" />
<Compile Include="System\Security\Cryptography\RC2Implementation.Apple.cs" />
<Compile Include="System\Security\Cryptography\RSA.Create.SecurityTransforms.cs" />
<Compile Include="System\Security\Cryptography\RSACryptoServiceProvider.Unix.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Managed.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
<Compile Include="System\Security\Cryptography\TripleDesImplementation.Apple.cs" />
<Compile Include="System\Security\Cryptography\X509Certificates\AppleCertificatePal.cs" />
Link="Common\System\Security\Cryptography\RSACng.ImportExport.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\RSACng.SignVerify.cs"
Link="Common\System\Security\Cryptography\RSACng.SignVerify.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+ <Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
+ Link="Common\System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="Microsoft\Win32\SafeHandles\NCryptSafeHandles.cs" />
<Compile Include="System\Security\Cryptography\AeadCommon.Windows.cs" />
<Compile Include="System\Security\Cryptography\AesCcm.Windows.cs" />
<Compile Include="System\Security\Cryptography\RSACng.cs" />
<Compile Include="System\Security\Cryptography\RSACng.ImportExport.cs" />
<Compile Include="System\Security\Cryptography\RSACng.Key.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdf.Windows.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationCng.cs" />
+ <Compile Include="System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCng.Windows.cs" />
<Compile Include="System\Security\Cryptography\TripleDESCryptoServiceProvider.Wrap.cs" />
<Compile Include="System\Security\Cryptography\TripleDesImplementation.Windows.cs" />
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ public sealed partial class SP800108HmacCounterKdf : IDisposable
+ {
+ private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm)
+ {
+ return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+ }
+
+ private static partial byte[] DeriveBytesCore(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ int derivedKeyLengthInBytes)
+ {
+ byte[] result = new byte[derivedKeyLengthInBytes];
+
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+
+ return result;
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination)
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<char> label,
+ ReadOnlySpan<char> context,
+ Span<byte> destination)
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Text;
+
+namespace System.Security.Cryptography
+{
+ public sealed partial class SP800108HmacCounterKdf : IDisposable
+ {
+ private static readonly bool s_isWindows8OrGreater = OperatingSystem.IsWindowsVersionAtLeast(6, 2);
+
+ private static partial SP800108HmacCounterKdfImplementationBase CreateImplementation(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm)
+ {
+ if (s_isWindows8OrGreater)
+ {
+ return new SP800108HmacCounterKdfImplementationCng(key, hashAlgorithm);
+ }
+ else
+ {
+ return new SP800108HmacCounterKdfImplementationManaged(key, hashAlgorithm);
+ }
+ }
+
+ private static partial byte[] DeriveBytesCore(
+ byte[] key,
+ HashAlgorithmName hashAlgorithm,
+ byte[] label,
+ byte[] context,
+ int derivedKeyLengthInBytes)
+ {
+ byte[] result = new byte[derivedKeyLengthInBytes];
+
+ if (s_isWindows8OrGreater)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, result);
+ }
+
+ return result;
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<byte> label,
+ ReadOnlySpan<byte> context,
+ Span<byte> destination)
+ {
+ if (s_isWindows8OrGreater)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ }
+
+ private static partial void DeriveBytesCore(
+ ReadOnlySpan<byte> key,
+ HashAlgorithmName hashAlgorithm,
+ ReadOnlySpan<char> label,
+ ReadOnlySpan<char> context,
+ Span<byte> destination)
+ {
+ if (s_isWindows8OrGreater)
+ {
+ SP800108HmacCounterKdfImplementationCng.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ else
+ {
+ SP800108HmacCounterKdfImplementationManaged.DeriveBytesOneShot(key, hashAlgorithm, label, context, destination);
+ }
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+
+namespace System.Security.Cryptography
+{
+ internal sealed partial class SP800108HmacCounterKdfImplementationCng
+ {
+ internal unsafe SP800108HmacCounterKdfImplementationCng(ReadOnlySpan<byte> key, HashAlgorithmName hashAlgorithm)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+
+ scoped ReadOnlySpan<byte> symmetricKeyMaterial;
+ scoped Span<byte> clearSpan = default;
+ int symmetricKeyMaterialLength;
+ int hashAlgorithmBlockSize = GetHashBlockSize(hashAlgorithm.Name);
+
+ if (key.Length > hashAlgorithmBlockSize)
+ {
+ Span<byte> buffer = stackalloc byte[512 / 8]; // Largest supported digest is SHA512.
+ symmetricKeyMaterialLength = HashOneShot(hashAlgorithm, key, buffer);
+ clearSpan = buffer.Slice(0, symmetricKeyMaterialLength);
+ symmetricKeyMaterial = clearSpan;
+ }
+ else if (!key.IsEmpty)
+ {
+ symmetricKeyMaterial = key;
+ symmetricKeyMaterialLength = key.Length;
+ }
+ else
+ {
+ // CNG requires a non-null pointer even when the length is zero.
+ symmetricKeyMaterial = stackalloc byte[] { 0 };
+ symmetricKeyMaterialLength = 0;
+ }
+
+ try
+ {
+ fixed (byte* pSymmetricKeyMaterial = symmetricKeyMaterial)
+ {
+ _keyHandle = CreateSymmetricKey(pSymmetricKeyMaterial, symmetricKeyMaterialLength);
+ }
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(clearSpan);
+ }
+
+ _hashAlgorithm = hashAlgorithm;
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "Weak algorithms are used as instructed by the caller")]
+ private static int HashOneShot(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> data, Span<byte> destination)
+ {
+ Debug.Assert(hashAlgorithm.Name is not null);
+
+ switch (hashAlgorithm.Name)
+ {
+ case HashAlgorithmNames.SHA1:
+ return SHA1.HashData(data, destination);
+ case HashAlgorithmNames.SHA256:
+ return SHA256.HashData(data, destination);
+ case HashAlgorithmNames.SHA384:
+ return SHA384.HashData(data, destination);
+ case HashAlgorithmNames.SHA512:
+ return SHA512.HashData(data, destination);
+ default:
+ Debug.Fail($"Unexpected hash algorithm '{hashAlgorithm.Name}'");
+ throw new CryptographicException();
+ }
+ }
+ }
+}
--- /dev/null
+// 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 = stackalloc byte[] { 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);
+ }
+ }
+ }
+}
Link="ProductionCode\Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs"
Link="ProductionCode\Common\System\Net\StreamBuffer.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ArgValidation.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Functional.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.Helpers.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs"
+ Link="CommonTest\System\Security\Cryptography\SP800108HmacCounterKdfTests.ThreadSafety.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\SignatureSupport.cs"
Link="CommonTest\System\Security\Cryptography\SignatureSupport.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.Data.cs"