-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Open
Labels
area-MetaenhancementProduct code improvement that does NOT require public API changes/additionsProduct code improvement that does NOT require public API changes/additionshelp wanted[up-for-grabs] Good issue for external contributors[up-for-grabs] Good issue for external contributors
Milestone
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
public class Sha1Runner
{
private byte[] _input;
private byte[] _digest = new byte[20];
[Params(0, 8, 12, 24, 32, 64, 128, 256)]
public int InputSizeInBytes { get; set; }
[GlobalSetup]
public void Setup()
{
_input = new byte[InputSizeInBytes];
RandomNumberGenerator.Fill(_input);
}
[Benchmark(Baseline = true)]
public byte[] UseManaged()
{
Sha1ForNonSecretPurposes sha1 = default;
sha1.Start();
sha1.Append(_input);
sha1.Finish(_digest);
return _digest;
}
[Benchmark(Baseline = false)]
public byte[] UseBCrypt()
{
SHA1.HashData(_input, _digest);
return _digest;
}
private struct Sha1ForNonSecretPurposes
{
private long length; // Total message length in bits
private uint[] w; // Workspace
private int pos; // Length of current chunk in bytes
/// <summary>
/// Call Start() to initialize the hash object.
/// </summary>
[SkipLocalsInit]
public void Start()
{
this.w ??= new uint[85];
this.length = 0;
this.pos = 0;
this.w[80] = 0x67452301;
this.w[81] = 0xEFCDAB89;
this.w[82] = 0x98BADCFE;
this.w[83] = 0x10325476;
this.w[84] = 0xC3D2E1F0;
}
/// <summary>
/// Adds an input byte to the hash.
/// </summary>
/// <param name="input">Data to include in the hash.</param>
[SkipLocalsInit]
public void Append(byte input)
{
this.w[this.pos / 4] = (this.w[this.pos / 4] << 8) | input;
if (64 == ++this.pos)
{
this.Drain();
}
}
/// <summary>
/// Adds input bytes to the hash.
/// </summary>
/// <param name="input">
/// Data to include in the hash. Must not be null.
/// </param>
[SkipLocalsInit]
#if ES_BUILD_STANDALONE
public void Append(byte[] input)
#else
public void Append(ReadOnlySpan<byte> input)
#endif
{
foreach (byte b in input)
{
this.Append(b);
}
}
/// <summary>
/// Retrieves the hash value.
/// Note that after calling this function, the hash object should
/// be considered uninitialized. Subsequent calls to Append or
/// Finish will produce useless results. Call Start() to
/// reinitialize.
/// </summary>
/// <param name="output">
/// Buffer to receive the hash value. Must not be null.
/// Up to 20 bytes of hash will be written to the output buffer.
/// If the buffer is smaller than 20 bytes, the remaining hash
/// bytes will be lost. If the buffer is larger than 20 bytes, the
/// rest of the buffer is left unmodified.
/// </param>
[SkipLocalsInit]
public void Finish(byte[] output)
{
long l = this.length + 8 * this.pos;
this.Append(0x80);
while (this.pos != 56)
{
this.Append(0x00);
}
unchecked
{
this.Append((byte)(l >> 56));
this.Append((byte)(l >> 48));
this.Append((byte)(l >> 40));
this.Append((byte)(l >> 32));
this.Append((byte)(l >> 24));
this.Append((byte)(l >> 16));
this.Append((byte)(l >> 8));
this.Append((byte)l);
int end = output.Length < 20 ? output.Length : 20;
for (int i = 0; i != end; i++)
{
uint temp = this.w[80 + i / 4];
output[i] = (byte)(temp >> 24);
this.w[80 + i / 4] = temp << 8;
}
}
}
/// <summary>
/// Called when this.pos reaches 64.
/// </summary>
[SkipLocalsInit]
private void Drain()
{
for (int i = 16; i != 80; i++)
{
this.w[i] = BitOperations.RotateLeft(this.w[i - 3] ^ this.w[i - 8] ^ this.w[i - 14] ^ this.w[i - 16], 1);
}
unchecked
{
uint a = this.w[80];
uint b = this.w[81];
uint c = this.w[82];
uint d = this.w[83];
uint e = this.w[84];
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 + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}
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 + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}
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 + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}
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 + this.w[i]; e = d; d = c; c = BitOperations.RotateLeft(b, 30); b = a; a = temp;
}
this.w[80] += a;
this.w[81] += b;
this.w[82] += c;
this.w[83] += d;
this.w[84] += e;
}
this.length += 512; // 64 bytes == 512 bits
this.pos = 0;
}
}
}
Method | InputSizeInBytes | Mean | Error | StdDev | Ratio |
---|---|---|---|---|---|
UseManaged | 0 | 354.8 ns | 2.63 ns | 2.34 ns | 1.00 |
UseBCrypt | 0 | 283.2 ns | 1.68 ns | 1.49 ns | 0.80 |
UseManaged | 8 | 367.5 ns | 5.09 ns | 4.76 ns | 1.00 |
UseBCrypt | 8 | 287.1 ns | 1.42 ns | 1.26 ns | 0.78 |
UseManaged | 12 | 329.7 ns | 2.29 ns | 2.03 ns | 1.00 |
UseBCrypt | 12 | 285.9 ns | 1.22 ns | 1.02 ns | 0.87 |
UseManaged | 24 | 352.7 ns | 2.24 ns | 2.09 ns | 1.00 |
UseBCrypt | 24 | 292.6 ns | 1.75 ns | 1.63 ns | 0.83 |
UseManaged | 32 | 362.8 ns | 3.31 ns | 3.09 ns | 1.00 |
UseBCrypt | 32 | 288.2 ns | 1.95 ns | 1.53 ns | 0.80 |
UseManaged | 64 | 597.5 ns | 3.44 ns | 3.22 ns | 1.00 |
UseBCrypt | 64 | 373.3 ns | 2.31 ns | 2.16 ns | 0.62 |
UseManaged | 128 | 885.1 ns | 7.33 ns | 6.50 ns | 1.00 |
UseBCrypt | 128 | 453.9 ns | 3.26 ns | 3.05 ns | 0.51 |
UseManaged | 256 | 1,450.3 ns | 11.29 ns | 9.42 ns | 1.00 |
UseBCrypt | 256 | 603.9 ns | 3.42 ns | 2.85 ns | 0.42 |
Copilot
Metadata
Metadata
Assignees
Labels
area-MetaenhancementProduct code improvement that does NOT require public API changes/additionsProduct code improvement that does NOT require public API changes/additionshelp wanted[up-for-grabs] Good issue for external contributors[up-for-grabs] Good issue for external contributors