diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/.editorconfig b/src/core/IronPython.Modules/hashlib/BouncyCastle/.editorconfig new file mode 100644 index 000000000..ace22db56 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +trim_trailing_whitespace = false +insert_final_newline = false + +[*.cs] +# leave upstream code formatting and style intact +generated_code = true + +dotnet_diagnostic.CS1574.severity = none # CS1574: XML comment has cref attribute that could not be resolved diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2bDigest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2bDigest.cs new file mode 100644 index 000000000..e9d21d3cd --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2bDigest.cs @@ -0,0 +1,663 @@ +using System; +using System.Diagnostics; + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /* + * The BLAKE2 cryptographic hash function was designed by Jean-Philippe Aumasson, Samuel Neves, Zooko + * Wilcox-O'Hearn, and Christian Winnerlein. + * + * Reference Implementation and Description can be found at: https://blake2.net/ + * RFC: https://tools.ietf.org/html/rfc7693 + * + * This implementation does not support the Tree Hashing Mode. + * + * For unkeyed hashing, developers adapting BLAKE2 to ASN.1-based message formats SHOULD use the OID tree at: + * x = 1.3.6.1.4.1.1722.12.2. + * + * +---------------+--------+-----------+------+------------+ + * | Algorithm | Target | Collision | Hash | Hash ASN.1 | + * | Identifier | Arch | Security | nn | OID Suffix | + * +---------------+--------+-----------+------+------------+ + * | id-blake2b160 | 64-bit | 2**80 | 20 | x.1.20 | + * | id-blake2b256 | 64-bit | 2**128 | 32 | x.1.32 | + * | id-blake2b384 | 64-bit | 2**192 | 48 | x.1.48 | + * | id-blake2b512 | 64-bit | 2**256 | 64 | x.1.64 | + * +---------------+--------+-----------+------+------------+ + */ + + /// + /// Implementation of the cryptographic hash function BLAKE2b. BLAKE2b is optimized for 64-bit platforms and + /// produces digests of any size between 1 and 64 bytes. + /// + /// + /// + /// BLAKE2b offers a built-in keying mechanism to be used directly for authentication ("Prefix-MAC") rather than an + /// HMAC construction. + /// + /// + /// BLAKE2b offers built-in support for a salt for randomized hashing and a personal string for defining a unique + /// hash function for each application. + /// + /// + internal sealed class Blake2bDigest + : IDigest + { + /* + * BLAKE2b Initialization Vector (the same as SHA-512 IV). + * + * Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19. + */ + private static readonly ulong[] IV = + { + 0x6a09e667f3bcc908UL, 0xbb67ae8584caa73bUL, 0x3c6ef372fe94f82bUL, 0xa54ff53a5f1d36f1UL, + 0x510e527fade682d1UL, 0x9b05688c2b3e6c1fUL, 0x1f83d9abfb41bd6bUL, 0x5be0cd19137e2179UL + }; + + // Message word permutations + private static readonly byte[] Sigma = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + }; + + private const int ROUNDS = 12; // to use for Catenas H' + private const int BLOCK_LENGTH_BYTES = 128;// bytes + + private readonly ulong[] chainValue = new ulong[8]; // State vector, in the BLAKE2 paper it is called h + + // Whenever this buffer overflows, it will be processed in the Compress() function. + // For performance issues, long messages will not use this buffer. + private readonly byte[] buffer = new byte[BLOCK_LENGTH_BYTES]; + + // General parameters: + private int digestLength = 64; // 1 - 64 bytes + private byte[] m_salt = null; + private byte[] m_personalization = null; + private byte[] m_key = null; + + /* + * Tree hashing parameters; because this class does not implement the Tree Hashing Mode, these parameters can be + * treated as constants (see Init() function). + */ + //private int fanout = 1; // 0 - 255 + //private int depth = 1; // 1 - 255 + //private int leafLength = 0; + //private long nodeOffset = 0L; + //private int nodeDepth = 0; + //private int innerHashLength = 0; + //private bool isLastNode = false; + + // Position of last inserted byte: + private int bufferPos = 0; // a value from 0 up to BLOCK_LENGTH_BYTES + + private ulong t0 = 0UL; // holds last significant bits, counter (counts bytes) + private ulong t1 = 0UL; // counter: Length up to 2^128 are supported + private ulong f0 = 0UL; // finalization flag, for last block: ~0UL + + // For Tree Hashing Mode, not used here: + //private ulong f1 = 0UL; // finalization flag, for last node: ~0UL + + /// + /// Initializes a new instance of . + /// + public Blake2bDigest() + : this(512) + { + } + + /// + /// Constructs a new instance of from another ./>. + /// + /// The original instance of that is copied. + public Blake2bDigest(Blake2bDigest digest) + { + Array.Copy(digest.chainValue, 0, chainValue, 0, 8); + Array.Copy(digest.buffer, 0, buffer, 0, BLOCK_LENGTH_BYTES); + + this.bufferPos = digest.bufferPos; + this.m_key = Arrays.Clone(digest.m_key); + this.digestLength = digest.digestLength; + this.m_personalization = Arrays.Clone(digest.m_personalization); + this.m_salt = Arrays.Clone(digest.m_salt); + this.t0 = digest.t0; + this.t1 = digest.t1; + this.f0 = digest.f0; + } + + /// + /// Initializes a new instance of with a given digest size. + /// + /// Digest size in bits. + /// + public Blake2bDigest(int digestSize) + { + if (digestSize < 8 || digestSize > 512 || digestSize % 8 != 0) + throw new ArgumentException("BLAKE2b digest bit length must be a multiple of 8 and not greater than 512"); + + this.digestLength = digestSize / 8; + + Init(); + } + + /// + /// + /// Initializes a new instance of with a key. + /// + /// + /// BLAKE2b for authentication ("Prefix-MAC mode"). + /// After calling the method, the key will + /// remain to be used for further computations of this instance. + /// The key can be cleared using the method. + /// + /// A key up to 64 bytes or null. + /// + public Blake2bDigest(byte[] key) + { + this.digestLength = 64; + + if (!Arrays.IsNullOrEmpty(key)) + { + if (key.Length > 64) + throw new ArgumentException("Keys > 64 bytes are not supported", nameof(key)); + + m_key = Arrays.CopyBuffer(key); + } + + Init(); + } + + /// + /// + /// Initializes a new instance of with a key, required digest length (in bytes), salt and personalization. + /// + /// + /// After calling the method, the key, the salt and the personalization + /// will remain and might be used for further computations with this instance. + /// The key can be overwritten using the method, the salt (pepper) + /// can be overwritten using the method. + /// + /// A key up to 64 bytes or null. + /// Digest length from 1 to 64 bytes. + /// A 16 bytes or nullable salt. + /// A 16 bytes or null personalization. + /// + public Blake2bDigest(byte[] key, int digestLength, byte[] salt, byte[] personalization) + { + if (digestLength < 1 || digestLength > 64) + throw new ArgumentException("Invalid digest length (required: 1 - 64)"); + + this.digestLength = digestLength; + + if (!Arrays.IsNullOrEmpty(key)) + { + if (key.Length > 64) + throw new ArgumentException("Keys > 64 bytes are not supported", nameof(key)); + + m_key = Arrays.CopyBuffer(key); + } + + if (salt != null) + { + if (salt.Length != 16) + throw new ArgumentException("salt length must be exactly 16 bytes", nameof(salt)); + + m_salt = Arrays.CopyBuffer(salt); + } + + if (personalization != null) + { + if (personalization.Length != 16) + throw new ArgumentException("personalization length must be exactly 16 bytes", + nameof(personalization)); + + m_personalization = Arrays.CopyBuffer(personalization); + } + + Init(); + } + + private void Init() + { + int keyLength = 0; + if (m_key != null) + { + keyLength = m_key.Length; + Array.Copy(m_key, 0, buffer, 0, keyLength); + //Arrays.Fill(buffer, keyLength, BLOCK_LENGTH_BYTES, 0); + bufferPos = BLOCK_LENGTH_BYTES; // zero padding + } + + chainValue[0] = IV[0] ^ (ulong)(digestLength | (keyLength << 8) | 0x1010000); + // 0x1010000 = ((fanout << 16) | (depth << 24) | (leafLength << 32)); + // with fanout = 1; depth = 0; leafLength = 0; + chainValue[1] = IV[1];// ^ nodeOffset; with nodeOffset = 0; + chainValue[2] = IV[2];// ^ ( nodeDepth | (innerHashLength << 8) ); + // with nodeDepth = 0; innerHashLength = 0; + chainValue[3] = IV[3]; + + chainValue[4] = IV[4]; + chainValue[5] = IV[5]; + if (m_salt != null) + { + chainValue[4] ^= Pack.LE_To_UInt64(m_salt, 0); + chainValue[5] ^= Pack.LE_To_UInt64(m_salt, 8); + } + + chainValue[6] = IV[6]; + chainValue[7] = IV[7]; + if (m_personalization != null) + { + chainValue[6] ^= Pack.LE_To_UInt64(m_personalization, 0); + chainValue[7] ^= Pack.LE_To_UInt64(m_personalization, 8); + } + } + + /// + public void Update(byte b) + { + // process the buffer if full else add to buffer: + int remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength == 0) + { + // full buffer + IncrementCounter(BLOCK_LENGTH_BYTES); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Compress(buffer); +#else + Compress(buffer, 0); +#endif + Array.Clear(buffer, 0, buffer.Length);// clear buffer + buffer[0] = b; + bufferPos = 1; + } + else + { + buffer[bufferPos++] = b; + } + } + + /// + public void BlockUpdate(byte[] message, int offset, int len) + { + if (message == null || len == 0) + return; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + BlockUpdate(message.AsSpan(offset, len)); +#else + int remainingLength = 0; // left bytes of buffer + + if (bufferPos != 0) + { + // commenced, incomplete buffer + + // complete the buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength >= len) + { + Array.Copy(message, offset, buffer, bufferPos, len); + bufferPos += len; + return; + } + + // full buffer + at least 1 byte + Array.Copy(message, offset, buffer, bufferPos, remainingLength); + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(buffer, 0); + bufferPos = 0; + Array.Clear(buffer, 0, buffer.Length);// clear buffer + } + + // process blocks except last block (also if last block is full) + int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES; + int messagePos = offset + remainingLength; + while (messagePos < blockWiseLastPos) + { + // block wise 128 bytes without buffer: + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(message, messagePos); + messagePos += BLOCK_LENGTH_BYTES; + } + + // fill the buffer with left bytes, this might be a full block + Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos); + bufferPos += offset + len - messagePos; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + public void BlockUpdate(ReadOnlySpan input) + { + if (input.IsEmpty) + return; + + int remainingLength = 0; // left bytes of buffer + + if (bufferPos != 0) + { + // commenced, incomplete buffer + + // complete the buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength >= input.Length) + { + input.CopyTo(buffer.AsSpan(bufferPos)); + bufferPos += input.Length; + return; + } + + // full buffer + at least 1 byte + input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos)); + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(buffer); + bufferPos = 0; + Array.Clear(buffer, 0, buffer.Length);// clear buffer + } + + // process blocks except last block (also if last block is full) + int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES; + int messagePos = remainingLength; + while (messagePos < blockWiseLastPos) + { + // block wise 128 bytes without buffer: + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(input[messagePos..]); + messagePos += BLOCK_LENGTH_BYTES; + } + + // fill the buffer with left bytes, this might be a full block + input[messagePos..].CopyTo(buffer.AsSpan()); + bufferPos += input.Length - messagePos; + } +#endif + + /// Close the digest, producing the final digest value. + /// + /// The call leaves the digest reset. + /// Key, salt and personal string remain. + /// + /// The byte array the digest is to be copied into. + /// The offset into the byte array the digest is to start at. + /// The number of bytes written. + public int DoFinal(byte[] output, int outOffset) + { + Check.OutputLength(output, outOffset, digestLength, "output buffer too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return DoFinal(output.AsSpan(outOffset)); +#else + f0 = 0xFFFFFFFFFFFFFFFFUL; + //if (isLastNode) + //{ + // f1 = 0xFFFFFFFFFFFFFFFFUL; + //} + + if (bufferPos > 0) + { + IncrementCounter(bufferPos); + } + + Compress(buffer, 0); + + int full = digestLength >> 3, partial = digestLength & 7; + Pack.UInt64_To_LE(chainValue, 0, full, output, outOffset); + if (partial > 0) + { + Pack.UInt64_To_LE_Low(chainValue[full], output, outOffset + digestLength - partial, partial); + } + + Reset(); + + return digestLength; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// Close the digest, producing the final digest value. + /// + /// The call leaves the digest reset. + /// Key, salt and personal string remain. + /// + /// The span the digest is to be copied into. + /// The number of bytes written. + public int DoFinal(Span output) + { + Check.OutputLength(output, digestLength, "output buffer too short"); + + f0 = 0xFFFFFFFFFFFFFFFFUL; + //if (isLastNode) + //{ + // f1 = 0xFFFFFFFFFFFFFFFFUL; + //} + + if (bufferPos > 0) + { + IncrementCounter(bufferPos); + } + + Compress(buffer); + + int full = digestLength >> 3, partial = digestLength & 7; + Pack.UInt64_To_LE(chainValue.AsSpan(0, full), output); + if (partial > 0) + { + Pack.UInt64_To_LE_Low(chainValue[full], output.Slice(digestLength - partial, partial)); + } + + Reset(); + + return digestLength; + } +#endif + + /// + /// Reset the digest back to it's initial state. + /// The key, the salt and the personalization will remain for further computations. + /// + public void Reset() + { + bufferPos = 0; + f0 = 0UL; + t0 = 0UL; + t1 = 0UL; + + Array.Clear(buffer, 0, buffer.Length); + + Init(); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Compress(ReadOnlySpan message) + { +#if NETCOREAPP3_0_OR_GREATER + if (Blake2b_X86.IsSupported) + { + Blake2b_X86.Compress(chainValue, IV, t0, t1, f0, message); + return; + } +#endif + + Span m = stackalloc ulong[16]; + Pack.LE_To_UInt64(message, m); + + ulong v0 = chainValue[0]; + ulong v1 = chainValue[1]; + ulong v2 = chainValue[2]; + ulong v3 = chainValue[3]; + ulong v4 = chainValue[4]; + ulong v5 = chainValue[5]; + ulong v6 = chainValue[6]; + ulong v7 = chainValue[7]; + ulong v8 = IV[0]; + ulong v9 = IV[1]; + ulong va = IV[2]; + ulong vb = IV[3]; + ulong vc = IV[4] ^ t0; + ulong vd = IV[5] ^ t1; + ulong ve = IV[6] ^ f0; + ulong vf = IV[7]; // ^ f1, with f1 == 0 + + for (int round = 0; round < ROUNDS; round++) + { + int pos = round * 16; + + // Apply G to columns of internal state + G(m[Sigma[pos + 0]], m[Sigma[pos + 1]], ref v0, ref v4, ref v8, ref vc); + G(m[Sigma[pos + 2]], m[Sigma[pos + 3]], ref v1, ref v5, ref v9, ref vd); + G(m[Sigma[pos + 4]], m[Sigma[pos + 5]], ref v2, ref v6, ref va, ref ve); + G(m[Sigma[pos + 6]], m[Sigma[pos + 7]], ref v3, ref v7, ref vb, ref vf); + + // Apply G to diagonals of internal state + G(m[Sigma[pos + 8]], m[Sigma[pos + 9]], ref v0, ref v5, ref va, ref vf); + G(m[Sigma[pos + 10]], m[Sigma[pos + 11]], ref v1, ref v6, ref vb, ref vc); + G(m[Sigma[pos + 12]], m[Sigma[pos + 13]], ref v2, ref v7, ref v8, ref vd); + G(m[Sigma[pos + 14]], m[Sigma[pos + 15]], ref v3, ref v4, ref v9, ref ve); + } + + chainValue[0] ^= v0 ^ v8; + chainValue[1] ^= v1 ^ v9; + chainValue[2] ^= v2 ^ va; + chainValue[3] ^= v3 ^ vb; + chainValue[4] ^= v4 ^ vc; + chainValue[5] ^= v5 ^ vd; + chainValue[6] ^= v6 ^ ve; + chainValue[7] ^= v7 ^ vf; + } +#else + private void Compress(byte[] message, int messagePos) + { + ulong[] m = new ulong[16]; + Pack.LE_To_UInt64(message, messagePos, m); + + ulong v0 = chainValue[0]; + ulong v1 = chainValue[1]; + ulong v2 = chainValue[2]; + ulong v3 = chainValue[3]; + ulong v4 = chainValue[4]; + ulong v5 = chainValue[5]; + ulong v6 = chainValue[6]; + ulong v7 = chainValue[7]; + ulong v8 = IV[0]; + ulong v9 = IV[1]; + ulong va = IV[2]; + ulong vb = IV[3]; + ulong vc = IV[4] ^ t0; + ulong vd = IV[5] ^ t1; + ulong ve = IV[6] ^ f0; + ulong vf = IV[7]; // ^ f1, with f1 == 0 + + for (int round = 0; round < ROUNDS; round++) + { + int pos = round * 16; + + // Apply G to columns of internal state + G(m[Sigma[pos + 0]], m[Sigma[pos + 1]], ref v0, ref v4, ref v8, ref vc); + G(m[Sigma[pos + 2]], m[Sigma[pos + 3]], ref v1, ref v5, ref v9, ref vd); + G(m[Sigma[pos + 4]], m[Sigma[pos + 5]], ref v2, ref v6, ref va, ref ve); + G(m[Sigma[pos + 6]], m[Sigma[pos + 7]], ref v3, ref v7, ref vb, ref vf); + + // Apply G to diagonals of internal state + G(m[Sigma[pos + 8]], m[Sigma[pos + 9]], ref v0, ref v5, ref va, ref vf); + G(m[Sigma[pos + 10]], m[Sigma[pos + 11]], ref v1, ref v6, ref vb, ref vc); + G(m[Sigma[pos + 12]], m[Sigma[pos + 13]], ref v2, ref v7, ref v8, ref vd); + G(m[Sigma[pos + 14]], m[Sigma[pos + 15]], ref v3, ref v4, ref v9, ref ve); + } + + chainValue[0] ^= v0 ^ v8; + chainValue[1] ^= v1 ^ v9; + chainValue[2] ^= v2 ^ va; + chainValue[3] ^= v3 ^ vb; + chainValue[4] ^= v4 ^ vc; + chainValue[5] ^= v5 ^ vd; + chainValue[6] ^= v6 ^ ve; + chainValue[7] ^= v7 ^ vf; + } +#endif + + /// + public string AlgorithmName => "BLAKE2b"; + + /// + public int GetDigestSize() => digestLength; + + /// + /// Return the size in bytes of the internal buffer the digest applies it's compression + /// function to. + /// + /// The byte length of the digests internal buffer. + public int GetByteLength() => BLOCK_LENGTH_BYTES; + + /// + /// Clears the key. + /// + public void ClearKey() + { + if (m_key != null) + { + Array.Clear(m_key, 0, m_key.Length); + Array.Clear(buffer, 0, buffer.Length); + } + } + + /// + /// Clears the salt (pepper). + /// + public void ClearSalt() + { + if (m_salt != null) + { + Array.Clear(m_salt, 0, m_salt.Length); + } + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void IncrementCounter(int count) + { + Debug.Assert(count > 0); + + ulong count64 = (ulong)count; + t0 += count64; + if (t0 < count64) + { + ++t1; + } + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void G(ulong m1, ulong m2, ref ulong a, ref ulong b, ref ulong c, ref ulong d) + { + a += b + m1; + d = Longs.RotateRight(d ^ a, 32); + c += d; + b = Longs.RotateRight(b ^ c, 24); + + a += b + m2; + d = Longs.RotateRight(d ^ a, 16); + c += d; + b = Longs.RotateRight(b ^ c, 63); + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2b_X86.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2b_X86.cs new file mode 100644 index 000000000..efb283673 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2b_X86.cs @@ -0,0 +1,399 @@ +#if NETCOREAPP3_0_OR_GREATER +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Org.BouncyCastle.Crypto.Digests +{ + // License from the original code created by Clinton Ingram (saucecontrol) for Blake2Fast + // at https://github.com/saucecontrol/Blake2Fast. The code has been copied and modified. + + // The MIT License + + // Copyright(c) 2018-2021 Clinton Ingram + + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + // THE SOFTWARE. + + internal static class Blake2b_X86 + { + internal static bool IsSupported => + Org.BouncyCastle.Runtime.Intrinsics.X86.Avx2.IsEnabled && + Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPackedLittleEndian; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Compress(Span hashBuffer, ReadOnlySpan blakeIV, ulong t0, ulong t1, ulong f0, + ReadOnlySpan message) + { + if (!IsSupported) + throw new PlatformNotSupportedException(nameof(Blake2b_X86)); + + Debug.Assert(hashBuffer.Length >= 8); + Debug.Assert(blakeIV.Length >= 8); + Debug.Assert(message.Length >= 128); + + var hashBytes = MemoryMarshal.AsBytes(hashBuffer); + var ivBytes = MemoryMarshal.AsBytes(blakeIV); + + var t_0 = Vector256.Create(t0, t1, f0, 0); + + var row1 = MemoryMarshal.Read>(hashBytes); + var row2 = MemoryMarshal.Read>(hashBytes[32..]); + var row3 = MemoryMarshal.Read>(ivBytes); + var row4 = MemoryMarshal.Read>(ivBytes[32..]); + row4 = Avx2.Xor(row4, t_0); + + var orig_1 = row1; + var orig_2 = row2; + + Perform12Rounds(message, ref row1, ref row2, ref row3, ref row4); + + row1 = Avx2.Xor(row1, row3); + row2 = Avx2.Xor(row2, row4); + row1 = Avx2.Xor(row1, orig_1); + row2 = Avx2.Xor(row2, orig_2); + +#if NET8_0_OR_GREATER + MemoryMarshal.Write(hashBytes, in row1); + MemoryMarshal.Write(hashBytes[32..], in row2); +#else + MemoryMarshal.Write(hashBytes, ref row1); + MemoryMarshal.Write(hashBytes[32..], ref row2); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Perform12Rounds(ReadOnlySpan m, ref Vector256 row1, ref Vector256 row2, ref Vector256 row3, ref Vector256 row4) + { +#region Rounds + //ROUND 1 + var m0 = Broadcast128ToVector256(m); + var m1 = Broadcast128ToVector256(m[16..]); + var m2 = Broadcast128ToVector256(m[32..]); + var m3 = Broadcast128ToVector256(m[48..]); + + var t0 = Avx2.UnpackLow(m0, m1); + var t1 = Avx2.UnpackLow(m2, m3); + var b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m0, m1); + t1 = Avx2.UnpackHigh(m2, m3); + var b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + var m4 = Broadcast128ToVector256(m[64..]); + var m5 = Broadcast128ToVector256(m[80..]); + var m6 = Broadcast128ToVector256(m[96..]); + var m7 = Broadcast128ToVector256(m[112..]); + + t0 = Avx2.UnpackLow(m7, m4); + t1 = Avx2.UnpackLow(m5, m6); + var b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m7, m4); + t1 = Avx2.UnpackHigh(m5, m6); + var b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 2 + t0 = Avx2.UnpackLow(m7, m2); + t1 = Avx2.UnpackHigh(m4, m6); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m5, m4); + t1 = Avx2.AlignRight(m3, m7, 8); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m2, m0); + t1 = Avx2.Blend(m0.AsUInt32(), m5.AsUInt32(), 0b_1100_1100).AsUInt64(); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m6, m1, 8); + t1 = Avx2.Blend(m1.AsUInt32(), m3.AsUInt32(), 0b_1100_1100).AsUInt64(); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 3 + t0 = Avx2.AlignRight(m6, m5, 8); + t1 = Avx2.UnpackHigh(m2, m7); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m4, m0); + t1 = Avx2.Blend(m1.AsUInt32(), m6.AsUInt32(), 0b_1100_1100).AsUInt64(); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m5, m4, 8); + t1 = Avx2.UnpackHigh(m1, m3); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m2, m7); + t1 = Avx2.Blend(m3.AsUInt32(), m0.AsUInt32(), 0b_1100_1100).AsUInt64(); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 4 + t0 = Avx2.UnpackHigh(m3, m1); + t1 = Avx2.UnpackHigh(m6, m5); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m4, m0); + t1 = Avx2.UnpackLow(m6, m7); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m1, m7, 8); + t1 = Avx2.Shuffle(m2.AsUInt32(), 0b_01_00_11_10).AsUInt64(); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m4, m3); + t1 = Avx2.UnpackLow(m5, m0); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 5 + t0 = Avx2.UnpackHigh(m4, m2); + t1 = Avx2.UnpackLow(m1, m5); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.Blend(m0.AsUInt32(), m3.AsUInt32(), 0b_1100_1100).AsUInt64(); + t1 = Avx2.Blend(m2.AsUInt32(), m7.AsUInt32(), 0b_1100_1100).AsUInt64(); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m7, m1, 8); + t1 = Avx2.AlignRight(m3, m5, 8); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m6, m0); + t1 = Avx2.UnpackLow(m6, m4); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 6 + t0 = Avx2.UnpackLow(m1, m3); + t1 = Avx2.UnpackLow(m0, m4); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m6, m5); + t1 = Avx2.UnpackHigh(m5, m1); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m2, m0, 8); + t1 = Avx2.UnpackHigh(m3, m7); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m4, m6); + t1 = Avx2.AlignRight(m7, m2, 8); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 7 + t0 = Avx2.Blend(m6.AsUInt32(), m0.AsUInt32(), 0b_1100_1100).AsUInt64(); + t1 = Avx2.UnpackLow(m7, m2); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m2, m7); + t1 = Avx2.AlignRight(m5, m6, 8); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m4, m0); + t1 = Avx2.Blend(m3.AsUInt32(), m4.AsUInt32(), 0b_1100_1100).AsUInt64(); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m5, m3); + t1 = Avx2.Shuffle(m1.AsUInt32(), 0b_01_00_11_10).AsUInt64(); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 8 + t0 = Avx2.UnpackHigh(m6, m3); + t1 = Avx2.Blend(m6.AsUInt32(), m1.AsUInt32(), 0b_1100_1100).AsUInt64(); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m7, m5, 8); + t1 = Avx2.UnpackHigh(m0, m4); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.Blend(m1.AsUInt32(), m2.AsUInt32(), 0b_1100_1100).AsUInt64(); + t1 = Avx2.AlignRight(m4, m7, 8); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m5, m0); + t1 = Avx2.UnpackLow(m2, m3); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 9 + t0 = Avx2.UnpackLow(m3, m7); + t1 = Avx2.AlignRight(m0, m5, 8); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m7, m4); + t1 = Avx2.AlignRight(m4, m1, 8); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m5, m6); + t1 = Avx2.UnpackHigh(m6, m0); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m1, m2, 8); + t1 = Avx2.AlignRight(m2, m3, 8); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 10 + t0 = Avx2.UnpackLow(m5, m4); + t1 = Avx2.UnpackHigh(m3, m0); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m1, m2); + t1 = Avx2.Blend(m3.AsUInt32(), m2.AsUInt32(), 0b_1100_1100).AsUInt64(); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m6, m7); + t1 = Avx2.UnpackHigh(m4, m1); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.Blend(m0.AsUInt32(), m5.AsUInt32(), 0b_1100_1100).AsUInt64(); + t1 = Avx2.UnpackLow(m7, m6); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 11 + t0 = Avx2.UnpackLow(m0, m1); + t1 = Avx2.UnpackLow(m2, m3); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m0, m1); + t1 = Avx2.UnpackHigh(m2, m3); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m7, m4); + t1 = Avx2.UnpackLow(m5, m6); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m7, m4); + t1 = Avx2.UnpackHigh(m5, m6); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 12 + t0 = Avx2.UnpackLow(m7, m2); + t1 = Avx2.UnpackHigh(m4, m6); + b1 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackLow(m5, m4); + t1 = Avx2.AlignRight(m3, m7, 8); + b2 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.UnpackHigh(m2, m0); + t1 = Avx2.Blend(m0.AsUInt32(), m5.AsUInt32(), 0b_1100_1100).AsUInt64(); + b3 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + t0 = Avx2.AlignRight(m6, m1, 8); + t1 = Avx2.Blend(m1.AsUInt32(), m3.AsUInt32(), 0b_1100_1100).AsUInt64(); + b4 = Avx2.Blend(t0.AsUInt32(), t1.AsUInt32(), 0b_1111_0000).AsUInt64(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); +#endregion + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Round(ref Vector256 row1, ref Vector256 row2, ref Vector256 row3, + ref Vector256 row4, Vector256 b1, Vector256 b2, Vector256 b3, + Vector256 b4) + { + Vector256 r24 = Vector256.Create( + (byte)3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10, 3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10); + Vector256 r16 = Vector256.Create( + (byte)2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9, 2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9); + + G1(r24, ref row1, ref row2, ref row3, ref row4, b1); + G2(r16, ref row1, ref row2, ref row3, ref row4, b2); + + Diagonalize(ref row1, ref row3, ref row4); + + G1(r24, ref row1, ref row2, ref row3, ref row4, b3); + G2(r16, ref row1, ref row2, ref row3, ref row4, b4); + + Diagonalize(ref row3, ref row1, ref row4); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Diagonalize(ref Vector256 row1, ref Vector256 row3, ref Vector256 row4) + { + // +-------------------+ +-------------------+ + // | 0 | 1 | 2 | 3 | | 3 | 0 | 1 | 2 | + // +-------------------+ +-------------------+ + // | 8 | 9 | 10 | 11 | ---> | 9 | 10 | 11 | 8 | + // +-------------------+ +-------------------+ + // | 12 | 13 | 14 | 15 | | 14 | 15 | 12 | 13 | + // +-------------------+ +-------------------+ + + row1 = Avx2.Permute4x64(row1, 0b_10_01_00_11); + row3 = Avx2.Permute4x64(row3, 0b_00_11_10_01); + row4 = Avx2.Permute4x64(row4, 0b_01_00_11_10); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void G1(Vector256 r24, ref Vector256 row1, ref Vector256 row2, + ref Vector256 row3, ref Vector256 row4, Vector256 b0) + { + row1 = Avx2.Add(Avx2.Add(row1, b0), row2); + row4 = Avx2.Xor(row4, row1); + row4 = Avx2.Shuffle(row4.AsUInt32(), 0b_10_11_00_01).AsUInt64(); + + row3 = Avx2.Add(row3, row4); + row2 = Avx2.Xor(row2, row3); + row2 = Avx2.Shuffle(row2.AsByte(), r24).AsUInt64(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void G2(Vector256 r16, ref Vector256 row1, ref Vector256 row2, + ref Vector256 row3, ref Vector256 row4, Vector256 b0) + { + row1 = Avx2.Add(Avx2.Add(row1, b0), row2); + row4 = Avx2.Xor(row4, row1); + row4 = Avx2.Shuffle(row4.AsByte(), r16).AsUInt64(); + + row3 = Avx2.Add(row3, row4); + row2 = Avx2.Xor(row2, row3); + row2 = Avx2.Xor(Avx2.ShiftRightLogical(row2, 63), Avx2.Add(row2, row2)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector256 Broadcast128ToVector256(ReadOnlySpan source) where T : struct + { + var vector = MemoryMarshal.Read>(source); + Vector256 result = vector.ToVector256Unsafe(); + return result.WithUpper(vector); + } + } +} +#endif diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2sDigest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2sDigest.cs new file mode 100644 index 000000000..3d7c2bf3f --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2sDigest.cs @@ -0,0 +1,688 @@ +using System; +using System.Diagnostics; + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /* + * The BLAKE2 cryptographic hash function was designed by Jean-Philippe Aumasson, Samuel Neves, Zooko + * Wilcox-O'Hearn, and Christian Winnerlein. + * + * Reference Implementation and Description can be found at: https://blake2.net/ + * RFC: https://tools.ietf.org/html/rfc7693 + * + * This implementation does not support the Tree Hashing Mode. + * + * For unkeyed hashing, developers adapting BLAKE2 to ASN.1-based message formats SHOULD use the OID tree at: + * x = 1.3.6.1.4.1.1722.12.2. + * + * +---------------+--------+-----------+------+------------+ + * | Algorithm | Target | Collision | Hash | Hash ASN.1 | + * | Identifier | Arch | Security | nn | OID Suffix | + * +---------------+--------+-----------+------+------------+ + * | id-blake2s128 | 32-bit | 2**64 | 16 | x.2.4 | + * | id-blake2s160 | 32-bit | 2**80 | 20 | x.2.5 | + * | id-blake2s224 | 32-bit | 2**112 | 28 | x.2.7 | + * | id-blake2s256 | 32-bit | 2**128 | 32 | x.2.8 | + * +---------------+--------+-----------+------+------------+ + */ + + /// + /// Implementation of the cryptographic hash function BLAKE2s. BLAKE2s is optimized for 32-bit platforms and + /// produces digests of any size between 1 and 32 bytes. + /// + /// + /// + /// BLAKE2s offers a built-in keying mechanism to be used directly for authentication ("Prefix-MAC") rather than an + /// HMAC construction. + /// + /// + /// BLAKE2s offers built-in support for a salt for randomized hashing and a personal string for defining a unique + /// hash function for each application. + /// + /// + internal sealed class Blake2sDigest + : IDigest + { + /* + * BLAKE2s Initialization Vector (the same as SHA-256 IV). + * + * Produced from the square root of primes 2, 3, 5, 7, 11, 13, 17, 19. + */ + private static readonly uint[] IV = + { + 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU, 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U + }; + + // Message word permutations + private static readonly byte[] Sigma = + { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3, + 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4, + 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8, + 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13, + 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9, + 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11, + 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10, + 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5, + 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0, + }; + + private const int ROUNDS = 10; // to use for Catenas H' + private const int BLOCK_LENGTH_BYTES = 64;// bytes + + private readonly uint[] chainValue = new uint[8]; // State vector, in the BLAKE2 paper it is called h + + // Whenever this buffer overflows, it will be processed in the Compress() function. + // For performance issues, long messages will not use this buffer. + private readonly byte[] buffer = new byte[BLOCK_LENGTH_BYTES]; + + // General parameters: + private int digestLength = 32; // 1 - 32 bytes + private byte[] m_salt = null; + private byte[] m_personalization = null; + private byte[] m_key = null; + + /* + * Tree hashing parameters; the Tree Hashing Mode is not supported but these are used for the XOF + * implementation. + */ + private int fanout = 1; // 0 - 255 + private int depth = 1; // 1 - 255 + private int leafLength = 0; + private long nodeOffset = 0L; + private int nodeDepth = 0; + private int innerHashLength = 0; + //private bool isLastNode = false; + + // Position of last inserted byte: + private int bufferPos = 0; // a value from 0 up to BLOCK_LENGTH_BYTES + + private uint t0 = 0U; // holds last significant bits, counter (counts bytes) + private uint t1 = 0U; // counter: Length up to 2^64 are supported + private uint f0 = 0U; // finalization flag, for last block: ~0U + + // For Tree Hashing Mode, not used here: + //private uint f1 = 0U; // finalization flag, for last node: ~0U + + /// + /// Initializes a new instance of . + /// + public Blake2sDigest() + : this(256) + { + } + + /// + /// Constructs a new instance of from another ./>. + /// + /// The original instance of that is copied. + public Blake2sDigest(Blake2sDigest digest) + { + Array.Copy(digest.chainValue, 0, chainValue, 0, 8); + Array.Copy(digest.buffer, 0, buffer, 0, BLOCK_LENGTH_BYTES); + + this.bufferPos = digest.bufferPos; + this.m_key = Arrays.Clone(digest.m_key); + this.digestLength = digest.digestLength; + this.t0 = digest.t0; + this.t1 = digest.t1; + this.f0 = digest.f0; + this.m_salt = Arrays.Clone(digest.m_salt); + this.m_personalization = Arrays.Clone(digest.m_personalization); + this.fanout = digest.fanout; + this.depth = digest.depth; + this.leafLength = digest.leafLength; + this.nodeOffset = digest.nodeOffset; + this.nodeDepth = digest.nodeDepth; + this.innerHashLength = digest.innerHashLength; + } + + /// + /// Initializes a new instance of with a given digest size. + /// + /// Digest size in bits. + /// + public Blake2sDigest(int digestBits) + { + if (digestBits < 8 || digestBits > 256 || digestBits % 8 != 0) + throw new ArgumentException("BLAKE2s digest bit length must be a multiple of 8 and not greater than 256"); + + this.digestLength = digestBits / 8; + + Init(); + } + + /// + /// + /// Initializes a new instance of with a key. + /// + /// + /// BLAKE2s for authentication ("Prefix-MAC mode"). + /// After calling the method, the key will + /// remain to be used for further computations of this instance. + /// The key can be cleared using the method. + /// + /// A key up to 32 bytes or null. + /// + public Blake2sDigest(byte[] key) + { + this.digestLength = 32; + + if (!Arrays.IsNullOrEmpty(key)) + { + if (key.Length > 32) + throw new ArgumentException("Keys > 32 bytes are not supported", nameof(key)); + + m_key = Arrays.CopyBuffer(key); + } + + Init(); + } + + /// + /// + /// Initializes a new instance of with a key, required digest length (in bytes), salt and personalization. + /// + /// + /// After calling the method, the key, the salt and the personalization + /// will remain and might be used for further computations with this instance. + /// The key can be overwritten using the method, the salt (pepper) + /// can be overwritten using the method. + /// + /// A key up to 32 bytes or null. + /// Digest length from 1 to 32 bytes. + /// A 8 bytes or nullable salt. + /// A 8 bytes or null personalization. + /// + public Blake2sDigest(byte[] key, int digestBytes, byte[] salt, byte[] personalization) + : this(digestBytes, key, salt, personalization, offset: 0L) + { + } + + // XOF root hash parameters + internal Blake2sDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization, long offset) + { + if (digestBytes < 1 || digestBytes > 32) + throw new ArgumentException("Invalid digest length (required: 1 - 32)"); + + this.digestLength = digestBytes; + + if (!Arrays.IsNullOrEmpty(key)) + { + if (key.Length > 32) + throw new ArgumentException("Keys > 32 bytes are not supported", nameof(key)); + + m_key = Arrays.CopyBuffer(key); + } + + if (salt != null) + { + if (salt.Length != 8) + throw new ArgumentException("salt length must be exactly 8 bytes", nameof(salt)); + + m_salt = Arrays.CopyBuffer(salt); + } + + if (personalization != null) + { + if (personalization.Length != 8) + throw new ArgumentException("personalization length must be exactly 8 bytes", + nameof(personalization)); + + m_personalization = Arrays.CopyBuffer(personalization); + } + + this.nodeOffset = offset; + + Init(); + } + + // XOF internal hash parameters + internal Blake2sDigest(int digestBytes, int hashLength, long offset) + { + digestLength = digestBytes; + nodeOffset = offset; + fanout = 0; + depth = 0; + leafLength = hashLength; + innerHashLength = hashLength; + nodeDepth = 0; + + Init(); + } + + private void Init() + { + int keyLength = 0; + if (m_key != null) + { + keyLength = m_key.Length; + Array.Copy(m_key, 0, buffer, 0, keyLength); + //Arrays.Fill(buffer, keyLength, BLOCK_LENGTH_BYTES, 0); + bufferPos = BLOCK_LENGTH_BYTES; // zero padding + } + + chainValue[0] = IV[0] ^ (uint)(digestLength | (keyLength << 8) | ((fanout << 16) | (depth << 24))); + chainValue[1] = IV[1] ^ (uint)leafLength; + + int nofHi = (int)(nodeOffset >> 32); + int nofLo = (int)nodeOffset; + chainValue[2] = IV[2] ^ (uint)nofLo; + chainValue[3] = IV[3] ^ (uint)(nofHi | (nodeDepth << 16) | (innerHashLength << 24)); + + chainValue[4] = IV[4]; + chainValue[5] = IV[5]; + if (m_salt != null) + { + chainValue[4] ^= Pack.LE_To_UInt32(m_salt, 0); + chainValue[5] ^= Pack.LE_To_UInt32(m_salt, 4); + } + + chainValue[6] = IV[6]; + chainValue[7] = IV[7]; + if (m_personalization != null) + { + chainValue[6] ^= Pack.LE_To_UInt32(m_personalization, 0); + chainValue[7] ^= Pack.LE_To_UInt32(m_personalization, 4); + } + } + + /// + public void Update(byte b) + { + // process the buffer if full else add to buffer: + int remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength == 0) + { + // full buffer + IncrementCounter(BLOCK_LENGTH_BYTES); +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Compress(buffer); +#else + Compress(buffer, 0); +#endif + Array.Clear(buffer, 0, buffer.Length);// clear buffer + buffer[0] = b; + bufferPos = 1; + } + else + { + buffer[bufferPos++] = b; + } + } + + /// + public void BlockUpdate(byte[] message, int offset, int len) + { + if (message == null || len == 0) + return; + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + BlockUpdate(message.AsSpan(offset, len)); +#else + int remainingLength = 0; // left bytes of buffer + + if (bufferPos != 0) + { + // commenced, incomplete buffer + + // complete the buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength >= len) + { + Array.Copy(message, offset, buffer, bufferPos, len); + bufferPos += len; + return; + } + + // full buffer + at least 1 byte + Array.Copy(message, offset, buffer, bufferPos, remainingLength); + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(buffer, 0); + bufferPos = 0; + Array.Clear(buffer, 0, buffer.Length);// clear buffer + } + + // process blocks except last block (also if last block is full) + int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES; + int messagePos = offset + remainingLength; + while (messagePos < blockWiseLastPos) + { + // block wise 64 bytes without buffer: + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(message, messagePos); + messagePos += BLOCK_LENGTH_BYTES; + } + + // fill the buffer with left bytes, this might be a full block + Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos); + bufferPos += offset + len - messagePos; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + public void BlockUpdate(ReadOnlySpan input) + { + if (input.IsEmpty) + return; + + int remainingLength = 0; // left bytes of buffer + + if (bufferPos != 0) + { + // commenced, incomplete buffer + + // complete the buffer: + remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + if (remainingLength >= input.Length) + { + input.CopyTo(buffer.AsSpan(bufferPos)); + bufferPos += input.Length; + return; + } + + // full buffer + at least 1 byte + input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos)); + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(buffer); + bufferPos = 0; + Array.Clear(buffer, 0, buffer.Length);// clear buffer + } + + // process blocks except last block (also if last block is full) + int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES; + int messagePos = remainingLength; + while (messagePos < blockWiseLastPos) + { + // block wise 64 bytes without buffer: + IncrementCounter(BLOCK_LENGTH_BYTES); + Compress(input[messagePos..]); + messagePos += BLOCK_LENGTH_BYTES; + } + + // fill the buffer with left bytes, this might be a full block + input[messagePos..].CopyTo(buffer.AsSpan()); + bufferPos += input.Length - messagePos; + } +#endif + + /// Close the digest, producing the final digest value. + /// + /// The call leaves the digest reset. + /// Key, salt and personal string remain. + /// + /// The byte array the digest is to be copied into. + /// The offset into the byte array the digest is to start at. + /// The number of bytes written. + public int DoFinal(byte[] output, int outOffset) + { + Check.OutputLength(output, outOffset, digestLength, "output buffer too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return DoFinal(output.AsSpan(outOffset)); +#else + f0 = 0xFFFFFFFFU; + //if (isLastNode) + //{ + // f1 = 0xFFFFFFFFU; + //} + + if (bufferPos > 0) + { + IncrementCounter(bufferPos); + } + + Compress(buffer, 0); + + int full = digestLength >> 2, partial = digestLength & 3; + Pack.UInt32_To_LE(chainValue, 0, full, output, outOffset); + if (partial > 0) + { + Pack.UInt32_To_LE_Low(chainValue[full], output, outOffset + digestLength - partial, partial); + } + + Reset(); + + return digestLength; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// Close the digest, producing the final digest value. + /// + /// The call leaves the digest reset. + /// Key, salt and personal string remain. + /// + /// The span the digest is to be copied into. + /// The number of bytes written. + public int DoFinal(Span output) + { + Check.OutputLength(output, digestLength, "output buffer too short"); + + f0 = 0xFFFFFFFFU; + //if (isLastNode) + //{ + // f1 = 0xFFFFFFFFU; + //} + + if (bufferPos > 0) + { + IncrementCounter(bufferPos); + } + + Compress(buffer); + + int full = digestLength >> 2, partial = digestLength & 3; + Pack.UInt32_To_LE(chainValue.AsSpan(0, full), output); + if (partial > 0) + { + Pack.UInt32_To_LE_Low(chainValue[full], output.Slice(digestLength - partial, partial)); + } + + Reset(); + + return digestLength; + } +#endif + + /// + /// Reset the digest back to it's initial state. + /// The key, the salt and the personalization will remain for further computations. + /// + public void Reset() + { + bufferPos = 0; + f0 = 0U; + t0 = 0U; + t1 = 0U; + + Array.Clear(buffer, 0, buffer.Length); + + Init(); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Compress(ReadOnlySpan message) + { +#if NETCOREAPP3_0_OR_GREATER + if (Blake2s_X86.IsSupported) + { + Blake2s_X86.Compress(chainValue, IV, t0, t1, f0, message); + return; + } +#endif + + Span m = stackalloc uint[16]; + Pack.LE_To_UInt32(message, m); + + uint v0 = chainValue[0]; + uint v1 = chainValue[1]; + uint v2 = chainValue[2]; + uint v3 = chainValue[3]; + uint v4 = chainValue[4]; + uint v5 = chainValue[5]; + uint v6 = chainValue[6]; + uint v7 = chainValue[7]; + uint v8 = IV[0]; + uint v9 = IV[1]; + uint va = IV[2]; + uint vb = IV[3]; + uint vc = IV[4] ^ t0; + uint vd = IV[5] ^ t1; + uint ve = IV[6] ^ f0; + uint vf = IV[7]; // ^ f1, with f1 == 0 + + for (int round = 0; round < ROUNDS; round++) + { + int pos = round * 16; + + // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1 + G(m[Sigma[pos + 0]], m[Sigma[pos + 1]], ref v0, ref v4, ref v8, ref vc); + G(m[Sigma[pos + 2]], m[Sigma[pos + 3]], ref v1, ref v5, ref v9, ref vd); + G(m[Sigma[pos + 4]], m[Sigma[pos + 5]], ref v2, ref v6, ref va, ref ve); + G(m[Sigma[pos + 6]], m[Sigma[pos + 7]], ref v3, ref v7, ref vb, ref vf); + + // G apply to diagonals of internalState: + G(m[Sigma[pos + 8]], m[Sigma[pos + 9]], ref v0, ref v5, ref va, ref vf); + G(m[Sigma[pos + 10]], m[Sigma[pos + 11]], ref v1, ref v6, ref vb, ref vc); + G(m[Sigma[pos + 12]], m[Sigma[pos + 13]], ref v2, ref v7, ref v8, ref vd); + G(m[Sigma[pos + 14]], m[Sigma[pos + 15]], ref v3, ref v4, ref v9, ref ve); + } + + chainValue[0] ^= v0 ^ v8; + chainValue[1] ^= v1 ^ v9; + chainValue[2] ^= v2 ^ va; + chainValue[3] ^= v3 ^ vb; + chainValue[4] ^= v4 ^ vc; + chainValue[5] ^= v5 ^ vd; + chainValue[6] ^= v6 ^ ve; + chainValue[7] ^= v7 ^ vf; + } +#else + private void Compress(byte[] message, int messagePos) + { + uint[] m = new uint[16]; + Pack.LE_To_UInt32(message, messagePos, m); + + uint v0 = chainValue[0]; + uint v1 = chainValue[1]; + uint v2 = chainValue[2]; + uint v3 = chainValue[3]; + uint v4 = chainValue[4]; + uint v5 = chainValue[5]; + uint v6 = chainValue[6]; + uint v7 = chainValue[7]; + uint v8 = IV[0]; + uint v9 = IV[1]; + uint va = IV[2]; + uint vb = IV[3]; + uint vc = IV[4] ^ t0; + uint vd = IV[5] ^ t1; + uint ve = IV[6] ^ f0; + uint vf = IV[7]; // ^ f1, with f1 == 0 + + for (int round = 0; round < ROUNDS; round++) + { + int pos = round * 16; + + // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1 + G(m[Sigma[pos + 0]], m[Sigma[pos + 1]], ref v0, ref v4, ref v8, ref vc); + G(m[Sigma[pos + 2]], m[Sigma[pos + 3]], ref v1, ref v5, ref v9, ref vd); + G(m[Sigma[pos + 4]], m[Sigma[pos + 5]], ref v2, ref v6, ref va, ref ve); + G(m[Sigma[pos + 6]], m[Sigma[pos + 7]], ref v3, ref v7, ref vb, ref vf); + + // G apply to diagonals of internalState: + G(m[Sigma[pos + 8]], m[Sigma[pos + 9]], ref v0, ref v5, ref va, ref vf); + G(m[Sigma[pos + 10]], m[Sigma[pos + 11]], ref v1, ref v6, ref vb, ref vc); + G(m[Sigma[pos + 12]], m[Sigma[pos + 13]], ref v2, ref v7, ref v8, ref vd); + G(m[Sigma[pos + 14]], m[Sigma[pos + 15]], ref v3, ref v4, ref v9, ref ve); + } + + chainValue[0] ^= v0 ^ v8; + chainValue[1] ^= v1 ^ v9; + chainValue[2] ^= v2 ^ va; + chainValue[3] ^= v3 ^ vb; + chainValue[4] ^= v4 ^ vc; + chainValue[5] ^= v5 ^ vd; + chainValue[6] ^= v6 ^ ve; + chainValue[7] ^= v7 ^ vf; + } +#endif + + /// + public string AlgorithmName => "BLAKE2s"; + + /// + public int GetDigestSize() => digestLength; + + /// + /// Return the size in bytes of the internal buffer the digest applies it's compression + /// function to. + /// + /// The byte length of the digests internal buffer. + public int GetByteLength() => BLOCK_LENGTH_BYTES; + + /// + /// Clears the key. + /// + public void ClearKey() + { + if (m_key != null) + { + Array.Clear(m_key, 0, m_key.Length); + Array.Clear(buffer, 0, buffer.Length); + } + } + + /// + /// Clears the salt (pepper). + /// + public void ClearSalt() + { + if (m_salt != null) + { + Array.Clear(m_salt, 0, m_salt.Length); + } + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void IncrementCounter(int count) + { + Debug.Assert(count > 0); + + uint count32 = (uint)count; + t0 += count32; + if (t0 < count32) + { + ++t1; + } + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private static void G(uint m1, uint m2, ref uint a, ref uint b, ref uint c, ref uint d) + { + a += b + m1; + d = Integers.RotateRight(d ^ a, 16); + c += d; + b = Integers.RotateRight(b ^ c, 12); + + a += b + m2; + d = Integers.RotateRight(d ^ a, 8); + c += d; + b = Integers.RotateRight(b ^ c, 7); + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2s_X86.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2s_X86.cs new file mode 100644 index 000000000..3fdb8f0f2 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/Blake2s_X86.cs @@ -0,0 +1,364 @@ +#if NETCOREAPP3_0_OR_GREATER +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace Org.BouncyCastle.Crypto.Digests +{ + // License from the original code created by Clinton Ingram (saucecontrol) for Blake2Fast + // at https://github.com/saucecontrol/Blake2Fast. The code has been copied and modified. + + // The MIT License + + // Copyright(c) 2018-2021 Clinton Ingram + + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to deal + // in the Software without restriction, including without limitation the rights + // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + // copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + // THE SOFTWARE. + + internal static class Blake2s_X86 + { + internal static bool IsSupported => + Org.BouncyCastle.Runtime.Intrinsics.X86.Sse41.IsEnabled && + Org.BouncyCastle.Runtime.Intrinsics.Vector.IsPackedLittleEndian; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void Compress(Span hashBuffer, ReadOnlySpan blakeIV, uint t0, uint t1, uint f0, + ReadOnlySpan message) + { + if (!IsSupported) + throw new PlatformNotSupportedException(nameof(Blake2s_X86)); + + Debug.Assert(hashBuffer.Length >= 8); + Debug.Assert(blakeIV.Length >= 8); + Debug.Assert(message.Length >= 64); + + var hashBytes = MemoryMarshal.AsBytes(hashBuffer); + var ivBytes = MemoryMarshal.AsBytes(blakeIV); + + var t_0 = Vector128.Create(t0, t1, f0, 0); + + var row1 = MemoryMarshal.Read>(hashBytes); + var row2 = MemoryMarshal.Read>(hashBytes[16..]); + var row3 = MemoryMarshal.Read>(ivBytes); + var row4 = MemoryMarshal.Read>(ivBytes[16..]); + row4 = Sse2.Xor(row4, t_0); + + var orig_1 = row1; + var orig_2 = row2; + + Perform10Rounds(message, ref row1, ref row2, ref row3, ref row4); + + row1 = Sse2.Xor(row1, row3); + row2 = Sse2.Xor(row2, row4); + row1 = Sse2.Xor(row1, orig_1); + row2 = Sse2.Xor(row2, orig_2); + +#if NET8_0_OR_GREATER + MemoryMarshal.Write(hashBytes, in row1); + MemoryMarshal.Write(hashBytes[16..], in row2); +#else + MemoryMarshal.Write(hashBytes, ref row1); + MemoryMarshal.Write(hashBytes[16..], ref row2); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Perform10Rounds(ReadOnlySpan m, ref Vector128 row1, ref Vector128 row2, + ref Vector128 row3, ref Vector128 row4) + { +#region Rounds + var m0 = MemoryMarshal.Read>(m); + var m1 = MemoryMarshal.Read>(m[16..]); + var m2 = MemoryMarshal.Read>(m[32..]); + var m3 = MemoryMarshal.Read>(m[48..]); + + //ROUND 1 + var b1 = Sse.Shuffle(m0.AsSingle(), m1.AsSingle(), 0b_10_00_10_00).AsUInt32(); + + var b2 = Sse.Shuffle(m0.AsSingle(), m1.AsSingle(), 0b_11_01_11_01).AsUInt32(); + + var t0 = Sse2.Shuffle(m2, 0b_11_10_00_01); + var t1 = Sse2.Shuffle(m3, 0b_00_01_11_10); + var b3 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_11_00_00_11).AsUInt32(); + + t0 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_11_11_00).AsUInt32(); + var b4 = Sse2.Shuffle(t0, 0b_10_11_00_01); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 2 + t0 = Sse41.Blend(m1.AsUInt16(), m2.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + t1 = Sse2.ShiftLeftLogical128BitLane(m3, 4); + var t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_11_11_00_00).AsUInt32(); + b1 = Sse2.Shuffle(t2, 0b_10_01_00_11); + + t0 = Sse2.Shuffle(m2, 0b_00_00_10_00); + t1 = Sse41.Blend(m1.AsUInt16(), m3.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_11_11_00_00).AsUInt32(); + b2 = Sse2.Shuffle(t2, 0b_10_11_00_01); + + t0 = Sse2.ShiftLeftLogical128BitLane(m1, 4); + t1 = Sse41.Blend(m2.AsUInt16(), t0.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + t2 = Sse41.Blend(m0.AsUInt16(), t1.AsUInt16(), 0b_11_11_00_00).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_11_00_01_10); + + t0 = Sse2.UnpackHigh(m0, m1); + t1 = Sse2.ShiftLeftLogical128BitLane(m3, 4); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + b4 = Sse2.Shuffle(t2, 0b_11_00_01_10); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 3 + t0 = Sse2.UnpackHigh(m2, m3); + t1 = Sse41.Blend(m3.AsUInt16(), m1.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + b1 = Sse2.Shuffle(t2, 0b_11_01_00_10); + + t0 = Sse2.UnpackLow(m2, m0); + t1 = Sse41.Blend(t0.AsUInt16(), m0.AsUInt16(), 0b_11_11_00_00).AsUInt32(); + t2 = Sse2.ShiftLeftLogical128BitLane(m3, 8); + b2 = Sse41.Blend(t1.AsUInt16(), t2.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + + t0 = Sse41.Blend(m0.AsUInt16(), m2.AsUInt16(), 0b_00_11_11_00).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m1, 12); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_00_11_10_01); + + t0 = Sse2.ShiftLeftLogical128BitLane(m3, 4); + t1 = Sse41.Blend(m0.AsUInt16(), m1.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + t2 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + b4 = Sse2.Shuffle(t2, 0b_01_10_11_00); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 4 + t0 = Sse2.UnpackHigh(m0, m1); + t1 = Sse2.UnpackHigh(t0, m2); + t2 = Sse41.Blend(t1.AsUInt16(), m3.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + b1 = Sse2.Shuffle(t2, 0b_11_01_00_10); + + t0 = Sse2.ShiftLeftLogical128BitLane(m2, 8); + t1 = Sse41.Blend(m3.AsUInt16(), m0.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + t2 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + b2 = Sse2.Shuffle(t2, 0b_10_00_01_11); + + t0 = Sse41.Blend(m0.AsUInt16(), m1.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + t1 = Sse41.Blend(t0.AsUInt16(), m3.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + b3 = Sse2.Shuffle(t1, 0b_00_01_10_11); + + t0 = Ssse3.AlignRight(m0, m1, 4); + b4 = Sse41.Blend(t0.AsUInt16(), m2.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 5 + t0 = Sse2.UnpackLow(m1.AsUInt64(), m2.AsUInt64()).AsUInt32(); + t1 = Sse2.UnpackHigh(m0.AsUInt64(), m2.AsUInt64()).AsUInt32(); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + b1 = Sse2.Shuffle(t2, 0b_10_00_01_11); + + t0 = Sse2.UnpackHigh(m1.AsUInt64(), m3.AsUInt64()).AsUInt32(); + t1 = Sse2.UnpackLow(m0.AsUInt64(), m1.AsUInt64()).AsUInt32(); + b2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + + t0 = Sse2.UnpackHigh(m3.AsUInt64(), m1.AsUInt64()).AsUInt32(); + t1 = Sse2.UnpackHigh(m2.AsUInt64(), m0.AsUInt64()).AsUInt32(); + t2 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_10_01_00_11); + + t0 = Sse41.Blend(m0.AsUInt16(), m2.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + t1 = Sse2.ShiftLeftLogical128BitLane(t0, 8); + t2 = Sse41.Blend(t1.AsUInt16(), m3.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + b4 = Sse2.Shuffle(t2, 0b_10_00_11_01); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 6 + t0 = Sse2.UnpackHigh(m0, m1); + t1 = Sse2.UnpackLow(m0, m2); + b1 = Sse2.UnpackLow(t0.AsUInt64(), t1.AsUInt64()).AsUInt32(); + + t0 = Sse2.ShiftRightLogical128BitLane(m2, 4); + t1 = Sse41.Blend(m0.AsUInt16(), m3.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + b2 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_00_11_11_00).AsUInt32(); + + t0 = Sse41.Blend(m1.AsUInt16(), m0.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m3, 4); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_10_11_00_01); + + t0 = Sse2.UnpackLow(m2.AsUInt64(), m1.AsUInt64()).AsUInt32(); + t1 = Sse2.Shuffle(m3, 0b_10_00_01_00); + t2 = Sse2.ShiftRightLogical128BitLane(t0, 4); + b4 = Sse41.Blend(t1.AsUInt16(), t2.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 7 + t0 = Sse2.ShiftLeftLogical128BitLane(m1, 12); + t1 = Sse41.Blend(m0.AsUInt16(), m3.AsUInt16(), 0b_00_11_00_11).AsUInt32(); + b1 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + + t0 = Sse41.Blend(m3.AsUInt16(), m2.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m1, 4); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + b2 = Sse2.Shuffle(t2, 0b_10_01_11_00); + + t0 = Sse2.UnpackLow(m0.AsUInt64(), m2.AsUInt64()).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m1, 4); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_11_00).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_11_01_00_10); + + t0 = Sse2.UnpackHigh(m1, m2); + t1 = Sse2.UnpackHigh(m0.AsUInt64(), t0.AsUInt64()).AsUInt32(); + b4 = Sse2.Shuffle(t1, 0b_00_01_10_11); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 8 + t0 = Sse2.UnpackHigh(m0, m1); + t1 = Sse41.Blend(t0.AsUInt16(), m3.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + b1 = Sse2.Shuffle(t1, 0b_10_00_11_01); + + t0 = Sse41.Blend(m2.AsUInt16(), m3.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m0, 4); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + b2 = Sse2.Shuffle(t2, 0b_01_00_10_11); + + t0 = Sse2.UnpackHigh(m0.AsUInt64(), m3.AsUInt64()).AsUInt32(); + t1 = Sse2.UnpackLow(m1.AsUInt64(), m2.AsUInt64()).AsUInt32(); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_11_11_00).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_10_11_01_00); + + t0 = Sse2.UnpackLow(m0, m1); + t1 = Sse2.UnpackHigh(m1, m2); + t2 = Sse2.UnpackLow(t0.AsUInt64(), t1.AsUInt64()).AsUInt32(); + b4 = Sse2.Shuffle(t2, 0b_10_01_00_11); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 9 + t0 = Sse2.UnpackHigh(m1, m3); + t1 = Sse2.UnpackLow(t0.AsUInt64(), m0.AsUInt64()).AsUInt32(); + t2 = Sse41.Blend(t1.AsUInt16(), m2.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + b1 = Sse2.ShuffleHigh(t2.AsUInt16(), 0b_01_00_11_10).AsUInt32(); + + t0 = Sse2.UnpackHigh(m0, m3); + t1 = Sse41.Blend(m2.AsUInt16(), t0.AsUInt16(), 0b_11_11_00_00).AsUInt32(); + b2 = Sse2.Shuffle(t1, 0b_00_10_01_11); + + t0 = Sse2.UnpackLow(m0.AsUInt64(), m3.AsUInt64()).AsUInt32(); + t1 = Sse2.ShiftRightLogical128BitLane(m2, 8); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_01_11_10_00); + + t0 = Sse41.Blend(m1.AsUInt16(), m0.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + b4 = Sse2.Shuffle(t0, 0b_00_11_10_01); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); + + //ROUND 10 + t0 = Sse41.Blend(m0.AsUInt16(), m2.AsUInt16(), 0b_00_00_00_11).AsUInt32(); + t1 = Sse41.Blend(m1.AsUInt16(), m2.AsUInt16(), 0b_00_11_00_00).AsUInt32(); + t2 = Sse41.Blend(t1.AsUInt16(), t0.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + b1 = Sse2.Shuffle(t2, 0b_01_11_00_10); + + t0 = Sse2.ShiftLeftLogical128BitLane(m0, 4); + t1 = Sse41.Blend(m1.AsUInt16(), t0.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + b2 = Sse2.Shuffle(t1, 0b_01_10_00_11); + + t0 = Sse2.UnpackHigh(m0, m3); + t1 = Sse2.UnpackLow(m2, m3); + t2 = Sse2.UnpackHigh(t0.AsUInt64(), t1.AsUInt64()).AsUInt32(); + b3 = Sse2.Shuffle(t2, 0b_00_10_01_11); + + t0 = Sse41.Blend(m3.AsUInt16(), m2.AsUInt16(), 0b_11_00_00_00).AsUInt32(); + t1 = Sse2.UnpackLow(m0, m3); + t2 = Sse41.Blend(t0.AsUInt16(), t1.AsUInt16(), 0b_00_00_11_11).AsUInt32(); + b4 = Sse2.Shuffle(t2, 0b_01_10_11_00); + + Round(ref row1, ref row2, ref row3, ref row4, b1, b2, b3, b4); +#endregion + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Round(ref Vector128 row1, ref Vector128 row2, ref Vector128 row3, + ref Vector128 row4, Vector128 b1, Vector128 b2, Vector128 b3, Vector128 b4) + { + Vector128 r8 = Vector128.Create((byte)1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8, 13, 14, 15, 12); + Vector128 r16 = Vector128.Create((byte)2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13); + + G1(r16, ref row1, ref row2, ref row3, ref row4, b1); + G2(r8, ref row1, ref row2, ref row3, ref row4, b2); + + Diagonalize(ref row1, ref row3, ref row4); + + G1(r16, ref row1, ref row2, ref row3, ref row4, b3); + G2(r8, ref row1, ref row2, ref row3, ref row4, b4); + + Diagonalize(ref row3, ref row1, ref row4); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Diagonalize(ref Vector128 row1, ref Vector128 row3, ref Vector128 row4) + { + // +-------------------+ +-------------------+ + // | 0 | 1 | 2 | 3 | | 3 | 0 | 1 | 2 | + // +-------------------+ +-------------------+ + // | 8 | 9 | 10 | 11 | ---> | 9 | 10 | 11 | 8 | + // +-------------------+ +-------------------+ + // | 12 | 13 | 14 | 15 | | 14 | 15 | 12 | 13 | + // +-------------------+ +-------------------+ + + row1 = Sse2.Shuffle(row1, 0b_10_01_00_11); + row3 = Sse2.Shuffle(row3, 0b_00_11_10_01); + row4 = Sse2.Shuffle(row4, 0b_01_00_11_10); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void G1(Vector128 r16, ref Vector128 row1, ref Vector128 row2, + ref Vector128 row3, ref Vector128 row4, Vector128 b0) + { + row1 = Sse2.Add(Sse2.Add(row1, b0), row2); + row4 = Sse2.Xor(row4, row1); + row4 = Ssse3.Shuffle(row4.AsByte(), r16).AsUInt32(); + + row3 = Sse2.Add(row3, row4); + row2 = Sse2.Xor(row2, row3); + row2 = Sse2.Xor(Sse2.ShiftRightLogical(row2, 12), Sse2.ShiftLeftLogical(row2, 32 - 12)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void G2(Vector128 r8, ref Vector128 row1, ref Vector128 row2, + ref Vector128 row3, ref Vector128 row4, Vector128 b0) + { + row1 = Sse2.Add(Sse2.Add(row1, b0), row2); + row4 = Sse2.Xor(row4, row1); + row4 = Ssse3.Shuffle(row4.AsByte(), r8).AsUInt32(); + + row3 = Sse2.Add(row3, row4); + row2 = Sse2.Xor(row2, row3); + row2 = Sse2.Xor(Sse2.ShiftRightLogical(row2, 7), Sse2.ShiftLeftLogical(row2, 32 - 7)); + } + } +} +#endif diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/IDigest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/IDigest.cs new file mode 100644 index 000000000..eaf04c05d --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/IDigest.cs @@ -0,0 +1,53 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// Base interface for a message digest. + internal interface IDigest + { + /// The algorithm name. + string AlgorithmName { get; } + + /// Return the size, in bytes, of the digest produced by this message digest. + /// The size, in bytes, of the digest produced by this message digest. + int GetDigestSize(); + + /// Return the size, in bytes, of the internal buffer used by this digest. + /// The size, in bytes, of the internal buffer used by this digest. + int GetByteLength(); + + /// Update the message digest with a single byte. + /// The input byte to be entered. + void Update(byte input); + + /// Update the message digest with a block of bytes. + /// The byte array containing the data. + /// The offset into the byte array where the data starts. + /// The length of the data. + void BlockUpdate(byte[] input, int inOff, int inLen); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// Update the message digest with a span of bytes. + /// The span containing the data. + void BlockUpdate(ReadOnlySpan input); +#endif + + /// Close the digest, producing the final digest value. + /// This call leaves the digest reset. + /// The byte array the digest is to be copied into. + /// The offset into the byte array the digest is to start at. + /// The number of bytes written. + int DoFinal(byte[] output, int outOff); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// Close the digest, producing the final digest value. + /// This call leaves the digest reset. + /// The span the digest is to be copied into. + /// The number of bytes written. + int DoFinal(Span output); +#endif + + /// Reset the digest back to its initial state. + void Reset(); + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/IMemoable.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/IMemoable.cs new file mode 100644 index 000000000..75c0c1e0f --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/IMemoable.cs @@ -0,0 +1,29 @@ +using System; + +namespace Org.BouncyCastle.Utilities +{ + internal interface IMemoable + { + /// + /// Produce a copy of this object with its configuration and in its current state. + /// + /// + /// The returned object may be used simply to store the state, or may be used as a similar object + /// starting from the copied state. + /// + IMemoable Copy(); + + /// + /// Restore a copied object state into this object. + /// + /// + /// Implementations of this method should try to avoid or minimise memory allocation to perform the reset. + /// + /// an object originally {@link #copy() copied} from an object of the same type as this instance. + /// if the provided object is not of the correct type. + /// if the other parameter is in some other way invalid. + void Reset(IMemoable other); + } + +} + diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/IXof.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/IXof.cs new file mode 100644 index 000000000..684530671 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/IXof.cs @@ -0,0 +1,50 @@ +using System; + +namespace Org.BouncyCastle.Crypto +{ + /// + /// With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes. + /// This interface provides the extra methods required to support variable output on a digest implementation. + /// + internal interface IXof + : IDigest + { + /// + /// Output the results of the final calculation for this XOF to outLen number of bytes. + /// + /// output array to write the output bytes to. + /// offset to start writing the bytes at. + /// the number of output bytes requested. + /// the number of bytes written + int OutputFinal(byte[] output, int outOff, int outLen); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + /// Output the results of the final calculation for this XOF to fill the output span. + /// + /// span to fill with the output bytes. + /// the number of bytes written + int OutputFinal(Span output); +#endif + + /// + /// Start outputting the results of the final calculation for this XOF. Unlike DoFinal, this method + /// will continue producing output until the XOF is explicitly reset, or signals otherwise. + /// + /// output array to write the output bytes to. + /// offset to start writing the bytes at. + /// the number of output bytes requested. + /// the number of bytes written + int Output(byte[] output, int outOff, int outLen); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + /// + /// Start outputting the results of the final calculation for this XOF. Unlike OutputFinal, this method + /// will continue producing output until the XOF is explicitly reset, or signals otherwise. + /// + /// span to fill with the output bytes. + /// the number of bytes written + int Output(Span output); +#endif + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/KeccakDigest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/KeccakDigest.cs new file mode 100644 index 000000000..0f678ea94 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/KeccakDigest.cs @@ -0,0 +1,636 @@ +using System; +using System.Diagnostics; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +#endif + +using Org.BouncyCastle.Crypto.Utilities; +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER +using Org.BouncyCastle.Math.Raw; +#endif +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + internal class KeccakDigest + : IDigest, IMemoable + { + private static readonly ulong[] KeccakRoundConstants = new ulong[]{ + 0x0000000000000001UL, 0x0000000000008082UL, 0x800000000000808aUL, 0x8000000080008000UL, + 0x000000000000808bUL, 0x0000000080000001UL, 0x8000000080008081UL, 0x8000000000008009UL, + 0x000000000000008aUL, 0x0000000000000088UL, 0x0000000080008009UL, 0x000000008000000aUL, + 0x000000008000808bUL, 0x800000000000008bUL, 0x8000000000008089UL, 0x8000000000008003UL, + 0x8000000000008002UL, 0x8000000000000080UL, 0x000000000000800aUL, 0x800000008000000aUL, + 0x8000000080008081UL, 0x8000000000008080UL, 0x0000000080000001UL, 0x8000000080008008UL + }; + + private readonly ulong[] state = new ulong[25]; + protected byte[] dataQueue = new byte[192]; + protected int rate; + protected int bitsInQueue; + protected internal int fixedOutputLength; + protected bool squeezing; + + public KeccakDigest() + : this(288) + { + } + + public KeccakDigest(int bitLength) + { + Init(bitLength); + } + + public KeccakDigest(KeccakDigest source) + { + CopyIn(source); + } + + private void CopyIn(KeccakDigest source) + { + Array.Copy(source.state, 0, this.state, 0, source.state.Length); + Array.Copy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.Length); + this.rate = source.rate; + this.bitsInQueue = source.bitsInQueue; + this.fixedOutputLength = source.fixedOutputLength; + this.squeezing = source.squeezing; + } + + public virtual string AlgorithmName + { + get { return "Keccak-" + fixedOutputLength; } + } + + public virtual int GetDigestSize() + { + return fixedOutputLength >> 3; + } + + public virtual void Update(byte input) + { + Absorb(input); + } + + public virtual void BlockUpdate(byte[] input, int inOff, int len) + { + Absorb(input, inOff, len); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public virtual void BlockUpdate(ReadOnlySpan input) + { + Absorb(input); + } +#endif + + public virtual int DoFinal(byte[] output, int outOff) + { + Squeeze(output, outOff, fixedOutputLength); + + Reset(); + + return GetDigestSize(); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public virtual int DoFinal(Span output) + { + int digestSize = GetDigestSize(); + Squeeze(output[..digestSize]); + + Reset(); + + return digestSize; + } +#endif + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + if (partialBits > 0) + { + AbsorbBits(partialByte, partialBits); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Squeeze(output.AsSpan(outOff, fixedOutputLength >> 3)); +#else + Squeeze(output, outOff, fixedOutputLength); +#endif + + Reset(); + + return GetDigestSize(); + } + + public virtual void Reset() + { + Init(fixedOutputLength); + } + + /** + * Return the size of block that the compression function is applied to in bytes. + * + * @return internal byte length of a block. + */ + public virtual int GetByteLength() + { + return rate >> 3; + } + + private void Init(int bitLength) + { + switch (bitLength) + { + case 128: + case 224: + case 256: + case 288: + case 384: + case 512: + InitSponge(1600 - (bitLength << 1)); + break; + default: + throw new ArgumentException("must be one of 128, 224, 256, 288, 384, or 512.", "bitLength"); + } + } + + private void InitSponge(int rate) + { + if (rate <= 0 || rate >= 1600 || (rate & 63) != 0) + throw new InvalidOperationException("invalid rate value"); + + this.rate = rate; + Array.Clear(state, 0, state.Length); + Arrays.Fill(this.dataQueue, (byte)0); + this.bitsInQueue = 0; + this.squeezing = false; + this.fixedOutputLength = (1600 - rate) >> 1; + } + + protected void Absorb(byte data) + { + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + dataQueue[bitsInQueue >> 3] = data; + if ((bitsInQueue += 8) == rate) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + KeccakAbsorb(dataQueue); +#else + KeccakAbsorb(dataQueue, 0); +#endif + bitsInQueue = 0; + } + } + + protected void Absorb(byte[] data, int off, int len) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Absorb(data.AsSpan(off, len)); +#else + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int bytesInQueue = bitsInQueue >> 3; + int rateBytes = rate >> 3; + + int available = rateBytes - bytesInQueue; + if (len < available) + { + Array.Copy(data, off, dataQueue, bytesInQueue, len); + this.bitsInQueue += len << 3; + return; + } + + int count = 0; + if (bytesInQueue > 0) + { + Array.Copy(data, off, dataQueue, bytesInQueue, available); + count += available; + KeccakAbsorb(dataQueue, 0); + } + + int remaining; + while ((remaining = (len - count)) >= rateBytes) + { + KeccakAbsorb(data, off + count); + count += rateBytes; + } + + Array.Copy(data, off + count, dataQueue, 0, remaining); + this.bitsInQueue = remaining << 3; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + protected void Absorb(ReadOnlySpan data) + { + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int bytesInQueue = bitsInQueue >> 3; + int rateBytes = rate >> 3; + + int len = data.Length; + int available = rateBytes - bytesInQueue; + if (len < available) + { + data.CopyTo(dataQueue.AsSpan(bytesInQueue)); + this.bitsInQueue += len << 3; + return; + } + + int count = 0; + if (bytesInQueue > 0) + { + data[..available].CopyTo(dataQueue.AsSpan(bytesInQueue)); + count += available; + KeccakAbsorb(dataQueue); + } + + int remaining; + while ((remaining = len - count) >= rateBytes) + { + KeccakAbsorb(data[count..]); + count += rateBytes; + } + + data[count..].CopyTo(dataQueue.AsSpan()); + this.bitsInQueue = remaining << 3; + } +#endif + + protected void AbsorbBits(int data, int bits) + { + if (bits < 1 || bits > 7) + throw new ArgumentException("must be in the range 1 to 7", "bits"); + if ((bitsInQueue & 7) != 0) + throw new InvalidOperationException("attempt to absorb with odd length queue"); + if (squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int mask = (1 << bits) - 1; + dataQueue[bitsInQueue >> 3] = (byte)(data & mask); + + // NOTE: After this, bitsInQueue is no longer a multiple of 8, so no more absorbs will work + bitsInQueue += bits; + } + + private void PadAndSwitchToSqueezingPhase() + { + Debug.Assert(bitsInQueue < rate); + + dataQueue[bitsInQueue >> 3] |= (byte)(1 << (bitsInQueue & 7)); + + if (++bitsInQueue == rate) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + KeccakAbsorb(dataQueue); +#else + KeccakAbsorb(dataQueue, 0); +#endif + } + else + { + int full = bitsInQueue >> 6, partial = bitsInQueue & 63; + int off = 0; + for (int i = 0; i < full; ++i) + { + state[i] ^= Pack.LE_To_UInt64(dataQueue, off); + off += 8; + } + if (partial > 0) + { + ulong mask = (1UL << partial) - 1UL; + state[full] ^= Pack.LE_To_UInt64(dataQueue, off) & mask; + } + } + + state[(rate - 1) >> 6] ^= (1UL << 63); + + bitsInQueue = 0; + squeezing = true; + } + + // TODO[api] Not needed under NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + protected void Squeeze(byte[] output, int offset, long outputLength) + { + if ((outputLength & 7L) != 0L) + throw new InvalidOperationException("outputLength not a multiple of 8"); + + if (!squeezing) + { + PadAndSwitchToSqueezingPhase(); + } + else if (bitsInQueue > 0) + { + int availableBits = bitsInQueue; + int dataQueuePos = (rate - bitsInQueue) >> 3; + + if (outputLength <= availableBits) + { + SqueezePartialState(dataQueuePos, (int)outputLength >> 3, output, offset); + bitsInQueue -= (int)outputLength; + return; + } + + SqueezePartialState(dataQueuePos, availableBits >> 3, output, offset); + offset += availableBits >> 3; + outputLength -= availableBits; + bitsInQueue = 0; + } + + while (outputLength >= rate) + { + KeccakPermutation(state); + SqueezeFullState(output, offset); + offset += rate >> 3; + outputLength -= rate; + } + + if (outputLength > 0) + { + KeccakPermutation(state); + SqueezePartialState(0, (int)outputLength >> 3, output, offset); + bitsInQueue = rate - (int)outputLength; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + protected void Squeeze(Span output) + { + int rateBytes = rate >> 3; + + if (!squeezing) + { + PadAndSwitchToSqueezingPhase(); + } + else if (bitsInQueue > 0) + { + int available = bitsInQueue >> 3; + int dataQueuePos = rateBytes - available; + + if (output.Length <= available) + { + SqueezePartialState(dataQueuePos, output.Length, output); + bitsInQueue -= output.Length << 3; + return; + } + + SqueezePartialState(dataQueuePos, available, output); + output = output[available..]; + bitsInQueue = 0; + } + + while (output.Length >= rateBytes) + { + KeccakPermutation(state); + SqueezeFullState(output); + output = output[rateBytes..]; + } + + if (!output.IsEmpty) + { + KeccakPermutation(state); + SqueezePartialState(0, output.Length, output); + bitsInQueue = rate - (output.Length << 3); + } + } +#endif + + private void EnsureQueuePacked(int byteOff, int byteLen) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + if (BitConverter.IsLittleEndian) + return; +#endif + + int from = (byteOff + 7) / 8; + int to = (byteOff + byteLen + 7) / 8; + Pack.UInt64_To_LE(state, from, to - from, dataQueue, from * 8); + } + + private void SqueezeFullState(byte[] output, int outputOff) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + SqueezeFullState(output.AsSpan(outputOff)); +#else + Pack.UInt64_To_LE(state, 0, rate >> 6, output, outputOff); +#endif + } + + private void SqueezePartialState(int byteOff, int byteLen, byte[] output, int outputOff) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + SqueezePartialState(byteOff, byteLen, output.AsSpan(outputOff)); +#else + EnsureQueuePacked(byteOff, byteLen); + Array.Copy(dataQueue, byteOff, output, outputOff, byteLen); +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SqueezeFullState(Span output) + { + var fullState = state.AsSpan(0, rate >> 6); + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.AsBytes(fullState).CopyTo(output); + } + else + { + Pack.UInt64_To_LE(fullState, output); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SqueezePartialState(int byteOff, int byteLen, Span output) + { + if (BitConverter.IsLittleEndian) + { + MemoryMarshal.AsBytes(state.AsSpan()).Slice(byteOff, byteLen).CopyTo(output); + } + else + { + EnsureQueuePacked(byteOff, byteLen); + dataQueue.AsSpan(byteOff, byteLen).CopyTo(output); + } + } +#endif + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void KeccakAbsorb(ReadOnlySpan data) + { + if (BitConverter.IsLittleEndian) + { + Nat.XorTo64(rate >> 6, MemoryMarshal.Cast(data), state); + } + else + { + int count = rate >> 6, off = 0; + for (int i = 0; i < count; ++i) + { + state[i] ^= Pack.LE_To_UInt64(data[off..]); + off += 8; + } + } + + KeccakPermutation(state); + } +#else + private void KeccakAbsorb(byte[] data, int off) + { + int count = rate >> 6; + for (int i = 0; i < count; ++i) + { + state[i] ^= Pack.LE_To_UInt64(data, off); + off += 8; + } + + KeccakPermutation(state); + } +#endif + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void KeccakPermutation(Span A) +#else + internal static void KeccakPermutation(ulong[] A) +#endif + { + var bounds = A[24]; + + ulong a00 = A[ 0], a01 = A[ 1], a02 = A[ 2], a03 = A[ 3], a04 = A[ 4]; + ulong a05 = A[ 5], a06 = A[ 6], a07 = A[ 7], a08 = A[ 8], a09 = A[ 9]; + ulong a10 = A[10], a11 = A[11], a12 = A[12], a13 = A[13], a14 = A[14]; + ulong a15 = A[15], a16 = A[16], a17 = A[17], a18 = A[18], a19 = A[19]; + ulong a20 = A[20], a21 = A[21], a22 = A[22], a23 = A[23], a24 = A[24]; + + for (int i = 0; i < 24; i++) + { + // theta + ulong c0 = a00 ^ a05 ^ a10 ^ a15 ^ a20; + ulong c1 = a01 ^ a06 ^ a11 ^ a16 ^ a21; + ulong c2 = a02 ^ a07 ^ a12 ^ a17 ^ a22; + ulong c3 = a03 ^ a08 ^ a13 ^ a18 ^ a23; + ulong c4 = a04 ^ a09 ^ a14 ^ a19 ^ a24; + + ulong d1 = Longs.RotateLeft(c1, 1) ^ c4; + ulong d2 = Longs.RotateLeft(c2, 1) ^ c0; + ulong d3 = Longs.RotateLeft(c3, 1) ^ c1; + ulong d4 = Longs.RotateLeft(c4, 1) ^ c2; + ulong d0 = Longs.RotateLeft(c0, 1) ^ c3; + + a00 ^= d1; a05 ^= d1; a10 ^= d1; a15 ^= d1; a20 ^= d1; + a01 ^= d2; a06 ^= d2; a11 ^= d2; a16 ^= d2; a21 ^= d2; + a02 ^= d3; a07 ^= d3; a12 ^= d3; a17 ^= d3; a22 ^= d3; + a03 ^= d4; a08 ^= d4; a13 ^= d4; a18 ^= d4; a23 ^= d4; + a04 ^= d0; a09 ^= d0; a14 ^= d0; a19 ^= d0; a24 ^= d0; + + // rho/pi + c1 = Longs.RotateLeft(a01, 1); + a01 = Longs.RotateLeft(a06, 44); + a06 = Longs.RotateLeft(a09, 20); + a09 = Longs.RotateLeft(a22, 61); + a22 = Longs.RotateLeft(a14, 39); + a14 = Longs.RotateLeft(a20, 18); + a20 = Longs.RotateLeft(a02, 62); + a02 = Longs.RotateLeft(a12, 43); + a12 = Longs.RotateLeft(a13, 25); + a13 = Longs.RotateLeft(a19, 8); + a19 = Longs.RotateLeft(a23, 56); + a23 = Longs.RotateLeft(a15, 41); + a15 = Longs.RotateLeft(a04, 27); + a04 = Longs.RotateLeft(a24, 14); + a24 = Longs.RotateLeft(a21, 2); + a21 = Longs.RotateLeft(a08, 55); + a08 = Longs.RotateLeft(a16, 45); + a16 = Longs.RotateLeft(a05, 36); + a05 = Longs.RotateLeft(a03, 28); + a03 = Longs.RotateLeft(a18, 21); + a18 = Longs.RotateLeft(a17, 15); + a17 = Longs.RotateLeft(a11, 10); + a11 = Longs.RotateLeft(a07, 6); + a07 = Longs.RotateLeft(a10, 3); + a10 = c1; + + // chi + c0 = a00 ^ (~a01 & a02); + c1 = a01 ^ (~a02 & a03); + a02 ^= ~a03 & a04; + a03 ^= ~a04 & a00; + a04 ^= ~a00 & a01; + a00 = c0; + a01 = c1; + + c0 = a05 ^ (~a06 & a07); + c1 = a06 ^ (~a07 & a08); + a07 ^= ~a08 & a09; + a08 ^= ~a09 & a05; + a09 ^= ~a05 & a06; + a05 = c0; + a06 = c1; + + c0 = a10 ^ (~a11 & a12); + c1 = a11 ^ (~a12 & a13); + a12 ^= ~a13 & a14; + a13 ^= ~a14 & a10; + a14 ^= ~a10 & a11; + a10 = c0; + a11 = c1; + + c0 = a15 ^ (~a16 & a17); + c1 = a16 ^ (~a17 & a18); + a17 ^= ~a18 & a19; + a18 ^= ~a19 & a15; + a19 ^= ~a15 & a16; + a15 = c0; + a16 = c1; + + c0 = a20 ^ (~a21 & a22); + c1 = a21 ^ (~a22 & a23); + a22 ^= ~a23 & a24; + a23 ^= ~a24 & a20; + a24 ^= ~a20 & a21; + a20 = c0; + a21 = c1; + + // iota + a00 ^= KeccakRoundConstants[i]; + } + + A[ 0] = a00; A[ 1] = a01; A[ 2] = a02; A[ 3] = a03; A[ 4] = a04; + A[ 5] = a05; A[ 6] = a06; A[ 7] = a07; A[ 8] = a08; A[ 9] = a09; + A[10] = a10; A[11] = a11; A[12] = a12; A[13] = a13; A[14] = a14; + A[15] = a15; A[16] = a16; A[17] = a17; A[18] = a18; A[19] = a19; + A[20] = a20; A[21] = a21; A[22] = a22; A[23] = a23; A[24] = a24; + } + + public virtual IMemoable Copy() + { + return new KeccakDigest(this); + } + + public virtual void Reset(IMemoable other) + { + CopyIn((KeccakDigest)other); + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/LICENSE.md b/src/core/IronPython.Modules/hashlib/BouncyCastle/LICENSE.md new file mode 100644 index 000000000..1f0172c5b --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/LICENSE.md @@ -0,0 +1,15 @@ +MIT License (https://opensource.org/licenses/MIT) + +Copyright (c) 2000-2026 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org). +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and +associated documentation files (the "Software"), to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, +sub license, and/or sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: The above copyright notice and this +permission notice shall be included in all copies or substantial portions of the Software. + +**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT +OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.** diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/README.md b/src/core/IronPython.Modules/hashlib/BouncyCastle/README.md new file mode 100644 index 000000000..107a39ae9 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/README.md @@ -0,0 +1,27 @@ +# Bouncy Castle digests + +The files in this folder implement the SHA-3, SHAKE and BLAKE2 hash algorithms used by the `_sha3` and `_blake2` modules. They are copied from [Bouncy Castle C#](https://github.com/bcgit/bc-csharp) and are covered by its MIT license, see [LICENSE.md](LICENSE.md). + +## Provenance + +Copied from commit [`4ccf1e6`](https://github.com/bcgit/bc-csharp/tree/4ccf1e6d2785aa8c3745723c21a4aa027eace7df). + +| File | Upstream path | +|---|---| +| `Blake2bDigest.cs` | `crypto/src/crypto/digests/Blake2bDigest.cs` | +| `Blake2b_X86.cs` | `crypto/src/crypto/digests/Blake2b_X86.cs` | +| `Blake2sDigest.cs` | `crypto/src/crypto/digests/Blake2sDigest.cs` | +| `Blake2s_X86.cs` | `crypto/src/crypto/digests/Blake2s_X86.cs` | +| `IDigest.cs` | `crypto/src/crypto/IDigest.cs` | +| `IMemoable.cs` | `crypto/src/util/IMemoable.cs` | +| `IXof.cs` | `crypto/src/crypto/IXof.cs` | +| `KeccakDigest.cs` | `crypto/src/crypto/digests/KeccakDigest.cs` | +| `SHA3Digest.cs` | `crypto/src/crypto/digests/SHA3Digest.cs` | +| `ShakeDigest.cs` | `crypto/src/crypto/digests/ShakeDigest.cs` | +| `LICENSE.md` | `LICENSE.md` | + +### Local changes + +The copied files are unmodified, except that every top-level `public` type (class or interface) is changed to `internal`. + +The copied files depend on a few Bouncy Castle utility classes which are implemented in [`../BouncyCastleSupport.cs`](../BouncyCastleSupport.cs) (outside this folder so the repository style is enforced). diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/SHA3Digest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/SHA3Digest.cs new file mode 100644 index 000000000..3a09aac7e --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/SHA3Digest.cs @@ -0,0 +1,236 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Math.Raw; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of SHA-3 based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + internal class Sha3Digest + : KeccakDigest + { + internal static void CalculateDigest(ulong[] input, int inputOffset, int inputLengthBits, + byte[] output, int outputOffset, int outputLengthBits) + { + int requiredInputLength = (inputLengthBits + 63) >> 6; + Check.DataLength(inputOffset > (input.Length - requiredInputLength), "input buffer too short"); + + int requiredOutputLength = (outputLengthBits + 7) >> 3; + Check.OutputLength(output, outputOffset, requiredOutputLength, "output buffer too short"); + + // Require byte-alignment (could be improved later) + if ((inputLengthBits & 7) != 0) + throw new ArgumentOutOfRangeException(nameof(inputLengthBits)); + + switch (outputLengthBits) + { + case 224: + case 256: + case 384: + case 512: + break; + default: + throw new ArgumentOutOfRangeException(nameof(outputLengthBits)); + } + + int rate = 1600 - (outputLengthBits << 1); + int rate64 = rate >> 6; + + ulong[] state = new ulong[25]; + + // ABSORB + + while (inputLengthBits >= rate) + { + Nat.XorTo64(rate64, input, inputOffset, state, 0); + inputOffset += rate64; + inputLengthBits -= rate; + + KeccakPermutation(state); + } + + int remaining64 = inputLengthBits >> 6; + int remainingPartial = inputLengthBits & 63; + + Nat.XorTo64(remaining64, input, inputOffset, state, 0); + + // If input not byte-aligned, the padding would be more complicated + ulong pad = 0b00000110UL; + if (remainingPartial != 0) + { + pad <<= remainingPartial; + pad |= input[inputOffset + remaining64] & ~(ulong.MaxValue << remainingPartial); + } + + state[remaining64] ^= pad; + state[rate64 - 1] ^= 1UL << 63; + + // SQUEEZE + + KeccakPermutation(state); + + Debug.Assert(outputLengthBits <= rate); + int count64 = outputLengthBits >> 6; + Pack.UInt64_To_LE(state, 0, count64, output, outputOffset); + if ((outputLengthBits & 32) != 0) + { + Pack.UInt32_To_LE((uint)state[count64], output, outputOffset + (count64 << 3)); + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void CalculateDigest(ReadOnlySpan input, int inputLengthBits, Span output, + int outputLengthBits) + { + int requiredInputLength = (inputLengthBits + 63) >> 6; + Check.DataLength(input, requiredInputLength, "input buffer too short"); + + int requiredOutputLength = (outputLengthBits + 7) >> 3; + Check.OutputLength(output, requiredOutputLength, "output buffer too short"); + + // Require byte-alignment (could be improved later) + if ((inputLengthBits & 7) != 0) + throw new ArgumentOutOfRangeException(nameof(inputLengthBits)); + + switch (outputLengthBits) + { + case 224: + case 256: + case 384: + case 512: + break; + default: + throw new ArgumentOutOfRangeException(nameof(outputLengthBits)); + } + + int rate = 1600 - (outputLengthBits << 1); + int rate64 = rate >> 6; + + Span state = stackalloc ulong[25]; + + // ABSORB + + while (inputLengthBits >= rate) + { + Nat.XorTo64(rate64, input, state); + input = input[rate64..]; + inputLengthBits -= rate; + + KeccakPermutation(state); + } + + int remaining64 = inputLengthBits >> 6; + int remainingPartial = inputLengthBits & 63; + + Nat.XorTo64(remaining64, input, state); + + // If input not byte-aligned, the padding would be more complicated + ulong pad = 0b00000110UL; + if (remainingPartial != 0) + { + pad <<= remainingPartial; + pad |= input[remaining64] & ~(ulong.MaxValue << remainingPartial); + } + + state[remaining64] ^= pad; + state[rate64 - 1] ^= 1UL << 63; + + // SQUEEZE + + KeccakPermutation(state); + + Debug.Assert(outputLengthBits <= rate); + int count64 = outputLengthBits >> 6; + Pack.UInt64_To_LE(state[..count64], output); + if ((outputLengthBits & 32) != 0) + { + Pack.UInt32_To_LE((uint)state[count64], output[(count64 << 3)..]); + } + } +#endif + + private static int CheckBitLength(int bitLength) + { + switch (bitLength) + { + case 224: + case 256: + case 384: + case 512: + return bitLength; + default: + throw new ArgumentException(bitLength + " not supported for SHA-3", "bitLength"); + } + } + + public Sha3Digest() + : this(256) + { + } + + public Sha3Digest(int bitLength) + : base(CheckBitLength(bitLength)) + { + } + + public Sha3Digest(Sha3Digest source) + : base(source) + { + } + + public override string AlgorithmName + { + get { return "SHA3-" + fixedOutputLength; } + } + + public override int DoFinal(byte[] output, int outOff) + { + AbsorbBits(0x02, 2); + + return base.DoFinal(output, outOff); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override int DoFinal(Span output) + { + AbsorbBits(0x02, 2); + + return base.DoFinal(output); + } +#endif + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x02 << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 2; + + if (finalBits >= 8) + { + Absorb((byte)finalInput); + finalBits -= 8; + finalInput >>= 8; + } + + return base.DoFinal(output, outOff, (byte)finalInput, finalBits); + } + + public override IMemoable Copy() + { + return new Sha3Digest(this); + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastle/ShakeDigest.cs b/src/core/IronPython.Modules/hashlib/BouncyCastle/ShakeDigest.cs new file mode 100644 index 000000000..1f075b8f2 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastle/ShakeDigest.cs @@ -0,0 +1,168 @@ +using System; +using System.Diagnostics; + +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// + /// Implementation of SHAKE based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + /// + /// + /// Following the naming conventions used in the C source code to enable easy review of the implementation. + /// + internal class ShakeDigest + : KeccakDigest, IXof + { + private static int CheckBitLength(int bitLength) + { + switch (bitLength) + { + case 128: + case 256: + return bitLength; + default: + throw new ArgumentException(bitLength + " not supported for SHAKE", "bitLength"); + } + } + + public ShakeDigest() + : this(128) + { + } + + public ShakeDigest(int bitLength) + : base(CheckBitLength(bitLength)) + { + } + + public ShakeDigest(ShakeDigest source) + : base(source) + { + } + + public override string AlgorithmName + { + get { return "SHAKE" + fixedOutputLength; } + } + + public override int GetDigestSize() + { + return fixedOutputLength >> 2; + } + + public override int DoFinal(byte[] output, int outOff) + { + return OutputFinal(output, outOff, GetDigestSize()); + } + + public virtual int OutputFinal(byte[] output, int outOff, int outLen) + { + Check.OutputLength(output, outOff, outLen, "output buffer is too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return OutputFinal(output.AsSpan(outOff, outLen)); +#else + int length = Output(output, outOff, outLen); + + Reset(); + + return length; +#endif + } + + public virtual int Output(byte[] output, int outOff, int outLen) + { + Check.OutputLength(output, outOff, outLen, "output buffer is too short"); + +#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return Output(output.AsSpan(outOff, outLen)); +#else + if (!squeezing) + { + AbsorbBits(0x0F, 4); + } + + Squeeze(output, outOff, (long)outLen << 3); + + return outLen; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override int DoFinal(Span output) + { + return OutputFinal(output[..GetDigestSize()]); + } + + public virtual int OutputFinal(Span output) + { + int length = Output(output); + + Reset(); + + return length; + } + + public virtual int Output(Span output) + { + if (!squeezing) + { + AbsorbBits(0x0F, 4); + } + + Squeeze(output); + + return output.Length; + } +#endif + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected override int DoFinal(byte[] output, int outOff, byte partialByte, int partialBits) + { + return OutputFinal(output, outOff, GetDigestSize(), partialByte, partialBits); + } + + /* + * TODO Possible API change to support partial-byte suffixes. + */ + protected virtual int OutputFinal(byte[] output, int outOff, int outLen, byte partialByte, int partialBits) + { + if (partialBits < 0 || partialBits > 7) + throw new ArgumentException("must be in the range [0,7]", "partialBits"); + + int finalInput = (partialByte & ((1 << partialBits) - 1)) | (0x0F << partialBits); + Debug.Assert(finalInput >= 0); + int finalBits = partialBits + 4; + + if (finalBits >= 8) + { + Absorb((byte)finalInput); + finalBits -= 8; + finalInput >>= 8; + } + + if (finalBits > 0) + { + AbsorbBits(finalInput, finalBits); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Squeeze(output.AsSpan(outOff, outLen)); +#else + Squeeze(output, outOff, (long)outLen << 3); +#endif + + Reset(); + + return outLen; + } + + public override IMemoable Copy() + { + return new ShakeDigest(this); + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/BouncyCastleSupport.cs b/src/core/IronPython.Modules/hashlib/BouncyCastleSupport.cs new file mode 100644 index 000000000..afd989ac9 --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/BouncyCastleSupport.cs @@ -0,0 +1,224 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +// Minimal replacements for the Bouncy Castle utility classes used by the digests in the BouncyCastle folder. +// Only the members needed by those files are implemented. The digest files themselves are kept +// identical to upstream so that they can be refreshed easily (see BouncyCastle/README.md). + +using System; + +namespace Org.BouncyCastle.Crypto { + internal static class Check { + internal static void DataLength(bool condition, string message) { + if (condition) throw new ArgumentException(message); + } + + internal static void OutputLength(byte[] buf, int off, int len, string message) { + if (off > buf.Length - len) throw new ArgumentException(message); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void DataLength(ReadOnlySpan input, int len, string message) { + if (input.Length < len) throw new ArgumentException(message); + } + + internal static void OutputLength(Span output, int len, string message) { + if (output.Length < len) throw new ArgumentException(message); + } +#endif + } +} + +namespace Org.BouncyCastle.Crypto.Utilities { + internal static class Pack { + internal static uint LE_To_UInt32(byte[] bs, int off) { + return bs[off] + | (uint)bs[off + 1] << 8 + | (uint)bs[off + 2] << 16 + | (uint)bs[off + 3] << 24; + } + + internal static void LE_To_UInt32(byte[] bs, int off, uint[] ns) { + for (int i = 0; i < ns.Length; ++i) { + ns[i] = LE_To_UInt32(bs, off); + off += 4; + } + } + + internal static ulong LE_To_UInt64(byte[] bs, int off) { + uint lo = LE_To_UInt32(bs, off); + uint hi = LE_To_UInt32(bs, off + 4); + return (ulong)hi << 32 | lo; + } + + internal static void LE_To_UInt64(byte[] bs, int off, ulong[] ns) { + for (int i = 0; i < ns.Length; ++i) { + ns[i] = LE_To_UInt64(bs, off); + off += 8; + } + } + + internal static void UInt32_To_LE(uint n, byte[] bs, int off) { + bs[off] = (byte)n; + bs[off + 1] = (byte)(n >> 8); + bs[off + 2] = (byte)(n >> 16); + bs[off + 3] = (byte)(n >> 24); + } + + internal static void UInt32_To_LE(uint[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) { + for (int i = 0; i < nsLen; ++i) { + UInt32_To_LE(ns[nsOff + i], bs, bsOff); + bsOff += 4; + } + } + + internal static void UInt32_To_LE_Low(uint n, byte[] bs, int off, int len) { + for (int i = 0; i < len; ++i) { + bs[off + i] = (byte)n; + n >>= 8; + } + } + + internal static void UInt64_To_LE(ulong n, byte[] bs, int off) { + UInt32_To_LE((uint)n, bs, off); + UInt32_To_LE((uint)(n >> 32), bs, off + 4); + } + + internal static void UInt64_To_LE(ulong[] ns, int nsOff, int nsLen, byte[] bs, int bsOff) { + for (int i = 0; i < nsLen; ++i) { + UInt64_To_LE(ns[nsOff + i], bs, bsOff); + bsOff += 8; + } + } + + internal static void UInt64_To_LE_Low(ulong n, byte[] bs, int off, int len) { + for (int i = 0; i < len; ++i) { + bs[off + i] = (byte)n; + n >>= 8; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void LE_To_UInt32(ReadOnlySpan bs, Span ns) { + for (int i = 0; i < ns.Length; ++i) { + ns[i] = System.Buffers.Binary.BinaryPrimitives.ReadUInt32LittleEndian(bs.Slice(i * 4)); + } + } + + internal static ulong LE_To_UInt64(ReadOnlySpan bs) { + return System.Buffers.Binary.BinaryPrimitives.ReadUInt64LittleEndian(bs); + } + + internal static void LE_To_UInt64(ReadOnlySpan bs, Span ns) { + for (int i = 0; i < ns.Length; ++i) { + ns[i] = System.Buffers.Binary.BinaryPrimitives.ReadUInt64LittleEndian(bs.Slice(i * 8)); + } + } + + internal static void UInt32_To_LE(uint n, Span bs) { + System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(bs, n); + } + + internal static void UInt32_To_LE(ReadOnlySpan ns, Span bs) { + for (int i = 0; i < ns.Length; ++i) { + System.Buffers.Binary.BinaryPrimitives.WriteUInt32LittleEndian(bs.Slice(i * 4), ns[i]); + } + } + + internal static void UInt32_To_LE_Low(uint n, Span bs) { + for (int i = 0; i < bs.Length; ++i) { + bs[i] = (byte)n; + n >>= 8; + } + } + + internal static void UInt64_To_LE(ReadOnlySpan ns, Span bs) { + for (int i = 0; i < ns.Length; ++i) { + System.Buffers.Binary.BinaryPrimitives.WriteUInt64LittleEndian(bs.Slice(i * 8), ns[i]); + } + } + + internal static void UInt64_To_LE_Low(ulong n, Span bs) { + for (int i = 0; i < bs.Length; ++i) { + bs[i] = (byte)n; + n >>= 8; + } + } +#endif + } +} + +namespace Org.BouncyCastle.Math.Raw { + internal static class Nat { + internal static void XorTo64(int len, ulong[] x, int xOff, ulong[] z, int zOff) { + for (int i = 0; i < len; ++i) { + z[zOff + i] ^= x[xOff + i]; + } + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + internal static void XorTo64(int len, ReadOnlySpan x, Span z) { + for (int i = 0; i < len; ++i) { + z[i] ^= x[i]; + } + } +#endif + } +} + +namespace Org.BouncyCastle.Utilities { + internal static class Arrays { + internal static byte[] Clone(byte[] data) { + return data == null ? null : (byte[])data.Clone(); + } + + internal static byte[] CopyBuffer(byte[] buf) { + return (byte[])buf.Clone(); + } + + internal static void Fill(byte[] buf, byte b) { + for (int i = 0; i < buf.Length; ++i) { + buf[i] = b; + } + } + + internal static bool IsNullOrEmpty(byte[] array) { + return array == null || array.Length < 1; + } + } + + internal static class Integers { + internal static uint RotateRight(uint i, int distance) { + return (i >> distance) | (i << -distance); + } + } + + internal static class Longs { + internal static ulong RotateLeft(ulong i, int distance) { + return (i << distance) | (i >> -distance); + } + + internal static ulong RotateRight(ulong i, int distance) { + return (i >> distance) | (i << -distance); + } + } +} + +#if NETCOREAPP3_0_OR_GREATER +namespace Org.BouncyCastle.Runtime.Intrinsics { + internal static class Vector { + internal static bool IsPackedLittleEndian => BitConverter.IsLittleEndian; + } +} + +namespace Org.BouncyCastle.Runtime.Intrinsics.X86 { + internal static class Avx2 { + internal static bool IsEnabled => System.Runtime.Intrinsics.X86.Avx2.IsSupported; + } + + internal static class Sse41 { + internal static bool IsEnabled => System.Runtime.Intrinsics.X86.Sse41.IsSupported; + } +} +#endif diff --git a/src/core/IronPython.Modules/hashlib/_blake2.cs b/src/core/IronPython.Modules/hashlib/_blake2.cs new file mode 100644 index 000000000..21070bf1d --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/_blake2.cs @@ -0,0 +1,211 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; + +using IronPython.Runtime; +using IronPython.Runtime.Operations; + +using Microsoft.Scripting.Runtime; + +using Org.BouncyCastle.Crypto.Digests; + +[assembly: PythonModule("_blake2", typeof(IronPython.Modules.PythonBlake2))] +namespace IronPython.Modules { + public static class PythonBlake2 { + public const string __doc__ = "BLAKE2b and BLAKE2s hash algorithms"; + + public const int BLAKE2B_SALT_SIZE = blake2b.SALT_SIZE; + public const int BLAKE2B_PERSON_SIZE = blake2b.PERSON_SIZE; + public const int BLAKE2B_MAX_KEY_SIZE = blake2b.MAX_KEY_SIZE; + public const int BLAKE2B_MAX_DIGEST_SIZE = blake2b.MAX_DIGEST_SIZE; + + public const int BLAKE2S_SALT_SIZE = blake2s.SALT_SIZE; + public const int BLAKE2S_PERSON_SIZE = blake2s.PERSON_SIZE; + public const int BLAKE2S_MAX_KEY_SIZE = blake2s.MAX_KEY_SIZE; + public const int BLAKE2S_MAX_DIGEST_SIZE = blake2s.MAX_DIGEST_SIZE; + + [PythonType] + public sealed class blake2b { + public const int SALT_SIZE = 16; + public const int PERSON_SIZE = 16; + public const int MAX_KEY_SIZE = 64; + public const int MAX_DIGEST_SIZE = 64; + private const long MAX_NODE_OFFSET = long.MaxValue; + + private readonly Blake2bDigest _digest; + + public blake2b(IBufferProtocol? data = null, int digest_size = MAX_DIGEST_SIZE, + IBufferProtocol? key = null, IBufferProtocol? salt = null, IBufferProtocol? person = null, + int fanout = 1, int depth = 1, long leaf_size = 0, long node_offset = 0, int node_depth = 0, + int inner_size = 0, bool last_node = false) { + + CheckParameters(digest_size, MAX_DIGEST_SIZE, MAX_NODE_OFFSET, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node); + + _digest = new Blake2bDigest( + GetParameter(key, MAX_KEY_SIZE, "key", pad: false), + digest_size, + GetParameter(salt, SALT_SIZE, "salt", pad: true), + GetParameter(person, PERSON_SIZE, "person", pad: true)); + + if (data != null) update(data); + } + + private blake2b(Blake2bDigest digest) { + _digest = digest; + } + + public string name => "blake2b"; + + public int digest_size => _digest.GetDigestSize(); + + public int block_size => _digest.GetByteLength(); + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] IBufferProtocol data) { + lock (_digest) { + HashHelpers.Update(_digest, data); + } + } + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] string data) { + // TODO: error message changes in Python 3.9 + throw PythonOps.TypeError("Unicode-objects must be encoded before hashing"); + } + + [Documentation("Return the digest value as a bytes object.")] + public Bytes digest() { + var copy = CopyDigest(); + var res = new byte[copy.GetDigestSize()]; + copy.DoFinal(res, 0); + return Bytes.Make(res); + } + + [Documentation("Return the digest value as a string of hexadecimal digits.")] + public string hexdigest() => HashHelpers.ToHex(digest().UnsafeByteArray); + + [Documentation("Return a copy of the hash object.")] + public blake2b copy() => new blake2b(CopyDigest()); + + private Blake2bDigest CopyDigest() { + lock (_digest) { + return new Blake2bDigest(_digest); + } + } + } + + [PythonType] + public sealed class blake2s { + public const int SALT_SIZE = 8; + public const int PERSON_SIZE = 8; + public const int MAX_KEY_SIZE = 32; + public const int MAX_DIGEST_SIZE = 32; + private const long MAX_NODE_OFFSET = (1L << 48) - 1; + + private readonly Blake2sDigest _digest; + + public blake2s(IBufferProtocol? data = null, int digest_size = MAX_DIGEST_SIZE, + IBufferProtocol? key = null, IBufferProtocol? salt = null, IBufferProtocol? person = null, + int fanout = 1, int depth = 1, long leaf_size = 0, long node_offset = 0, int node_depth = 0, + int inner_size = 0, bool last_node = false) { + + CheckParameters(digest_size, MAX_DIGEST_SIZE, MAX_NODE_OFFSET, fanout, depth, leaf_size, node_offset, node_depth, inner_size, last_node); + + _digest = new Blake2sDigest( + GetParameter(key, MAX_KEY_SIZE, "key", pad: false), + digest_size, + GetParameter(salt, SALT_SIZE, "salt", pad: true), + GetParameter(person, PERSON_SIZE, "person", pad: true)); + + if (data != null) update(data); + } + + private blake2s(Blake2sDigest digest) { + _digest = digest; + } + + public string name => "blake2s"; + + public int digest_size => _digest.GetDigestSize(); + + public int block_size => _digest.GetByteLength(); + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] IBufferProtocol data) { + lock (_digest) { + HashHelpers.Update(_digest, data); + } + } + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] string data) { + throw PythonOps.TypeError("Unicode-objects must be encoded before hashing"); + } + + [Documentation("Return the digest value as a bytes object.")] + public Bytes digest() { + var copy = CopyDigest(); + var res = new byte[copy.GetDigestSize()]; + copy.DoFinal(res, 0); + return Bytes.Make(res); + } + + [Documentation("Return the digest value as a string of hexadecimal digits.")] + public string hexdigest() => HashHelpers.ToHex(digest().UnsafeByteArray); + + [Documentation("Return a copy of the hash object.")] + public blake2s copy() => new blake2s(CopyDigest()); + + private Blake2sDigest CopyDigest() { + lock (_digest) { + return new Blake2sDigest(_digest); + } + } + } + + private static void CheckParameters(int digest_size, int maxDigestSize, long maxNodeOffset, int fanout, int depth, long leaf_size, + long node_offset, int node_depth, int inner_size, bool last_node) { + + if (digest_size < 1 || digest_size > maxDigestSize) + throw PythonOps.ValueError("digest_size must be between 1 and {0} bytes", maxDigestSize); + if (fanout < 0 || fanout > 255) + throw PythonOps.ValueError("fanout must be between 0 and 255"); + if (depth < 1 || depth > 255) + throw PythonOps.ValueError("depth must be between 1 and 255"); + if (leaf_size < 0 || leaf_size > uint.MaxValue) + throw PythonOps.OverflowError("leaf_size is too large"); + if (node_offset < 0 || node_offset > maxNodeOffset) + throw PythonOps.OverflowError("node_offset is too large"); + if (node_depth < 0 || node_depth > 255) + throw PythonOps.ValueError("node_depth must be between 0 and 255"); + if (inner_size < 0 || inner_size > maxDigestSize) + throw PythonOps.ValueError("inner_size must be between 0 and is {0}", maxDigestSize); + + // The underlying implementation does not support the tree hashing mode. + if (fanout != 1 || depth != 1 || leaf_size != 0 || node_offset != 0 || node_depth != 0 || inner_size != 0 || last_node) + throw new NotImplementedException("BLAKE2 tree hashing parameters are not supported"); + } + + private static byte[]? GetParameter(IBufferProtocol? value, int maxSize, string name, bool pad) { + if (value is null) return null; + + byte[] bytes; + using (var buffer = value.GetBuffer()) { + bytes = buffer.ToArray(); + } + + if (bytes.Length > maxSize) + throw PythonOps.ValueError("maximum {0} length is {1} bytes", name, maxSize); + if (bytes.Length == 0) + return null; + if (pad && bytes.Length < maxSize) + Array.Resize(ref bytes, maxSize); + + return bytes; + } + } +} diff --git a/src/core/IronPython.Modules/hashlib/_hashlib.cs b/src/core/IronPython.Modules/hashlib/_hashlib.cs index f463224ca..127a0cc85 100644 --- a/src/core/IronPython.Modules/hashlib/_hashlib.cs +++ b/src/core/IronPython.Modules/hashlib/_hashlib.cs @@ -41,9 +41,9 @@ internal HashBase(string name, int blocksize, int digestsize) { [Documentation("update(string) -> None (update digest with string data)")] public void update([NotNone] IBufferProtocol data) { using var buffer = data.GetBuffer(); - byte[] bytes = buffer.ToArray(); + byte[] bytes = buffer.AsUnsafeArray() ?? buffer.ToArray(); lock (_hasher) { - _hasher.TransformBlock(bytes, 0, bytes.Length, bytes, 0); + _hasher.TransformBlock(bytes, 0, bytes.Length, null, 0); } } public void update([NotNone] string data) { @@ -58,16 +58,7 @@ public Bytes digest() { } [Documentation("hexdigest() -> string (current digest as hex digits)")] - public string hexdigest() { - T copy = CloneHasher(); - copy.TransformFinalBlock(_empty, 0, 0); - - StringBuilder result = new StringBuilder(2 * copy.Hash.Length); - for (int i = 0; i < copy.Hash.Length; i++) { - result.Append(copy.Hash[i].ToString("x2")); - } - return result.ToString(); - } + public string hexdigest() => HashHelpers.ToHex(digest().UnsafeByteArray); public abstract HashBase copy(); @@ -106,4 +97,26 @@ protected T CloneHasher() { return clone; } } + + internal static class HashHelpers { + internal static void Update(Org.BouncyCastle.Crypto.IDigest digest, IBufferProtocol data) { + using var buffer = data.GetBuffer(); +#if NETCOREAPP + if (buffer.IsCContiguous()) { + digest.BlockUpdate(buffer.AsReadOnlySpan()); + return; + } +#endif + byte[] bytes = buffer.AsUnsafeArray() ?? buffer.ToArray(); + digest.BlockUpdate(bytes, 0, bytes.Length); + } + + internal static string ToHex(byte[] bytes) { + StringBuilder result = new StringBuilder(2 * bytes.Length); + for (int i = 0; i < bytes.Length; i++) { + result.Append(bytes[i].ToString("x2")); + } + return result.ToString(); + } + } } diff --git a/src/core/IronPython.Modules/hashlib/_sha3.cs b/src/core/IronPython.Modules/hashlib/_sha3.cs new file mode 100644 index 000000000..efb43a47e --- /dev/null +++ b/src/core/IronPython.Modules/hashlib/_sha3.cs @@ -0,0 +1,178 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using IronPython.Runtime; +using IronPython.Runtime.Operations; + +using Microsoft.Scripting.Runtime; + +using Org.BouncyCastle.Crypto.Digests; + +[assembly: PythonModule("_sha3", typeof(IronPython.Modules.PythonSha3))] +namespace IronPython.Modules { + public static class PythonSha3 { + public const string __doc__ = "SHA-3 and SHAKE hash algorithms"; + + [PythonHidden] + public abstract class Sha3Base { + private protected readonly KeccakDigest _digest; + + private protected Sha3Base(KeccakDigest digest) { + _digest = digest; + } + + public abstract string name { get; } + + public int block_size => _digest.GetByteLength(); + + public int _rate_bits => _digest.GetByteLength() * 8; + + public int _capacity_bits => 1600 - _rate_bits; + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] IBufferProtocol data) { + lock (_digest) { + HashHelpers.Update(_digest, data); + } + } + + [Documentation("Update this hash object's state with the provided bytes-like object.")] + public void update([NotNone] string data) { + throw PythonOps.TypeError("Unicode-objects must be encoded before hashing"); + } + + private protected T CopyDigest() where T : KeccakDigest { + lock (_digest) { + return (T)_digest.Copy(); + } + } + } + + [PythonHidden] + public abstract class Sha3FixedBase : Sha3Base { + private protected Sha3FixedBase(int bitLength) : base(new Sha3Digest(bitLength)) { } + + private protected Sha3FixedBase(Sha3Digest digest) : base(digest) { } + + public int digest_size => _digest.GetDigestSize(); + + public Bytes _suffix => Bytes.Make([0x06]); + + [Documentation("Return the digest value as a bytes object.")] + public Bytes digest() { + var copy = CopyDigest(); + var res = new byte[copy.GetDigestSize()]; + copy.DoFinal(res, 0); + return Bytes.Make(res); + } + + [Documentation("Return the digest value as a string of hexadecimal digits.")] + public string hexdigest() => HashHelpers.ToHex(digest().UnsafeByteArray); + } + + [PythonHidden] + public abstract class ShakeBase : Sha3Base { + private protected ShakeBase(int bitLength) : base(new ShakeDigest(bitLength)) { } + + private protected ShakeBase(ShakeDigest digest) : base(digest) { } + + public int digest_size => 0; + + public Bytes _suffix => Bytes.Make([0x1f]); + + [Documentation("Return the digest value as a bytes object.")] + public Bytes digest(int length) { + if (length < 0) throw PythonOps.ValueError("length must be non-negative"); + if (length >= 1 << 29) throw PythonOps.ValueError("length is too large"); + var copy = CopyDigest(); + var res = new byte[length]; + copy.OutputFinal(res, 0, length); + return Bytes.Make(res); + } + + [Documentation("Return the digest value as a string of hexadecimal digits.")] + public string hexdigest(int length) => HashHelpers.ToHex(digest(length).UnsafeByteArray); + } + + [PythonType] + public sealed class sha3_224 : Sha3FixedBase { + public sha3_224() : base(224) { } + public sha3_224([NotNone] IBufferProtocol data) : this() { update(data); } + public sha3_224([NotNone] string data) : this() { update(data); } + private sha3_224(Sha3Digest digest) : base(digest) { } + + public override string name => "sha3_224"; + + [Documentation("Return a copy of the hash object.")] + public sha3_224 copy() => new sha3_224(CopyDigest()); + } + + [PythonType] + public sealed class sha3_256 : Sha3FixedBase { + public sha3_256() : base(256) { } + public sha3_256([NotNone] IBufferProtocol data) : this() { update(data); } + public sha3_256([NotNone] string data) : this() { update(data); } + private sha3_256(Sha3Digest digest) : base(digest) { } + + public override string name => "sha3_256"; + + [Documentation("Return a copy of the hash object.")] + public sha3_256 copy() => new sha3_256(CopyDigest()); + } + + [PythonType] + public sealed class sha3_384 : Sha3FixedBase { + public sha3_384() : base(384) { } + public sha3_384([NotNone] IBufferProtocol data) : this() { update(data); } + public sha3_384([NotNone] string data) : this() { update(data); } + private sha3_384(Sha3Digest digest) : base(digest) { } + + public override string name => "sha3_384"; + + [Documentation("Return a copy of the hash object.")] + public sha3_384 copy() => new sha3_384(CopyDigest()); + } + + [PythonType] + public sealed class sha3_512 : Sha3FixedBase { + public sha3_512() : base(512) { } + public sha3_512([NotNone] IBufferProtocol data) : this() { update(data); } + public sha3_512([NotNone] string data) : this() { update(data); } + private sha3_512(Sha3Digest digest) : base(digest) { } + + public override string name => "sha3_512"; + + [Documentation("Return a copy of the hash object.")] + public sha3_512 copy() => new sha3_512(CopyDigest()); + } + + [PythonType] + public sealed class shake_128 : ShakeBase { + public shake_128() : base(128) { } + public shake_128([NotNone] IBufferProtocol data) : this() { update(data); } + public shake_128([NotNone] string data) : this() { update(data); } + private shake_128(ShakeDigest digest) : base(digest) { } + + public override string name => "shake_128"; + + [Documentation("Return a copy of the hash object.")] + public shake_128 copy() => new shake_128(CopyDigest()); + } + + [PythonType] + public sealed class shake_256 : ShakeBase { + public shake_256() : base(256) { } + public shake_256([NotNone] IBufferProtocol data) : this() { update(data); } + public shake_256([NotNone] string data) : this() { update(data); } + private shake_256(ShakeDigest digest) : base(digest) { } + + public override string name => "shake_256"; + + [Documentation("Return a copy of the hash object.")] + public shake_256 copy() => new shake_256(CopyDigest()); + } + } +} diff --git a/tests/suite/modules/misc/test__blake2.py b/tests/suite/modules/misc/test__blake2.py new file mode 100644 index 000000000..b4d5ac1a2 --- /dev/null +++ b/tests/suite/modules/misc/test__blake2.py @@ -0,0 +1,80 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +''' +Tests for the _blake2 module which are not covered by test_hashlib from the StdLib. The StdLib +tests which check salt, person and parameter validation also use tree hashing which is not +supported. Expected values were generated with CPython 3.12. +''' + +import _blake2 +import unittest + +from iptest import is_cli, run_test + +def data(n): + return bytes((i * 7 + 3) & 0xff for i in range(n)) + +PARAMS = [ + ('blake2b', {'salt': b'salt'}, 0, 'e13acc0cbc033165279da570723a79e4f35d37141b094225b4d26d18e9a6fa7ad1a74ae3ed749b7aa9beb70a25e73d35b2e4a94477a8d54f00330331481c013a'), + ('blake2b', {'salt': b'salt'}, 200, 'c068456b3c55510c2766676e8ea00122628ce5cef06e82a1007c25c940e4fd885464fd2f1b80d395f8fec6c73c52a382b0c508b7f13dc729a912d5198090a439'), + ('blake2b', {'salt': b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f', 'person': b'me'}, 0, 'ca6aa446967c6f33d49e373173186554863b28ed672f4ed8a3641f23d66a4412c97f6b481fadace0560af4975125c7b40195eceef5508d560b5e0b5a72ee6fe3'), + ('blake2b', {'salt': b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f', 'person': b'me'}, 200, '50624555fe019e59e6c1bfb37ccf4a85579d71020d6422159477b5df2c9886ca8ff6b4c090c4f0b201db05907a84b37bc69bbb06f47a4744ddfdbd38f8a41ecf'), + ('blake2b', {'key': b'k', 'salt': b's', 'person': b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f', 'digest_size': 33}, 0, 'eefb2381791f179994c8be25dca7c0877fbe91096b2d9a4670190ad6f61e177297'), + ('blake2b', {'key': b'k', 'salt': b's', 'person': b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f', 'digest_size': 33}, 200, '764f4fbf33a2afbcafb25a511c6f34b3ea10975684c4e67036c922d0bf8c649b32'), + ('blake2s', {'salt': b'salt'}, 0, '57a3dca94c3e8c943d2007ad780932ff95f585ddc7c9dca3a0d9f2481b9ab4cc'), + ('blake2s', {'salt': b'salt'}, 200, '3b2edb1052b39c7647ad808e6f7d9ea7b28363f5e874538be7468cf0b2c70170'), + ('blake2s', {'salt': b'\x00\x01\x02\x03\x04\x05\x06\x07', 'person': b'me'}, 0, 'ced8049ec88e4dd872b5b038161dd8706ce9b386d3d2ab6cca127e16ece2c950'), + ('blake2s', {'salt': b'\x00\x01\x02\x03\x04\x05\x06\x07', 'person': b'me'}, 200, '195a611949a7860107838cb7366f5940208eb0c05328f97944b3d4959971a5a3'), + ('blake2s', {'key': b'k', 'salt': b's', 'person': b'\x00\x01\x02\x03\x04\x05\x06\x07', 'digest_size': 17}, 0, 'aae1c76391b9f3e5285a8c1d8bac9f7649'), + ('blake2s', {'key': b'k', 'salt': b's', 'person': b'\x00\x01\x02\x03\x04\x05\x06\x07', 'digest_size': 17}, 200, 'cfb552122d9f5ada0bc95cece7d44d861d'), +] + +class _Blake2Test(unittest.TestCase): + + def test_constants(self): + self.assertEqual(_blake2.BLAKE2B_SALT_SIZE, 16) + self.assertEqual(_blake2.BLAKE2B_PERSON_SIZE, 16) + self.assertEqual(_blake2.BLAKE2B_MAX_KEY_SIZE, 64) + self.assertEqual(_blake2.BLAKE2B_MAX_DIGEST_SIZE, 64) + self.assertEqual(_blake2.BLAKE2S_SALT_SIZE, 8) + self.assertEqual(_blake2.BLAKE2S_PERSON_SIZE, 8) + self.assertEqual(_blake2.BLAKE2S_MAX_KEY_SIZE, 32) + self.assertEqual(_blake2.BLAKE2S_MAX_DIGEST_SIZE, 32) + + def test_parameters(self): + for name, kwargs, n, hexdigest in PARAMS: + with self.subTest(name=name, kwargs=kwargs, n=n): + ctor = getattr(_blake2, name) + self.assertEqual(ctor(data(n), **kwargs).hexdigest(), hexdigest) + h = ctor(**kwargs) + h.update(data(n)) + self.assertEqual(h.copy().hexdigest(), hexdigest) + + def test_errors(self): + for ctor in [_blake2.blake2b, _blake2.blake2s]: + with self.subTest(ctor=ctor): + self.assertRaises(ValueError, ctor, digest_size=0) + self.assertRaises(ValueError, ctor, digest_size=ctor.MAX_DIGEST_SIZE + 1) + self.assertRaises(ValueError, ctor, key=b'x' * (ctor.MAX_KEY_SIZE + 1)) + self.assertRaises(ValueError, ctor, salt=b'x' * (ctor.SALT_SIZE + 1)) + self.assertRaises(ValueError, ctor, person=b'x' * (ctor.PERSON_SIZE + 1)) + self.assertRaises(ValueError, ctor, fanout=256) + self.assertRaises(ValueError, ctor, depth=0) + self.assertRaises(ValueError, ctor, node_depth=256) + self.assertRaises(ValueError, ctor, inner_size=ctor.MAX_DIGEST_SIZE + 1) + self.assertRaises((ValueError, OverflowError), ctor, leaf_size=-1) + self.assertRaises(OverflowError, ctor, leaf_size=1<<32) + self.assertRaises((ValueError, OverflowError), ctor, node_offset=-1) + self.assertRaises(OverflowError, _blake2.blake2s, node_offset=1<<48) + + @unittest.skipUnless(is_cli, 'IronPython does not support tree hashing') + def test_tree_hashing_not_supported(self): + for ctor in [_blake2.blake2b, _blake2.blake2s]: + with self.subTest(ctor=ctor): + self.assertRaises(NotImplementedError, ctor, fanout=2) + self.assertRaises(NotImplementedError, ctor, last_node=True) + self.assertRaises(NotImplementedError, ctor, node_offset=1) + +run_test(__name__) diff --git a/tests/suite/stdlib/test_hashlib.py b/tests/suite/stdlib/test_hashlib.py new file mode 100644 index 000000000..80b7050b6 --- /dev/null +++ b/tests/suite/stdlib/test_hashlib.py @@ -0,0 +1,35 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the Apache 2.0 License. +# See the LICENSE file in the project root for more information. + +## +## Run selected tests from test_hashlib from StdLib +## + +import sys + +from iptest import is_ironpython, generate_suite, run_test + +import test.test_hashlib + +def load_tests(loader, standard_tests, pattern): + tests = loader.loadTestsFromModule(test.test_hashlib, pattern=pattern) + + if is_ironpython: + failing_tests = [] + if sys.version_info >= (3, 6): + failing_tests += [ + test.test_hashlib.HashLibTestCase('test_blake2b'), # NotImplementedError: BLAKE2 tree hashing parameters are not supported + test.test_hashlib.HashLibTestCase('test_blake2s'), # NotImplementedError: BLAKE2 tree hashing parameters are not supported + test.test_hashlib.HashLibTestCase('test_case_blake2b_all_parameters'), # NotImplementedError: BLAKE2 tree hashing parameters are not supported + test.test_hashlib.HashLibTestCase('test_case_blake2s_all_parameters'), # NotImplementedError: BLAKE2 tree hashing parameters are not supported + ] + + skip_tests = [] + + return generate_suite(tests, failing_tests, skip_tests) + + else: + return tests + +run_test(__name__)