Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c67c709
Initial plan
Copilot Oct 11, 2025
7f3d214
Add Windows-specific BCrypt implementation for Sha1ForNonSecretPurposes
Copilot Oct 11, 2025
22b79f2
Use MemoryMarshal.GetReference for safer pointer handling
Copilot Oct 11, 2025
51cac6c
Refactor: Split Sha1ForNonSecretPurposes into platform-specific files
Copilot Oct 11, 2025
6dafca3
Update src/libraries/System.Security.Cryptography/src/System.Security…
jkotas Oct 11, 2025
abb74e0
Address review feedback: Remove Unix file from csproj and reorder Win…
Copilot Oct 11, 2025
cf4d613
Delete original Sha1ForNonSecretPurposes.cs file
Copilot Oct 11, 2025
c2cc664
Use streaming BCrypt hash APIs for better performance
Copilot Oct 11, 2025
06608fe
Add SafeBCryptAlgorithmHandle to fix build error
Copilot Oct 12, 2025
0438e94
Simplify to one-shot HashData API per feedback
Copilot Oct 12, 2025
fb515a0
Add Unix Sha1 file for Browser build
Copilot Oct 12, 2025
86075cd
Optimize GenerateGuidFromName to reduce allocations
Copilot Oct 12, 2025
0efb9dd
Apply suggestions from code review
jkotas Oct 12, 2025
793033b
Update src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windo…
jkotas Oct 12, 2025
9ee1744
Fix bug, optimize one-shot managed hash
jkotas Oct 12, 2025
46dd271
Update src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Unix.cs
jkotas Oct 12, 2025
102238a
Update src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windo…
jkotas Oct 12, 2025
5aa39a4
Update src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windo…
jkotas Oct 12, 2025
b60ffd4
Update src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windo…
jkotas Oct 12, 2025
4a07041
Add BCryptHash to direct pinvoke list
jkotas Oct 13, 2025
2936b48
Update src/coreclr/nativeaot/BuildIntegration/WindowsAPIs.txt
jkotas Oct 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/coreclr/nativeaot/BuildIntegration/WindowsAPIs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2371,3 +2371,8 @@ ucrtbase!memset
ucrtbase!realloc
ucrtbase!wmemcpy_s
ucrtbase!wmemmove_s

#
# Windows 10+
#
bcrypt!BCryptHash
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my edification, what does this do?

Copy link
Member

@jkotas jkotas Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file lists Windows APIs that are expected to be available on all Windows OS that we support. APIs in this file are opted into https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/interop#direct-pinvoke-calls . NAOT binary can have hard dependency on these APIs, and it will fail to launch if the API is missing.

APIs not on the list are imported lazily using LoadLibrary/GetProcAddress when called for the first time. EntryPointNotFoundException is thrown when the API is not available. (This is the default behavior for runtime with the JIT.)

BCryptHash is available on Windows 10+ only, thus it was missing from this list. We plan to raise the minimum supported version to Windows 10 in .NET 11, and so it should be ok to add it. This should fix the binary size regression. The binary size regression was introduced by BCryptHash introducing dependency on the lazy loading infrastructure.

Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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.Numerics;

namespace System
Expand All @@ -17,20 +19,77 @@ internal struct Sha1ForNonSecretPurposes
private uint[] _w; // Workspace
private int _pos; // Length of current chunk in bytes

/// <summary>
/// Computes the SHA1 hash of the provided data.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer to receive the hash value.</param>
public static void HashData(ReadOnlySpan<byte> source, Span<byte> destination)
{
Debug.Assert(destination.Length == 20);

Span<uint> w = stackalloc uint[85];

Start(w);

int originalLength = source.Length;

while (source.Length >= 64)
{
for (int i = 0; i < 16; i++)
{
w[i] = BinaryPrimitives.ReadUInt32BigEndian(source);
source = source.Slice(4);
}
Drain(w);
}

Span<byte> tail = stackalloc byte[2 * 64];
source.CopyTo(tail);
int pos = source.Length;
tail[pos++] = 0x80;
while ((pos & 63) != 56)
{
tail[pos++] = 0x00;
}
BinaryPrimitives.WriteUInt64BigEndian(tail.Slice(pos), (ulong)originalLength * 8);
tail = tail.Slice(0, pos + 8);

while (tail.Length > 0)
{
for (int i = 0; i < 16; i++)
{
w[i] = BinaryPrimitives.ReadUInt32BigEndian(tail);
tail = tail.Slice(4);
}
Drain(w);
}

for (int i = 80; i < 85; i++)
{
BinaryPrimitives.WriteUInt32BigEndian(destination, w[i]);
destination = destination.Slice(4);
}
}

