-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for SP800-108 CTR Key Derivation Function
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.
- Loading branch information
Showing
30 changed files
with
3,171 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
611 changes: 611 additions & 0 deletions
611
src/libraries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdf.cs
Large diffs are not rendered by default.
Oops, something went wrong.
14 changes: 14 additions & 0 deletions
14
...aries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// 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(); | ||
} | ||
} |
211 changes: 211 additions & 0 deletions
211
...raries/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationCng.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// 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(); | ||
} | ||
} | ||
} | ||
} |
111 changes: 111 additions & 0 deletions
111
...es/Common/src/System/Security/Cryptography/SP800108HmacCounterKdfImplementationManaged.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// 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!; | ||
} | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/libraries/Common/src/System/Security/Cryptography/Utf8DataEncoding.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// 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); | ||
} | ||
} | ||
} | ||
|
||
} |
Oops, something went wrong.
)