HKDF.DeriveKey fails with 0xc100000d on Windows when ikm exceeds 2048 bytes (.NET 11 regression; CNG provider key-length limit)
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
`HKDF.DeriveKey` throws `CryptographicException: Unknown error (0xc100000d)` on Windows when the input keying material is longer than **2048 bytes**. The same call succeeds on .NET 10, so this is a regression in .NET 11.
The boundary is exact and reproducible: **2048 bytes succeeds, 2049 bytes fails.**
The cause is a CNG provider limit that the managed layer does not account for. Windows' HKDF provider advertises a maximum key length of 16384 bits — exactly 2048 bytes — and rejects a longer secret at import, before any derivation happens:
```
BCryptOpenAlgorithmProvider(HKDF) -> 0x00000000
BCryptGetProperty(BCRYPT_KEY_LENGTHS) -> 0x00000000
dwMinLength = 0 bits
dwMaxLength = 16384 bits (2048 bytes)
dwIncrement = 8 bits
BCryptGenerateSymmetricKey(secret=2048) -> 0x00000000
BCryptGenerateSymmetricKey(secret=2049) -> 0xc000000d (STATUS_INVALID_PARAMETER)
```
So `HKDF.DeriveKey` fails inside `BCryptGenerateSymmetricKey` while importing the IKM, and `0xc100000d` is that `STATUS_INVALID_PARAMETER` surfacing through the managed layer. RFC 5869 places no limit on IKM length, and the managed implementation used by .NET 10 has no 2048-byte IKM limit.
`HKDF.Expand` fails the same way when given a PRK longer than 2048 bytes, since that PRK goes through the same import.
Among the values tested, only the secret's length matters:
| Varied | Values tested | Effect |
|---|---|---|
| `salt` | empty, 4 bytes | none — both fail above the threshold |
| `info` | 4 and 27 bytes | none |
| output length | 16, 32 | none |
| overload | array-returning, `Span` destination | none |
| destination buffer | heap array, `stackalloc` | none |
| hash algorithm | SHA-256, SHA-512 | none — threshold is 2048/2049 for both |
The threshold not moving with the hash algorithm is consistent with the provider-advertised key length above rather than anything block-size derived.
### Reproduction Steps
`dotnet new console -f net11.0`, then:
```csharp
using System.Security.Cryptography;
byte[] info = "info"u8.ToArray();
foreach (int ikmLength in new[] { 2048, 2049 })
{
byte[] ikm = new byte[ikmLength];
try
{
byte[] okm = HKDF.DeriveKey(HashAlgorithmName.SHA256, ikm, 16, salt: null, info);
Console.WriteLine($"ikm={ikmLength}: ok, {Convert.ToHexString(okm)}");
}
catch (CryptographicException ex)
{
Console.WriteLine($"ikm={ikmLength}: {ex.GetType().Name}: {ex.Message}");
}
}
```
`dotnet run -c Release`. To compare against .NET 10, set `net10.0;net11.0` and run each build.
### Expected behavior
Both lengths derive a key, as on .NET 10.0.11:
```
ikm=2048: ok, 646311E797D3D81ACBC7EF3A4531A08A
ikm=2049: ok, 2F7428553E1A651C70F78825CF86A9EA
```
### Actual behavior
On .NET 11.0.0-preview.7.26381.103:
```
ikm=2048: ok, 646311E797D3D81ACBC7EF3A4531A08A
ikm=2049: CryptographicException: Unknown error (0xc100000d)
```
The 16-byte result for the 2048-byte IKM is identical on both runtimes.
Stack trace from the original failure:
```
System.Security.Cryptography.CryptographicException : Unknown error (0xc100000d)
at System.Security.Cryptography.HKDF.CngDeriveKey(HashAlgorithmName hashAlgorithmName, ReadOnlySpan`1 secret, ReadOnlySpan`1 info, ReadOnlySpan`1 salt, Span`1 destination, Boolean secretIsIkm)
at System.Security.Cryptography.HKDF.DeriveKey(HashAlgorithmName hashAlgorithmName, ReadOnlySpan`1 ikm, Span`1 output, ReadOnlySpan`1 salt, ReadOnlySpan`1 info)
```
### Regression?
Yes — .NET 10 → .NET 11, same machine, same OS, same source.
https://github.com/dotnet/runtime/pull/120310 ("Bring up Windows HKDF with CNG") introduced the Windows CNG path that `DeriveKey` now takes; .NET 10 has no CNG path for HKDF, which is consistent with .NET 10 being unaffected at every length tested up to 8192 bytes. To be precise about the attribution: that PR introduced the code path that reaches the provider limit — I am not claiming the PR contains a buffer bug of its own.
I have not tested a current `main` or nightly build.
### Known Workarounds
`HKDF.Extract` followed by `HKDF.Expand`. `Extract` accepts a long IKM and returns a PRK of hash length, which is well under the limit, so the subsequent `Expand` import succeeds. Calling `Expand` directly with an oversized PRK does **not** work around it.
### Configuration
- **.NET SDK:** 11.0.100-preview.7.26381.103
- **Runtime:** 11.0.0-preview.7.26381.103 (fails), 10.0.11 (passes)
- **OS:** Windows 11 Home, 25H2, build 26200.9168
- **Architecture:** x64
The 2048-byte maximum is reported by the OS provider, so whether it is the same on other Windows builds is unverified — I have reproduced this on one machine only.
### Other information
This surfaced in production code rather than synthetically: a routine derives a deterministic nonce from a variable-length protocol transcript, and one message shape pushes the IKM past 2048 bytes while another stays under it, so the same code path succeeds or fails depending on its inputs.
A managed fallback when the secret exceeds the provider's advertised `dwMaxLength` would fix it, and an IKM larger than 2048 bytes looks like a worthwhile test case for the Windows path.
Contributor guide
Assessment
This issue has not been assessed yet.