/// <summary>
/// Call Start() to initialize the hash object.
/// </summary>
public void Start()
{
_w ??= new uint[85];
Start(_w ??= new uint[85]);

_length = 0;
_pos = 0;
_w[80] = 0x67452301;
_w[81] = 0xEFCDAB89;
_w[82] = 0x98BADCFE;
_w[83] = 0x10325476;
_w[84] = 0xC3D2E1F0;
}

private static void Start(Span<uint> w)
{
w[80] = 0x67452301;
w[81] = 0xEFCDAB89;
w[82] = 0x98BADCFE;
w[83] = 0x10325476;
w[84] = 0xC3D2E1F0;
}

/// <summary>
Expand Down Expand Up @@ -77,6 +136,8 @@ public void Append(ReadOnlySpan<byte> input)
/// </param>
public void Finish(Span<byte> output)
{
Debug.Assert(output.Length == 20);

long l = _length + 8 * _pos;
Append(0x80);
while (_pos != 56)
Expand All @@ -93,12 +154,10 @@ public void Finish(Span<byte> output)
Append((byte)(l >> 8));
Append((byte)l);

int end = output.Length < 20 ? output.Length : 20;
for (int i = 0; i != end; i++)
for (int i = 80; i < 85; i++)
{
uint temp = _w[80 + i / 4];
output[i] = (byte)(temp >> 24);
_w[80 + i / 4] = temp << 8;
BinaryPrimitives.WriteUInt32BigEndian(output, _w[i]);
output = output.Slice(4);
}
}

Expand All @@ -107,53 +166,59 @@ public void Finish(Span<byte> output)
/// </summary>
private void Drain()
{
for (int i = 16; i != 80; i++)
Drain(_w);
_length += 512; // 64 bytes == 512 bits
_pos = 0;
}

