From 95a7590043c9bd2ceda95a06df09c2422ade0bed Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Fri, 30 Sep 2022 17:38:59 +0700 Subject: Port Blake2xsDigest from bc-java - refactor Blake2bDigest, Blake2sDigest --- crypto/src/crypto/digests/Blake2bDigest.cs | 105 ++++---- crypto/src/crypto/digests/Blake2sDigest.cs | 258 ++++++++++--------- crypto/src/crypto/digests/Blake2xsDigest.cs | 368 ++++++++++++++++++++++++++++ 3 files changed, 566 insertions(+), 165 deletions(-) create mode 100644 crypto/src/crypto/digests/Blake2xsDigest.cs (limited to 'crypto/src') diff --git a/crypto/src/crypto/digests/Blake2bDigest.cs b/crypto/src/crypto/digests/Blake2bDigest.cs index ec80a3355..953ac0062 100644 --- a/crypto/src/crypto/digests/Blake2bDigest.cs +++ b/crypto/src/crypto/digests/Blake2bDigest.cs @@ -1,4 +1,7 @@ using System; +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; @@ -39,7 +42,7 @@ namespace Org.BouncyCastle.Crypto.Digests * BLAKE2b is optimized for 64-bit platforms and produces digests of any size * between 1 and 64 bytes. */ - public class Blake2bDigest + public sealed class Blake2bDigest : IDigest { // Blake2b Initialization Vector: @@ -276,12 +279,10 @@ namespace Org.BouncyCastle.Crypto.Digests * * @param b the input byte to be entered. */ - public virtual void Update(byte b) + public void Update(byte b) { - int remainingLength = 0; // left bytes of buffer - // process the buffer if full else add to buffer: - remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + int remainingLength = BLOCK_LENGTH_BYTES - bufferPos; if (remainingLength == 0) { // full buffer t0 += BLOCK_LENGTH_BYTES; @@ -289,7 +290,11 @@ namespace Org.BouncyCastle.Crypto.Digests { // if message > 2^64 t1++; } +#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; @@ -298,7 +303,6 @@ namespace Org.BouncyCastle.Crypto.Digests { buffer[bufferPos] = b; bufferPos++; - return; } } @@ -309,11 +313,14 @@ namespace Org.BouncyCastle.Crypto.Digests * @param offset the offset into the byte array where the data starts. * @param len the length of the data. */ - public virtual void BlockUpdate(byte[] message, int offset, int len) + 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) @@ -323,8 +330,7 @@ namespace Org.BouncyCastle.Crypto.Digests remainingLength = BLOCK_LENGTH_BYTES - bufferPos; if (remainingLength < len) { // full buffer + at least 1 byte - Array.Copy(message, offset, buffer, bufferPos, - remainingLength); + Array.Copy(message, offset, buffer, bufferPos, remainingLength); t0 += BLOCK_LENGTH_BYTES; if (t0 == 0) { // if message > 2^64 @@ -359,10 +365,11 @@ namespace Org.BouncyCastle.Crypto.Digests // 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 virtual void BlockUpdate(ReadOnlySpan input) + public void BlockUpdate(ReadOnlySpan input) { if (input.IsEmpty) return; @@ -382,7 +389,7 @@ namespace Org.BouncyCastle.Crypto.Digests { // if message > 2^64 t1++; } - Compress(buffer, 0); + Compress(buffer); bufferPos = 0; Array.Clear(buffer, 0, buffer.Length);// clear buffer } @@ -422,8 +429,11 @@ namespace Org.BouncyCastle.Crypto.Digests * @param out the array the digest is to be copied into. * @param outOffset the offset into the out array the digest is to start at. */ - public virtual int DoFinal(byte[] output, int outOffset) + public int DoFinal(byte[] output, int outOffset) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return DoFinal(output.AsSpan(outOffset)); +#else f0 = 0xFFFFFFFFFFFFFFFFUL; t0 += (ulong)bufferPos; if (bufferPos > 0 && t0 == 0) @@ -448,10 +458,11 @@ namespace Org.BouncyCastle.Crypto.Digests Reset(); return digestLength; +#endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public virtual int DoFinal(Span output) + public int DoFinal(Span output) { f0 = 0xFFFFFFFFFFFFFFFFUL; t0 += (ulong)bufferPos; @@ -459,7 +470,7 @@ namespace Org.BouncyCastle.Crypto.Digests { t1++; } - Compress(buffer, 0); + Compress(buffer); Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null Array.Clear(internalState, 0, internalState.Length); @@ -485,7 +496,7 @@ namespace Org.BouncyCastle.Crypto.Digests * The key, the salt and the personal string will * remain for further computations. */ - public virtual void Reset() + public void Reset() { bufferPos = 0; f0 = 0L; @@ -501,25 +512,26 @@ namespace Org.BouncyCastle.Crypto.Digests Init(); } - private void Compress(byte[] message, int messagePos) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Compress(ReadOnlySpan message) { InitializeInternalState(); - ulong[] m = new ulong[16]; - Pack.LE_To_UInt64(message, messagePos, m); + Span m = stackalloc ulong[16]; + Pack.LE_To_UInt64(message, m); for (int round = 0; round < ROUNDS; round++) { // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1 - G(m[blake2b_sigma[round,0]], m[blake2b_sigma[round,1]], 0, 4, 8, 12); - G(m[blake2b_sigma[round,2]], m[blake2b_sigma[round,3]], 1, 5, 9, 13); - G(m[blake2b_sigma[round,4]], m[blake2b_sigma[round,5]], 2, 6, 10, 14); - G(m[blake2b_sigma[round,6]], m[blake2b_sigma[round,7]], 3, 7, 11, 15); + G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12); + G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13); + G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14); + G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15); // G apply to diagonals of internalState: - G(m[blake2b_sigma[round,8]], m[blake2b_sigma[round,9]], 0, 5, 10, 15); - G(m[blake2b_sigma[round,10]], m[blake2b_sigma[round,11]], 1, 6, 11, 12); - G(m[blake2b_sigma[round,12]], m[blake2b_sigma[round,13]], 2, 7, 8, 13); - G(m[blake2b_sigma[round,14]], m[blake2b_sigma[round,15]], 3, 4, 9, 14); + G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15); + G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12); + G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13); + G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14); } // update chain values: @@ -528,27 +540,26 @@ namespace Org.BouncyCastle.Crypto.Digests chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8]; } } - -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - private void Compress(ReadOnlySpan message) +#else + private void Compress(byte[] message, int messagePos) { InitializeInternalState(); - Span m = stackalloc ulong[16]; - Pack.LE_To_UInt64(message, m); + ulong[] m = new ulong[16]; + Pack.LE_To_UInt64(message, messagePos, m); for (int round = 0; round < ROUNDS; round++) { // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1 - G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12); - G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13); - G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14); - G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15); + G(m[blake2b_sigma[round,0]], m[blake2b_sigma[round,1]], 0, 4, 8, 12); + G(m[blake2b_sigma[round,2]], m[blake2b_sigma[round,3]], 1, 5, 9, 13); + G(m[blake2b_sigma[round,4]], m[blake2b_sigma[round,5]], 2, 6, 10, 14); + G(m[blake2b_sigma[round,6]], m[blake2b_sigma[round,7]], 3, 7, 11, 15); // G apply to diagonals of internalState: - G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15); - G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12); - G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13); - G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14); + G(m[blake2b_sigma[round,8]], m[blake2b_sigma[round,9]], 0, 5, 10, 15); + G(m[blake2b_sigma[round,10]], m[blake2b_sigma[round,11]], 1, 6, 11, 12); + G(m[blake2b_sigma[round,12]], m[blake2b_sigma[round,13]], 2, 7, 8, 13); + G(m[blake2b_sigma[round,14]], m[blake2b_sigma[round,15]], 3, 4, 9, 14); } // update chain values: @@ -559,6 +570,9 @@ namespace Org.BouncyCastle.Crypto.Digests } #endif +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif private void G(ulong m1, ulong m2, int posA, int posB, int posC, int posD) { internalState[posA] = internalState[posA] + internalState[posB] + m1; @@ -581,17 +595,14 @@ namespace Org.BouncyCastle.Crypto.Digests * * @return the algorithm name */ - public virtual string AlgorithmName - { - get { return "BLAKE2b"; } - } + public string AlgorithmName => "BLAKE2b"; /** * return the size, in bytes, of the digest produced by this message digest. * * @return the size, in bytes, of the digest produced by this message digest. */ - public virtual int GetDigestSize() + public int GetDigestSize() { return digestLength; } @@ -602,7 +613,7 @@ namespace Org.BouncyCastle.Crypto.Digests * * @return byte length of the digests internal buffer. */ - public virtual int GetByteLength() + public int GetByteLength() { return BLOCK_LENGTH_BYTES; } @@ -611,7 +622,7 @@ namespace Org.BouncyCastle.Crypto.Digests * Overwrite the key * if it is no longer used (zeroization) */ - public virtual void ClearKey() + public void ClearKey() { if (key != null) { @@ -624,7 +635,7 @@ namespace Org.BouncyCastle.Crypto.Digests * Overwrite the salt (pepper) if it * is secret and no longer used (zeroization) */ - public virtual void ClearSalt() + public void ClearSalt() { if (salt != null) { diff --git a/crypto/src/crypto/digests/Blake2sDigest.cs b/crypto/src/crypto/digests/Blake2sDigest.cs index 187808aa0..a6ee75af5 100644 --- a/crypto/src/crypto/digests/Blake2sDigest.cs +++ b/crypto/src/crypto/digests/Blake2sDigest.cs @@ -1,4 +1,7 @@ using System; +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER +using System.Runtime.CompilerServices; +#endif using Org.BouncyCastle.Crypto.Utilities; using Org.BouncyCastle.Utilities; @@ -40,7 +43,7 @@ namespace Org.BouncyCastle.Crypto.Digests * BLAKE2s is optimized for 32-bit platforms and produces digests of any size * between 1 and 32 bytes. */ - public class Blake2sDigest + public sealed class Blake2sDigest : IDigest { /** @@ -83,16 +86,13 @@ namespace Org.BouncyCastle.Crypto.Digests private byte[] 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; - */ + // 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; // 0-255 + private int leafLength = 0; + private long nodeOffset = 0L; + private int nodeDepth = 0; + private int innerHashLength = 0; /** * Whenever this buffer overflows, it will be processed in the Compress() @@ -145,8 +145,19 @@ namespace Org.BouncyCastle.Crypto.Digests this.keyLength = digest.keyLength; this.key = Arrays.Clone(digest.key); this.digestLength = digest.digestLength; + this.internalState = Arrays.Clone(digest.internalState); this.chainValue = Arrays.Clone(digest.chainValue); + this.t0 = digest.t0; + this.t1 = digest.t1; + this.f0 = digest.f0; + this.salt = Arrays.Clone(digest.salt); this.personalization = Arrays.Clone(digest.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; } /** @@ -159,10 +170,9 @@ namespace Org.BouncyCastle.Crypto.Digests 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"); - buffer = new byte[BLOCK_LENGTH_BYTES]; - keyLength = 0; digestLength = digestBits / 8; - Init(); + + Init(null, null, null); } /** @@ -176,21 +186,7 @@ namespace Org.BouncyCastle.Crypto.Digests */ public Blake2sDigest(byte[] key) { - buffer = new byte[BLOCK_LENGTH_BYTES]; - if (key != null) - { - if (key.Length > 32) - throw new ArgumentException("Keys > 32 are not supported"); - - this.key = new byte[key.Length]; - Array.Copy(key, 0, this.key, 0, key.Length); - - keyLength = key.Length; - Array.Copy(key, 0, buffer, 0, key.Length); - bufferPos = BLOCK_LENGTH_BYTES; // zero padding - } - digestLength = 32; - Init(); + Init(null, null, key); } /** @@ -206,65 +202,79 @@ namespace Org.BouncyCastle.Crypto.Digests * @param salt 8 bytes or null * @param personalization 8 bytes or null */ - public Blake2sDigest(byte[] key, int digestBytes, byte[] salt, - byte[] personalization) + public Blake2sDigest(byte[] key, int digestBytes, byte[] salt, byte[] personalization) { if (digestBytes < 1 || digestBytes > 32) throw new ArgumentException("Invalid digest length (required: 1 - 32)"); this.digestLength = digestBytes; - this.buffer = new byte[BLOCK_LENGTH_BYTES]; - if (salt != null) - { - if (salt.Length != 8) - throw new ArgumentException("Salt length must be exactly 8 bytes"); + Init(salt, personalization, key); + } - this.salt = new byte[8]; - Array.Copy(salt, 0, this.salt, 0, salt.Length); - } - if (personalization != null) - { - if (personalization.Length != 8) - throw new ArgumentException("Personalization length must be exactly 8 bytes"); + // XOF root hash parameters + internal Blake2sDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization, long offset) + { + digestLength = digestBytes; + nodeOffset = offset; - this.personalization = new byte[8]; - Array.Copy(personalization, 0, this.personalization, 0, personalization.Length); - } - if (key != null) - { - if (key.Length > 32) - throw new ArgumentException("Keys > 32 bytes are not supported"); + Init(salt, personalization, key); + } - this.key = new byte[key.Length]; - Array.Copy(key, 0, this.key, 0, key.Length); + // 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(null, null, null); + } + // initialize the digest's parameters + private void Init(byte[] salt, byte[] personalization, byte[] key) + { + buffer = new byte[BLOCK_LENGTH_BYTES]; + + if (key != null && key.Length > 0) + { keyLength = key.Length; - Array.Copy(key, 0, buffer, 0, key.Length); + if (keyLength > 32) + throw new ArgumentException("Keys > 32 bytes are not supported"); + + this.key = new byte[keyLength]; + Array.Copy(key, 0, this.key, 0, keyLength); + Array.Copy(key, 0, buffer, 0, keyLength); bufferPos = BLOCK_LENGTH_BYTES; // zero padding } - Init(); - } - // initialize chainValue - private void Init() - { if (chainValue == null) { chainValue = new uint[8]; - chainValue[0] = blake2s_IV[0] ^ (uint)(digestLength | (keyLength << 8) | 0x1010000); - // 0x1010000 = ((fanout << 16) | (depth << 24)); - // with fanout = 1; depth = 0; - chainValue[1] = blake2s_IV[1];// ^ leafLength; with leafLength = 0; - chainValue[2] = blake2s_IV[2];// ^ nodeOffset; with nodeOffset = 0; - chainValue[3] = blake2s_IV[3];// ^ ( (nodeOffset << 32) | (nodeDepth << 16) | (innerHashLength << 24) ); - // with nodeDepth = 0; innerHashLength = 0; + chainValue[0] = blake2s_IV[0] + ^ (uint)(digestLength | (keyLength << 8) | ((fanout << 16) | (depth << 24))); + chainValue[1] = blake2s_IV[1] ^ (uint)leafLength; + + int nofHi = (int)(nodeOffset >> 32); + int nofLo = (int)nodeOffset; + chainValue[2] = blake2s_IV[2] ^ (uint)nofLo; + chainValue[3] = blake2s_IV[3] ^ (uint)(nofHi | (nodeDepth << 16) | (innerHashLength << 24)); chainValue[4] = blake2s_IV[4]; chainValue[5] = blake2s_IV[5]; if (salt != null) { + if (salt.Length != 8) + throw new ArgumentException("Salt length must be exactly 8 bytes"); + + this.salt = new byte[8]; + Array.Copy(salt, 0, this.salt, 0, salt.Length); + chainValue[4] ^= Pack.LE_To_UInt32(salt, 0); chainValue[5] ^= Pack.LE_To_UInt32(salt, 4); } @@ -273,6 +283,12 @@ namespace Org.BouncyCastle.Crypto.Digests chainValue[7] = blake2s_IV[7]; if (personalization != null) { + if (personalization.Length != 8) + throw new ArgumentException("Personalization length must be exactly 8 bytes"); + + this.personalization = new byte[8]; + Array.Copy(personalization, 0, this.personalization, 0, personalization.Length); + chainValue[6] ^= Pack.LE_To_UInt32(personalization, 0); chainValue[7] ^= Pack.LE_To_UInt32(personalization, 4); } @@ -295,12 +311,10 @@ namespace Org.BouncyCastle.Crypto.Digests * * @param b the input byte to be entered. */ - public virtual void Update(byte b) + public void Update(byte b) { - int remainingLength; // left bytes of buffer - // process the buffer if full else add to buffer: - remainingLength = BLOCK_LENGTH_BYTES - bufferPos; + int remainingLength = BLOCK_LENGTH_BYTES - bufferPos; if (remainingLength == 0) { // full buffer t0 += BLOCK_LENGTH_BYTES; @@ -308,7 +322,11 @@ namespace Org.BouncyCastle.Crypto.Digests { // if message > 2^32 t1++; } +#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; @@ -327,11 +345,14 @@ namespace Org.BouncyCastle.Crypto.Digests * @param offset the offset into the byte array where the data starts. * @param len the length of the data. */ - public virtual void BlockUpdate(byte[] message, int offset, int len) + 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) @@ -379,10 +400,11 @@ namespace Org.BouncyCastle.Crypto.Digests Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos); bufferPos += offset + len - messagePos; +#endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public virtual void BlockUpdate(ReadOnlySpan input) + public void BlockUpdate(ReadOnlySpan input) { if (input.IsEmpty) return; @@ -402,7 +424,7 @@ namespace Org.BouncyCastle.Crypto.Digests { // if message > 2^32 t1++; } - Compress(buffer, 0); + Compress(buffer); bufferPos = 0; Array.Clear(buffer, 0, buffer.Length);// clear buffer } @@ -443,8 +465,11 @@ namespace Org.BouncyCastle.Crypto.Digests * @param out the array the digest is to be copied into. * @param outOffset the offset into the out array the digest is to start at. */ - public virtual int DoFinal(byte[] output, int outOffset) + public int DoFinal(byte[] output, int outOffset) { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return DoFinal(output.AsSpan(outOffset)); +#else f0 = 0xFFFFFFFFU; t0 += (uint)bufferPos; // bufferPos may be < 64, so (t0 == 0) does not work @@ -471,10 +496,11 @@ namespace Org.BouncyCastle.Crypto.Digests Reset(); return digestLength; +#endif } #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - public virtual int DoFinal(Span output) + public int DoFinal(Span output) { f0 = 0xFFFFFFFFU; t0 += (uint)bufferPos; @@ -484,7 +510,7 @@ namespace Org.BouncyCastle.Crypto.Digests { t1++; } - Compress(buffer, 0); + Compress(buffer); Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null Array.Clear(internalState, 0, internalState.Length); @@ -509,7 +535,7 @@ namespace Org.BouncyCastle.Crypto.Digests * Reset the digest back to its initial state. The key, the salt and the * personal string will remain for further computations. */ - public virtual void Reset() + public void Reset() { bufferPos = 0; f0 = 0; @@ -522,28 +548,30 @@ namespace Org.BouncyCastle.Crypto.Digests Array.Copy(key, 0, buffer, 0, key.Length); bufferPos = BLOCK_LENGTH_BYTES; // zero padding } - Init(); + + Init(this.salt, this.personalization, this.key); } - private void Compress(byte[] message, int messagePos) +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + private void Compress(ReadOnlySpan message) { InitializeInternalState(); - uint[] m = new uint[16]; - Pack.LE_To_UInt32(message, messagePos, m); + Span m = stackalloc uint[16]; + Pack.LE_To_UInt32(message, m); for (int round = 0; round < ROUNDS; round++) { // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1 - G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12); - G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13); - G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14); - G(m[blake2s_sigma[round,6]], m[blake2s_sigma[round,7]], 3, 7, 11, 15); + G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12); + G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13); + G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14); + G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15); // G apply to diagonals of internalState: - G(m[blake2s_sigma[round,8]], m[blake2s_sigma[round,9]], 0, 5, 10, 15); - G(m[blake2s_sigma[round,10]], m[blake2s_sigma[round,11]], 1, 6, 11, 12); - G(m[blake2s_sigma[round,12]], m[blake2s_sigma[round,13]], 2, 7, 8, 13); - G(m[blake2s_sigma[round,14]], m[blake2s_sigma[round,15]], 3, 4, 9, 14); + G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15); + G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12); + G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13); + G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14); } // update chain values: @@ -552,27 +580,26 @@ namespace Org.BouncyCastle.Crypto.Digests chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8]; } } - -#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER - private void Compress(ReadOnlySpan message) +#else + private void Compress(byte[] message, int messagePos) { InitializeInternalState(); - Span m = stackalloc uint[16]; - Pack.LE_To_UInt32(message, m); + uint[] m = new uint[16]; + Pack.LE_To_UInt32(message, messagePos, m); for (int round = 0; round < ROUNDS; round++) { // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1 - G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12); - G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13); - G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14); - G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15); + G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12); + G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13); + G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14); + G(m[blake2s_sigma[round,6]], m[blake2s_sigma[round,7]], 3, 7, 11, 15); // G apply to diagonals of internalState: - G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15); - G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12); - G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13); - G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14); + G(m[blake2s_sigma[round,8]], m[blake2s_sigma[round,9]], 0, 5, 10, 15); + G(m[blake2s_sigma[round,10]], m[blake2s_sigma[round,11]], 1, 6, 11, 12); + G(m[blake2s_sigma[round,12]], m[blake2s_sigma[round,13]], 2, 7, 8, 13); + G(m[blake2s_sigma[round,14]], m[blake2s_sigma[round,15]], 3, 4, 9, 14); } // update chain values: @@ -583,21 +610,19 @@ namespace Org.BouncyCastle.Crypto.Digests } #endif +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif private void G(uint m1, uint m2, int posA, int posB, int posC, int posD) { internalState[posA] = internalState[posA] + internalState[posB] + m1; - internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 16); + internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 16); internalState[posC] = internalState[posC] + internalState[posD]; - internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 12); + internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 12); internalState[posA] = internalState[posA] + internalState[posB] + m2; - internalState[posD] = rotr32(internalState[posD] ^ internalState[posA], 8); + internalState[posD] = Integers.RotateRight(internalState[posD] ^ internalState[posA], 8); internalState[posC] = internalState[posC] + internalState[posD]; - internalState[posB] = rotr32(internalState[posB] ^ internalState[posC], 7); - } - - private uint rotr32(uint x, int rot) - { - return x >> rot | x << -rot; + internalState[posB] = Integers.RotateRight(internalState[posB] ^ internalState[posC], 7); } /** @@ -605,17 +630,14 @@ namespace Org.BouncyCastle.Crypto.Digests * * @return the algorithm name */ - public virtual string AlgorithmName - { - get { return "BLAKE2s"; } - } + public string AlgorithmName => "BLAKE2s"; /** * Return the size in bytes of the digest produced by this message digest. * * @return the size in bytes of the digest produced by this message digest. */ - public virtual int GetDigestSize() + public int GetDigestSize() { return digestLength; } @@ -626,7 +648,7 @@ namespace Org.BouncyCastle.Crypto.Digests * * @return byte length of the digest's internal buffer. */ - public virtual int GetByteLength() + public int GetByteLength() { return BLOCK_LENGTH_BYTES; } @@ -634,7 +656,7 @@ namespace Org.BouncyCastle.Crypto.Digests /** * Overwrite the key if it is no longer used (zeroization). */ - public virtual void ClearKey() + public void ClearKey() { if (key != null) { @@ -647,7 +669,7 @@ namespace Org.BouncyCastle.Crypto.Digests * Overwrite the salt (pepper) if it is secret and no longer used * (zeroization). */ - public virtual void ClearSalt() + public void ClearSalt() { if (salt != null) { diff --git a/crypto/src/crypto/digests/Blake2xsDigest.cs b/crypto/src/crypto/digests/Blake2xsDigest.cs new file mode 100644 index 000000000..43bfbac18 --- /dev/null +++ b/crypto/src/crypto/digests/Blake2xsDigest.cs @@ -0,0 +1,368 @@ +using System; + +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/blake2x.pdf + */ + + /** + * Implementation of the eXtendable Output Function (XOF) BLAKE2xs. + *

+ * BLAKE2xs offers a built-in keying mechanism to be used directly + * for authentication ("Prefix-MAC") rather than a HMAC construction. + *

+ * BLAKE2xs offers a built-in support for a salt for randomized hashing + * and a personal string for defining a unique hash function for each application. + *

+ * BLAKE2xs is optimized for 32-bit platforms and produces digests of any size + * between 1 and 2^16-2 bytes. The length can also be unknown and then the maximum + * length will be 2^32 blocks of 32 bytes. + */ + public sealed class Blake2xsDigest + : IXof + { + /** + * Magic number to indicate an unknown length of digest + */ + public const int UnknownDigestLength = 65535; + + private const int DigestLength = 32; + private const long MaxNumberBlocks = 1L << 32; + + /** + * Expected digest length for the xof. It can be unknown. + */ + private int digestLength; + + /** + * Root hash that will take the updates + */ + private Blake2sDigest hash; + + /** + * Digest of the root hash + */ + private byte[] h0 = null; + + /** + * Digest of each round of the XOF + */ + private byte[] buf = new byte[32]; + + /** + * Current position for a round + */ + private int bufPos = 32; + + /** + * Overall position of the digest. It is useful when the length is known + * in advance to get last block length. + */ + private int digestPos = 0; + + /** + * Keep track of the round number to detect the end of the digest after + * 2^32 blocks of 32 bytes. + */ + private long blockPos = 0; + + /** + * Current node offset incremented by 1 every round. + */ + private long nodeOffset; + + /** + * BLAKE2xs for hashing with unknown digest length + */ + public Blake2xsDigest() + : this(UnknownDigestLength) + { + } + + /** + * BLAKE2xs for hashing + * + * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1 + */ + public Blake2xsDigest(int digestBytes) + : this(digestBytes, null, null, null) + { + } + + /** + * BLAKE2xs with key + * + * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1 + * @param key A key up to 32 bytes or null + */ + public Blake2xsDigest(int digestBytes, byte[] key) + : this(digestBytes, key, null, null) + { + } + + /** + * BLAKE2xs with key, salt and personalization + * + * @param digestBytes The desired digest length in bytes. Must be above 1 and less than 2^16-1 + * @param key A key up to 32 bytes or null + * @param salt 8 bytes or null + * @param personalization 8 bytes or null + */ + public Blake2xsDigest(int digestBytes, byte[] key, byte[] salt, byte[] personalization) + { + if (digestBytes < 1 || digestBytes > UnknownDigestLength) + throw new ArgumentException("BLAKE2xs digest length must be between 1 and 2^16-1"); + + digestLength = digestBytes; + nodeOffset = ComputeNodeOffset(); + hash = new Blake2sDigest(DigestLength, key, salt, personalization, nodeOffset); + } + + public Blake2xsDigest(Blake2xsDigest digest) + { + digestLength = digest.digestLength; + hash = new Blake2sDigest(digest.hash); + h0 = Arrays.Clone(digest.h0); + buf = Arrays.Clone(digest.buf); + bufPos = digest.bufPos; + digestPos = digest.digestPos; + blockPos = digest.blockPos; + nodeOffset = digest.nodeOffset; + } + + /** + * Return the algorithm name. + * + * @return the algorithm name + */ + public string AlgorithmName => "BLAKE2xs"; + + /** + * Return the size in bytes of the digest produced by this message digest. + * + * @return the size in bytes of the digest produced by this message digest. + */ + public int GetDigestSize() => digestLength; + + /** + * Return the size in bytes of the internal buffer the digest applies its + * compression function to. + * + * @return byte length of the digest's internal buffer. + */ + public int GetByteLength() => hash.GetByteLength(); + + /** + * Return the maximum size in bytes the digest can produce when the length + * is unknown + * + * @return byte length of the largest digest with unknown length + */ + public long GetUnknownMaxLength() + { + return MaxNumberBlocks * DigestLength; + } + + /** + * Update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void Update(byte b) + { + hash.Update(b); + } + + /** + * Update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void BlockUpdate(byte[] input, int inOff, int inLen) + { + hash.BlockUpdate(input, inOff, inLen); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void BlockUpdate(ReadOnlySpan input) + { + hash.BlockUpdate(input); + } +#endif + + /** + * Reset the digest back to its initial state. The key, the salt and the + * personal string will remain for further computations. + */ + public void Reset() + { + hash.Reset(); + + h0 = null; + bufPos = DigestLength; + digestPos = 0; + blockPos = 0; + nodeOffset = ComputeNodeOffset(); + } + + /** + * Close the digest, producing the final digest value. The doFinal() call + * leaves the digest reset. Key, salt and personal string remain. + * + * @param out the array the digest is to be copied into. + * @param outOffset the offset into the out array the digest is to start at. + */ + public int DoFinal(byte[] output, int outOff) + { + return DoFinal(output, outOff, digestLength); + } + + /** + * Close the digest, producing the final digest value. The doFinal() call + * leaves the digest reset. Key, salt, personal string remain. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + */ + public int DoFinal(byte[] output, int outOff, int outLen) + { + int ret = DoOutput(output, outOff, outLen); + + Reset(); + + return ret; + } + + /** + * Start outputting the results of the final calculation for this digest. Unlike doFinal, this method + * will continue producing output until the Xof is explicitly reset, or signals otherwise. + * + * @param out output array to write the output bytes to. + * @param outOff offset to start writing the bytes at. + * @param outLen the number of output bytes requested. + * @return the number of bytes written + */ + public int DoOutput(byte[] output, int outOff, int outLen) + { +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + return Output(output.AsSpan(outOff, outLen)); +#else + if (h0 == null) + { + h0 = new byte[hash.GetDigestSize()]; + hash.DoFinal(h0, 0); + } + + if (digestLength != UnknownDigestLength) + { + if (digestPos + outLen > digestLength) + throw new ArgumentException("Output length is above the digest length"); + } + else if (blockPos << 5 >= GetUnknownMaxLength()) + { + throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes"); + } + + for (int i = 0; i < outLen; i++) + { + if (bufPos >= DigestLength) + { + Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset); + h.BlockUpdate(h0, 0, h0.Length); + + Arrays.Fill(buf, 0); + h.DoFinal(buf, 0); + bufPos = 0; + nodeOffset++; + blockPos++; + } + output[outOff + i] = buf[bufPos]; + bufPos++; + digestPos++; + } + + return outLen; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int DoFinal(Span output) + { + return OutputFinal(output[..digestLength]); + } + + public int OutputFinal(Span output) + { + int ret = Output(output); + + Reset(); + + return ret; + } + + public int Output(Span output) + { + int outLen = output.Length; + if (h0 == null) + { + h0 = new byte[hash.GetDigestSize()]; + hash.DoFinal(h0); + } + + if (digestLength != UnknownDigestLength) + { + if (digestPos + outLen > digestLength) + throw new ArgumentException("Output length is above the digest length"); + } + else if (blockPos << 5 >= GetUnknownMaxLength()) + { + throw new ArgumentException("Maximum length is 2^32 blocks of 32 bytes"); + } + + for (int i = 0; i < outLen; i++) + { + if (bufPos >= DigestLength) + { + Blake2sDigest h = new Blake2sDigest(ComputeStepLength(), DigestLength, nodeOffset); + h.BlockUpdate(h0); + + Arrays.Fill(buf, 0); + h.DoFinal(buf); + bufPos = 0; + nodeOffset++; + blockPos++; + } + output[i] = buf[bufPos]; + bufPos++; + digestPos++; + } + + return outLen; + } +#endif + + // get the next round length. If the length is unknown, the digest length is always the maximum. + private int ComputeStepLength() + { + if (digestLength == UnknownDigestLength) + return DigestLength; + + return System.Math.Min(DigestLength, digestLength - digestPos); + } + + private long ComputeNodeOffset() + { + return digestLength * 0x100000000L; + } + } +} -- cgit 1.4.1