-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Use Windows BCrypt for Sha1ForNonSecretPurposes instead of managed implementation #120633
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Co-authored-by: stephentoub <[email protected]>
Co-authored-by: stephentoub <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR replaces the managed SHA-1 implementation in Sha1ForNonSecretPurposes
with Windows BCrypt API calls on Windows platforms while preserving the managed implementation for non-Windows platforms. The change is motivated by compliance requirements (easier security exemptions for OS-provided implementations), performance improvements (20-58% faster), and reduced binary size on Windows.
Key changes:
- Added conditional compilation to use BCrypt on Windows (
TARGET_WINDOWS
) and managed implementation elsewhere - Windows implementation buffers input data and uses BCrypt's one-shot hash API in
Finish()
- Added required BCrypt interop declarations to the project file
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
File | Description |
---|---|
System.Private.CoreLib.Shared.projitems | Adds BCrypt interop file references for Windows builds |
Sha1ForNonSecretPurposes.cs | Implements Windows-specific BCrypt implementation with conditional compilation directives |
Co-authored-by: stephentoub <[email protected]>
src/libraries/System.Security.Cryptography/src/System.Security.Cryptography.csproj
Outdated
Show resolved
Hide resolved
….Cryptography.csproj
src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
Outdated
Show resolved
Hide resolved
…dows file alphabetically Co-authored-by: jkotas <[email protected]>
@copilot, what happened to the original runtime/src/libraries/Common/src/System/Sha1ForNonSecretPurposes.cs file? I no longer see that being modified or deleted. Shouldn't it have just been renamed with the .Unix.cs suffix, rather than duplicating it and leaving the original? |
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windows.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: stephentoub <[email protected]>
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Unix.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windows.cs
Outdated
Show resolved
Hide resolved
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; |
There was a problem hiding this comment.
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
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windows.cs
Outdated
Show resolved
Hide resolved
src/libraries/Common/src/System/Sha1ForNonSecretPurposes.Windows.cs
Outdated
Show resolved
Hide resolved
@EgorBot -intel using System.Reflection;
using BenchmarkDotNet.Attributes;
public class Bench
{
static AssemblyName an = typeof(object).Assembly.GetName();
[Benchmark]
public byte[] GetPublicKeyToken() => ((AssemblyName)an.Clone()).GetPublicKeyToken();
} |
According to MichalStrehovsky/rt-sz#191, this is introducing 5% size regression in minimal NAOT app on Windows. I guess this introduced unconditional dependency on more interop infrastructure. |
@EgorBot -windows_x64 using System.Reflection;
using BenchmarkDotNet.Attributes;
public class Bench
{
static AssemblyName an = typeof(object).Assembly.GetName();
[Benchmark]
public byte[] GetPublicKeyToken() => ((AssemblyName)an.Clone()).GetPublicKeyToken();
} |
# | ||
# Windows 8+ | ||
# | ||
bcrypt!BCryptHash |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
Adding BCryptHash to WindowsAPIs.txt does not seem to be enough to fix the size regression. This will need more investigation. In the meantime, I have split the platform neutral refactoring and optimization to #120674 . |
Summary
Per reviewer feedback, implemented one-shot hashing API for better performance:
Sha1ForNonSecretPurposes.Windows.cs
- Uses BCrypt one-shot hash API (BCryptHash) with pseudo-handleSha1ForNonSecretPurposes.Unix.cs
- Managed implementation with static HashData methodBoth implementations expose a static
HashData(ReadOnlySpan<byte>, Span<byte>)
method. The Windows implementation uses the BCrypt one-shot API for optimal performance on small inputs (typical usage is ~100s of bytes). The Unix implementation wraps the existing incremental implementation and is also used for Browser builds.The EventSource GUID generation has been optimized to reduce allocations by encoding directly into the destination buffer.
Original prompt
This section details on the original issue you should resolve
<issue_title>Consider ifdefing away Sha1ForNonSecretPurposes on Windows</issue_title>
<issue_description>We have a managed implementation of SHA-1 hanging around for use in
EventSource
and related APIs. We should consider ifdefing it away and relying on the OS's underlying implementation on runtimes where we know an OS implementation exists. This can help compliance, as getting an exemption for SHA-1 for compat purposes is far easier than getting an exemption for carrying our own implementation. There are also some small perf wins.Benchmark code below the fold