private static void Drain(Span<uint> w)
{
var _ = w[84]; // Hint to eliminate bounds checks

for (int i = 16; i < 80; i++)
{
_w[i] = BitOperations.RotateLeft(_w[i - 3] ^ _w[i - 8] ^ _w[i - 14] ^ _w[i - 16], 1);
w[i] = BitOperations.RotateLeft(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1);
}

uint a = _w[80];
uint b = _w[81];
uint c = _w[82];
uint d = _w[83];
uint e = _w[84];
uint a = w[80];
uint b = w[81];
uint c = w[82];
uint d = w[83];
uint e = w[84];

for (int i = 0; i != 20; i++)
for (int i = 0; i < 20; i++)
{
const uint k = 0x5A827999;
uint f = (b & c) | ((~b) & d);
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
Copy link
Member

@jkotas jkotas Oct 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.NET Framewwork managed implementation of SHA1 hash had these loops manually unrolled: https://github.com/microsoft/referencesource/blob/a48449cb48a9a693903668a71449ac719b76867c/mscorlib/system/security/cryptography/sha1managed.cs#L227-L231 . Doing so gets the performance close to bcrypt, but I do not think the extra code bloat is worth it for Sha1ForNonSecretPurposes

}

for (int i = 20; i != 40; i++)
for (int i = 20; i < 40; i++)
{
uint f = b ^ c ^ d;
const uint k = 0x6ED9EBA1;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}

for (int i = 40; i != 60; i++)
for (int i = 40; i < 60; i++)
{
uint f = (b & c) | (b & d) | (c & d);
const uint k = 0x8F1BBCDC;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}

for (int i = 60; i != 80; i++)
for (int i = 60; i < 80; i++)
{
uint f = b ^ c ^ d;
const uint k = 0xCA62C1D6;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + _w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
uint temp = BitOperations.RotateLeft(a, 5) + f + e + k + w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}

_w[80] += a;
_w[81] += b;
_w[82] += c;
_w[83] += d;
_w[84] += e;

_length += 512; // 64 bytes == 512 bits
_pos = 0;
w[80] += a;
w[81] += b;
w[82] += c;
w[83] += d;
w[84] += e;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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.Runtime.InteropServices;
using System.Security.Cryptography;

namespace System
{
/// <summary>
/// Implements the SHA1 hashing algorithm. Note that
/// implementation is for hashing public information. Do not
/// use code to hash private data, as implementation does
/// not take any steps to avoid information disclosure.
/// </summary>
internal struct Sha1ForNonSecretPurposes
{
/// <summary>
/// Computes the SHA1 hash of the provided data.
/// </summary>
/// <param name="source">The data to hash.</param>
/// <param name="destination">The buffer to receive the hash value.</param>
public static void HashData(ReadOnlySpan<byte> source, Span<byte> destination)
{
Debug.Assert(destination.Length == 20);

unsafe
{
fixed (byte* pSrc = &MemoryMarshal.GetReference(source))
fixed (byte* pDest = &MemoryMarshal.GetReference(destination))
{
Interop.BCrypt.NTSTATUS ntStatus = Interop.BCrypt.BCryptHash(
(uint)Interop.BCrypt.BCryptAlgPseudoHandle.BCRYPT_SHA1_ALG_HANDLE,
null,
0,
pSrc,
source.Length,
pDest,
destination.Length);

if (ntStatus != Interop.BCrypt.NTSTATUS.STATUS_SUCCESS)
{
if (ntStatus == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY)
{
throw new OutOfMemoryException();
}
else
{
throw new InvalidOperationException();
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1486,9 +1486,6 @@
<Compile Include="$(CommonPath)System\Obsoletions.cs">
<Link>Common\System\Obsoletions.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.cs">
<Link>Common\System\Sha1ForNonSecretPurposes.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\SR.cs">
<Link>Common\System\SR.cs</Link>
</Compile>
Expand Down Expand Up @@ -1750,6 +1747,12 @@
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.NTSTATUS.cs">
<Link>Common\Interop\Windows\BCrypt\Interop.NTSTATUS.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptHash.cs">
<Link>Common\Interop\Windows\BCrypt\Interop.BCryptHash.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs">
<Link>Common\Interop\Windows\BCrypt\Interop.BCryptAlgPseudoHandle.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CryptProtectMemory.cs">
<Link>Common\Interop\Windows\Crypt32\Interop.CryptProtectMemory.cs</Link>
</Compile>
Expand Down Expand Up @@ -2233,6 +2236,9 @@
<Compile Include="$(CommonPath)System\IO\PathInternal.Windows.cs">
<Link>Common\System\IO\PathInternal.Windows.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.Windows.cs">
<Link>Common\System\Sha1ForNonSecretPurposes.Windows.cs</Link>
</Compile>
<Compile Include="$(CommonPath)System\Threading\AsyncOverSyncWithIoCancellation.cs">
<Link>Common\System\Threading\AsyncOverSyncWithIoCancellation.cs</Link>
</Compile>
Expand Down Expand Up @@ -2349,6 +2355,9 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Threading\EventWaitHandle.Windows.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true' or '$(TargetsBrowser)' == 'true' or '$(TargetsWasi)' == 'true'">
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.Unix.cs">
<Link>Common\System\Sha1ForNonSecretPurposes.Unix.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs">
<Link>Common\Interop\Unix\Interop.Errors.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1764,16 +1764,18 @@ private static Guid GenerateGuidFromName(string name)
0x87, 0xF8, 0x1A, 0x15, 0xBF, 0xC1, 0x30, 0xFB,
];

byte[] bytes = Encoding.BigEndianUnicode.GetBytes(name);
Sha1ForNonSecretPurposes hash = default;
hash.Start();
hash.Append(namespaceBytes);
hash.Append(bytes);
Array.Resize(ref bytes, 16);
hash.Finish(bytes);
int nameByteCount = Encoding.BigEndianUnicode.GetByteCount(name);
int totalLength = namespaceBytes.Length + nameByteCount;
Span<byte> source = totalLength <= 256 ? stackalloc byte[totalLength] : new byte[totalLength];

namespaceBytes.CopyTo(source);
Encoding.BigEndianUnicode.GetBytes(name, source.Slice(namespaceBytes.Length));

Span<byte> bytes = stackalloc byte[20];
Sha1ForNonSecretPurposes.HashData(source, bytes);

bytes[7] = unchecked((byte)((bytes[7] & 0x0F) | 0x50)); // Set high 4 bits of octet 7 to 5, as per RFC 4122
return new Guid(bytes);
return new Guid(bytes.Slice(0, 16));
}

private static unsafe void DecodeObjects(object?[] decodedObjects, Type[] parameterTypes, EventData* data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,7 @@ internal static partial class AssemblyNameHelpers
throw new SecurityException(SR.Security_InvalidAssemblyPublicKey);

Span<byte> hash = stackalloc byte[20];

Sha1ForNonSecretPurposes sha1 = default;
sha1.Start();
sha1.Append(publicKey);
sha1.Finish(hash);
Sha1ForNonSecretPurposes.HashData(publicKey, hash);

byte[] publicKeyToken = new byte[PublicKeyTokenLength];
for (int i = 0; i < publicKeyToken.Length; i++)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -831,8 +831,8 @@
Link="Common\Interop\Unix\System.Native\Interop.GetRandomBytes.cs" />
<Compile Include="$(CommonPath)Interop\Browser\Interop.Libraries.cs"
Link="Common\Interop\Browser\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.cs"
Link="Common\System\Sha1ForNonSecretPurposes.cs" />
<Compile Include="$(CommonPath)System\Sha1ForNonSecretPurposes.Unix.cs"
Link="Common\System\Sha1ForNonSecretPurposes.Unix.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\CompositeMLDsaImplementation.NotSupported.cs"
Link="Common\System\Security\Cryptography\CompositeMLDsaImplementation.NotSupported.cs" />
<Compile Include="$(CommonPath)System\Security\Cryptography\SP800108HmacCounterKdfImplementationManaged.cs"
Expand Down
Loading