// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // Vendored from dotnet/runtime, src/libraries/Common/src/System/Sha1ForNonSecretPurposes.cs // (commit 6e51f762bc4c98ea90ae6ca21c4e220b4b2e7a5c). The only changes are the "unchecked" blocks // in Finish and Drain: upstream compiles without overflow checks, while this project sets // CheckForOverflowUnderflow, and SHA-1 relies on wrapping 32-bit arithmetic. // // Strong-name public-key tokens are defined by ECMA-335 as a SHA-1 digest, so this managed // implementation is used instead of System.Security.Cryptography.SHA1 to keep reading assembly // metadata independent of the host crypto policy (FIPS-mode systems refuse to create platform // SHA-1 instances). using System.Buffers.Binary; using System.Diagnostics; using System.Numerics; namespace System { /// /// 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. /// internal struct Sha1ForNonSecretPurposes { private long _length; // Total message length in bits private uint[] _w; // Workspace private int _pos; // Length of current chunk in bytes /// /// Computes the SHA1 hash of the provided data. /// /// The data to hash. /// The buffer to receive the hash value. public static unsafe void HashData(ReadOnlySpan source, Span destination) { Debug.Assert(destination.Length == 20); Span 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 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 < w.Length; i++) { BinaryPrimitives.WriteUInt32BigEndian(destination, w[i]); destination = destination.Slice(4); } } /// /// Call Start() to initialize the hash object. /// public void Start() { Start(_w ??= new uint[85]); _length = 0; _pos = 0; } private static void Start(Span w) { w[80] = 0x67452301; w[81] = 0xEFCDAB89; w[82] = 0x98BADCFE; w[83] = 0x10325476; w[84] = 0xC3D2E1F0; } /// /// Adds an input byte to the hash. /// /// Data to include in the hash. public void Append(byte input) { int idx = _pos >> 2; _w[idx] = (_w[idx] << 8) | input; if (64 == ++_pos) { Drain(); } } /// /// Adds input bytes to the hash. /// /// /// Data to include in the hash. Must not be null. /// public void Append(ReadOnlySpan input) { foreach (byte b in input) { Append(b); } } /// /// Retrieves the hash value. /// Note that after calling function, the hash object should /// be considered uninitialized. Subsequent calls to Append or /// Finish will produce useless results. Call Start() to /// reinitialize. /// /// /// 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. /// public void Finish(Span output) { Debug.Assert(output.Length == 20); long l = _length + 8 * _pos; Append(0x80); while (_pos != 56) { Append(0x00); } unchecked { Append((byte)(l >> 56)); Append((byte)(l >> 48)); Append((byte)(l >> 40)); Append((byte)(l >> 32)); Append((byte)(l >> 24)); Append((byte)(l >> 16)); Append((byte)(l >> 8)); Append((byte)l); } for (int i = 80; i < _w.Length; i++) { BinaryPrimitives.WriteUInt32BigEndian(output, _w[i]); output = output.Slice(4); } } /// /// Called when pos reaches 64. /// private void Drain() { Drain(_w); _length += 512; // 64 bytes == 512 bits _pos = 0; } private static void Drain(Span w) { unchecked { 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); } 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++) { 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; } 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; } 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; } 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; } w[80] += a; w[81] += b; w[82] += c; w[83] += d; w[84] += e; } } } }