-
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?
Changes from all commits
c67c709
7f3d214
22b79f2
51cac6c
6dafca3
abb74e0
cf4d613
c2cc664
06608fe
0438e94
fb515a0
86075cd
0efb9dd
793033b
9ee1744
46dd271
102238a
5aa39a4
b60ffd4
4a07041
2936b48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2371,3 +2371,8 @@ ucrtbase!memset | |
ucrtbase!realloc | ||
ucrtbase!wmemcpy_s | ||
ucrtbase!wmemmove_s | ||
|
||
# | ||
# Windows 10+ | ||
# | ||
bcrypt!BCryptHash | ||
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 | ||
|
@@ -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> | ||
|
@@ -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) | ||
|
@@ -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); | ||
} | ||
} | ||
|
||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
jkotas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 | ||
jkotas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
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?
Uh oh!
There was an error while loading. Please reload this page.
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.