diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
index 68738f67a1323b..6d8324b7eef531 100644
--- a/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
+++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Cng.cs
@@ -29,10 +29,11 @@ internal static class AlgorithmName
public const string ECDsaP256 = "ECDSA_P256"; // BCRYPT_ECDSA_P256_ALGORITHM
public const string ECDsaP384 = "ECDSA_P384"; // BCRYPT_ECDSA_P384_ALGORITHM
public const string ECDsaP521 = "ECDSA_P521"; // BCRYPT_ECDSA_P521_ALGORITHM
- public const string RSA = "RSA"; // BCRYPT_RSA_ALGORITHM
+ public const string HKDF = "HKDF"; // BCRYPT_HKDF_ALGORITHM
public const string MD5 = "MD5"; // BCRYPT_MD5_ALGORITHM
public const string MLDsa = "ML-DSA"; // BCRYPT_MLDSA_ALGORITHM
public const string MLKem = "ML-KEM"; // BCRYPT_MLKEM_ALGORITHM
+ public const string RSA = "RSA"; // BCRYPT_RSA_ALGORITHM
public const string Sha1 = "SHA1"; // BCRYPT_SHA1_ALGORITHM
public const string Sha256 = "SHA256"; // BCRYPT_SHA256_ALGORITHM
public const string Sha384 = "SHA384"; // BCRYPT_SHA384_ALGORITHM
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
index 1fd6bd17669038..aa58b261e6ab84 100644
--- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
+++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.BCryptAlgPseudoHandle.cs
@@ -23,6 +23,7 @@ public enum BCryptAlgPseudoHandle : uint
BCRYPT_HMAC_SHA384_ALG_HANDLE = 0x000000c1,
BCRYPT_HMAC_SHA512_ALG_HANDLE = 0x000000d1,
BCRYPT_PBKDF2_ALG_HANDLE = 0x00000331,
+ BCRYPT_HKDF_ALG_HANDLE = 0x00000391,
BCRYPT_SHA3_256_ALG_HANDLE = 0x000003B1,
BCRYPT_SHA3_384_ALG_HANDLE = 0x000003C1,
BCRYPT_SHA3_512_ALG_HANDLE = 0x000003D1,
diff --git a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
index f919daf562998b..42289566281153 100644
--- a/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
+++ b/src/libraries/Common/src/Interop/Windows/BCrypt/Interop.Blobs.cs
@@ -302,6 +302,7 @@ internal enum CngBufferDescriptors : int
KDF_CONTEXT = 14,
KDF_SALT = 15,
KDF_ITERATION_COUNT = 16,
+ KDF_HKDF_INFO = 20,
NCRYPTBUFFER_ECC_CURVE_NAME = 60,
}
diff --git a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
index 31fab862e9ce7c..3ef51bb89ca051 100644
--- a/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
+++ b/src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
@@ -1988,7 +1988,7 @@
-
+
diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Windows.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Windows.cs
new file mode 100644
index 00000000000000..60544defa60436
--- /dev/null
+++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/HKDF.Windows.cs
@@ -0,0 +1,239 @@
+// 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.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Internal.Cryptography;
+using Microsoft.Win32.SafeHandles;
+
+using BCryptAlgPseudoHandle = Interop.BCrypt.BCryptAlgPseudoHandle;
+using BCryptBuffer = Interop.BCrypt.BCryptBuffer;
+using BCryptBufferDesc = Interop.BCrypt.BCryptBufferDesc;
+using BCryptOpenAlgorithmProviderFlags = Interop.BCrypt.BCryptOpenAlgorithmProviderFlags;
+using BCRYPT_KEY_DATA_BLOB_HEADER = Interop.BCrypt.BCRYPT_KEY_DATA_BLOB_HEADER;
+using CngBufferDescriptors = Interop.BCrypt.CngBufferDescriptors;
+using NTSTATUS = Interop.BCrypt.NTSTATUS;
+
+namespace System.Security.Cryptography
+{
+ public static partial class HKDF
+ {
+ private static readonly bool s_hasCngImplementation = IsCngSupported();
+ private const string BCRYPT_HKDF_SALT_AND_FINALIZE = "HkdfSaltAndFinalize";
+ private const string BCRYPT_HKDF_PRK_AND_FINALIZE = "HkdfPrkAndFinalize";
+ private const string BCRYPT_HKDF_HASH_ALGORITHM = "HkdfHashAlgorithm";
+
+ private static void ExtractCore(
+ HashAlgorithmName hashAlgorithmName,
+ ReadOnlySpan ikm,
+ ReadOnlySpan salt,
+ Span prk)
+ {
+ // Windows does not clearly have a way to perform just the Extract step from HKDF. We'll use managed for now.
+ HKDFManagedImplementation.Extract(hashAlgorithmName, ikm, salt, prk);
+ }
+
+ private static void Expand(
+ HashAlgorithmName hashAlgorithmName,
+ int hashLength,
+ ReadOnlySpan prk,
+ Span output,
+ ReadOnlySpan info)
+ {
+ if (s_hasCngImplementation && !IsAlgorithmRequiringManagedFallback(hashAlgorithmName))
+ {
+ CngDeriveKey(
+ hashAlgorithmName,
+ prk,
+ info,
+ salt: default,
+ output,
+ secretIsIkm: false);
+ }
+ else
+ {
+ HKDFManagedImplementation.Expand(hashAlgorithmName, hashLength, prk, output, info);
+ }
+ }
+
+ private static void DeriveKeyCore(
+ HashAlgorithmName hashAlgorithmName,
+ int hashLength,
+ ReadOnlySpan ikm,
+ Span output,
+ ReadOnlySpan salt,
+ ReadOnlySpan info)
+ {
+ if (s_hasCngImplementation && !IsAlgorithmRequiringManagedFallback(hashAlgorithmName))
+ {
+ CngDeriveKey(
+ hashAlgorithmName,
+ ikm,
+ info,
+ salt,
+ output,
+ secretIsIkm: true);
+ }
+ else
+ {
+ HKDFManagedImplementation.DeriveKey(hashAlgorithmName, hashLength, ikm, output, salt, info);
+ }
+ }
+
+ private static bool IsAlgorithmRequiringManagedFallback(HashAlgorithmName hashAlgorithmName)
+ {
+ return hashAlgorithmName == HashAlgorithmName.MD5;
+ }
+
+ private static void ThrowIfAlgorithmNotSupported(HashAlgorithmName hashAlgorithmName)
+ {
+ if ((hashAlgorithmName == HashAlgorithmName.SHA3_256 && !SHA3_256.IsSupported) ||
+ (hashAlgorithmName == HashAlgorithmName.SHA3_384 && !SHA3_384.IsSupported) ||
+ (hashAlgorithmName == HashAlgorithmName.SHA3_512 && !SHA3_512.IsSupported))
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ private static bool IsCngSupported()
+ {
+ NTSTATUS openStatus = Interop.BCrypt.BCryptOpenAlgorithmProvider(
+ out SafeBCryptAlgorithmHandle handle,
+ Internal.NativeCrypto.BCryptNative.AlgorithmName.HKDF,
+ null,
+ BCryptOpenAlgorithmProviderFlags.None);
+
+ handle.Dispose();
+
+ // HKDF was added in Windows 10 1803.
+ Debug.Assert(!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134) || openStatus == NTSTATUS.STATUS_SUCCESS);
+ return openStatus == NTSTATUS.STATUS_SUCCESS;
+ }
+
+ private static unsafe void CngDeriveKey(
+ HashAlgorithmName hashAlgorithmName,
+ ReadOnlySpan secret,
+ ReadOnlySpan info,
+ ReadOnlySpan salt,
+ Span destination,
+ bool secretIsIkm)
+ {
+ Debug.Assert(Interop.BCrypt.PseudoHandlesSupported);
+ Debug.Assert(hashAlgorithmName.Name is not null);
+
+ ThrowIfAlgorithmNotSupported(hashAlgorithmName);
+
+ byte[]? rented;
+ ReadOnlySpan infoBlob;
+
+ if (destination.Overlaps(info))
+ {
+ rented = CryptoPool.Rent(info.Length);
+ info.CopyTo(rented);
+ infoBlob = rented.AsSpan(0, info.Length);
+ }
+ else
+ {
+ rented = null;
+ infoBlob = info;
+ }
+
+ SafeBCryptKeyHandle? keyHandle = null;
+ NTSTATUS status;
+
+ try
+ {
+ fixed (byte* pSecret = &Helpers.GetNonNullPinnableReference(secret))
+ {
+ status = Interop.BCrypt.BCryptGenerateSymmetricKey(
+ (nuint)BCryptAlgPseudoHandle.BCRYPT_HKDF_ALG_HANDLE,
+ out keyHandle,
+ pbKeyObject: IntPtr.Zero,
+ cbKeyObject: 0,
+ pSecret,
+ secret.Length,
+ dwFlags: 0);
+
+ if (status != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(status);
+ }
+
+ Interop.BCrypt.BCryptSetSZProperty(keyHandle, BCRYPT_HKDF_HASH_ALGORITHM, hashAlgorithmName.Name);
+
+ if (secretIsIkm)
+ {
+ fixed (byte* pSalt = &Helpers.GetNonNullPinnableReference(salt))
+ {
+ status = Interop.BCrypt.BCryptSetProperty(
+ keyHandle,
+ BCRYPT_HKDF_SALT_AND_FINALIZE,
+ pSalt,
+ (uint)salt.Length,
+ dwFlags: 0);
+ }
+ }
+ else
+ {
+ Debug.Assert(salt.IsEmpty);
+ status = Interop.BCrypt.BCryptSetProperty(
+ keyHandle,
+ BCRYPT_HKDF_PRK_AND_FINALIZE,
+ null,
+ 0U,
+ dwFlags: 0);
+ }
+
+ if (status != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(status);
+ }
+ }
+
+ fixed (byte* pDestination = destination)
+ fixed (byte* pInfoBlob = &Helpers.GetNonNullPinnableReference(infoBlob))
+ {
+ BCryptBuffer infoBuffer = default;
+ infoBuffer.cbBuffer = infoBlob.Length;
+ infoBuffer.BufferType = CngBufferDescriptors.KDF_HKDF_INFO;
+ infoBuffer.pvBuffer = (IntPtr)pInfoBlob;
+
+ BCryptBufferDesc bufferDesc = default;
+ bufferDesc.ulVersion = Interop.BCrypt.BCRYPTBUFFER_VERSION;
+ bufferDesc.cBuffers = 1;
+ bufferDesc.pBuffers = (IntPtr)(&infoBuffer);
+
+ status = Interop.BCrypt.BCryptKeyDerivation(
+ keyHandle,
+ &bufferDesc,
+ pDestination,
+ destination.Length,
+ out uint resultLength,
+ dwFlags: 0);
+
+ if (status != NTSTATUS.STATUS_SUCCESS)
+ {
+ throw Interop.BCrypt.CreateCryptographicException(status);
+ }
+
+ if (destination.Length != resultLength)
+ {
+ Debug.Fail("HKDF resultLength != destination.Length");
+ throw new CryptographicException();
+ }
+ }
+ }
+ finally
+ {
+ if (rented is not null)
+ {
+ CryptoPool.Return(rented, clearSize: 0); // Info is not considered secret.
+ }
+
+ keyHandle?.Dispose();
+ }
+ }
+ }
+}