From b4763830b493e40673dd6552f9f21fa44528b999 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Wed, 15 Feb 2023 01:47:29 +0700 Subject: Separate Ascon Hash, XOF --- crypto/src/crypto/digests/AsconDigest.cs | 40 +--- crypto/src/crypto/digests/AsconXof.cs | 399 +++++++++++++++++++++++++++++++ crypto/test/src/crypto/test/AsconTest.cs | 87 ++++++- 3 files changed, 485 insertions(+), 41 deletions(-) create mode 100644 crypto/src/crypto/digests/AsconXof.cs diff --git a/crypto/src/crypto/digests/AsconDigest.cs b/crypto/src/crypto/digests/AsconDigest.cs index 20d39ce77..84c9299d3 100644 --- a/crypto/src/crypto/digests/AsconDigest.cs +++ b/crypto/src/crypto/digests/AsconDigest.cs @@ -20,8 +20,6 @@ namespace Org.BouncyCastle.Crypto.Digests { AsconHash, AsconHashA, - AsconXof, - AsconXofA, } private readonly AsconParameters m_asconParameters; @@ -47,12 +45,6 @@ namespace Org.BouncyCastle.Crypto.Digests case AsconParameters.AsconHashA: ASCON_PB_ROUNDS = 8; break; - case AsconParameters.AsconXof: - ASCON_PB_ROUNDS = 12; - break; - case AsconParameters.AsconXofA: - ASCON_PB_ROUNDS = 8; - break; default: throw new ArgumentException("Invalid parameter settings for Ascon Hash"); } @@ -67,8 +59,6 @@ namespace Org.BouncyCastle.Crypto.Digests { case AsconParameters.AsconHash: return "Ascon-Hash"; case AsconParameters.AsconHashA: return "Ascon-HashA"; - case AsconParameters.AsconXof: return "Ascon-Xof"; - case AsconParameters.AsconXofA: return "Ascon-XofA"; default: throw new InvalidOperationException(); } } @@ -167,10 +157,8 @@ namespace Org.BouncyCastle.Crypto.Digests #else Check.OutputLength(output, outOff, 32, "output buffer is too short"); - m_buf[m_bufPos] = 0x80; - x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3))); + FinishAbsorbing(); - P(12); Pack.UInt64_To_BE(x0, output, outOff); for (int i = 0; i < 3; ++i) @@ -191,10 +179,8 @@ namespace Org.BouncyCastle.Crypto.Digests { Check.OutputLength(output, 32, "output buffer is too short"); - m_buf[m_bufPos] = 0x80; - x0 ^= Pack.BE_To_UInt64(m_buf) & (ulong.MaxValue << (56 - (m_bufPos << 3))); + FinishAbsorbing(); - P(12); Pack.UInt64_To_BE(x0, output); for (int i = 0; i < 3; ++i) @@ -231,25 +217,19 @@ namespace Org.BouncyCastle.Crypto.Digests x3 = 4834782570098516968UL; x4 = 3787428097924915520UL; break; - case AsconParameters.AsconXof: - x0 = 13077933504456348694UL; - x1 = 3121280575360345120UL; - x2 = 7395939140700676632UL; - x3 = 6533890155656471820UL; - x4 = 5710016986865767350UL; - break; - case AsconParameters.AsconXofA: - x0 = 4940560291654768690UL; - x1 = 14811614245468591410UL; - x2 = 17849209150987444521UL; - x3 = 2623493988082852443UL; - x4 = 12162917349548726079UL; - break; default: throw new InvalidOperationException(); } } + private void FinishAbsorbing() + { + m_buf[m_bufPos] = 0x80; + x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3))); + + P(12); + } + private void P(int nr) { //if (nr >= 8) diff --git a/crypto/src/crypto/digests/AsconXof.cs b/crypto/src/crypto/digests/AsconXof.cs new file mode 100644 index 000000000..04af0b7e3 --- /dev/null +++ b/crypto/src/crypto/digests/AsconXof.cs @@ -0,0 +1,399 @@ +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; + +namespace Org.BouncyCastle.Crypto.Digests +{ + /// ASCON v1.2 XOF, https://ascon.iaik.tugraz.at/ . + /// + /// https://csrc.nist.gov/CSRC/media/Projects/lightweight-cryptography/documents/finalist-round/updated-spec-doc/ascon-spec-final.pdf
+ /// ASCON v1.2 XOF with reference to C Reference Impl from: https://github.com/ascon/ascon-c . + ///
+ public sealed class AsconXof + : IXof + { + public enum AsconParameters + { + AsconXof, + AsconXofA, + } + + private readonly AsconParameters m_asconParameters; + private readonly int ASCON_PB_ROUNDS; + + private ulong x0; + private ulong x1; + private ulong x2; + private ulong x3; + private ulong x4; + + private readonly byte[] m_buf = new byte[8]; + private int m_bufPos = 0; + private bool m_squeezing = false; + + public AsconXof(AsconParameters parameters) + { + m_asconParameters = parameters; + switch (parameters) + { + case AsconParameters.AsconXof: + ASCON_PB_ROUNDS = 12; + break; + case AsconParameters.AsconXofA: + ASCON_PB_ROUNDS = 8; + break; + default: + throw new ArgumentException("Invalid parameter settings for Ascon XOF"); + } + Reset(); + } + + public string AlgorithmName + { + get + { + switch (m_asconParameters) + { + case AsconParameters.AsconXof: return "Ascon-Xof"; + case AsconParameters.AsconXofA: return "Ascon-XofA"; + default: throw new InvalidOperationException(); + } + } + } + + public int GetDigestSize() => 32; + + public int GetByteLength() => 8; + + public void Update(byte input) + { + if (m_squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + m_buf[m_bufPos] = input; + if (++m_bufPos == 8) + { + x0 ^= Pack.BE_To_UInt64(m_buf, 0); + P(ASCON_PB_ROUNDS); + m_bufPos = 0; + } + } + + public void BlockUpdate(byte[] input, int inOff, int inLen) + { + Check.DataLength(input, inOff, inLen, "input buffer too short"); + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + BlockUpdate(input.AsSpan(inOff, inLen)); +#else + if (m_squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + if (inLen < 1) + return; + + int available = 8 - m_bufPos; + if (inLen < available) + { + Array.Copy(input, inOff, m_buf, m_bufPos, inLen); + m_bufPos += inLen; + return; + } + + int inPos = 0; + if (m_bufPos > 0) + { + Array.Copy(input, inOff, m_buf, m_bufPos, available); + inPos += available; + x0 ^= Pack.BE_To_UInt64(m_buf, 0); + P(ASCON_PB_ROUNDS); + } + + int remaining; + while ((remaining = inLen - inPos) >= 8) + { + x0 ^= Pack.BE_To_UInt64(input, inOff + inPos); + P(ASCON_PB_ROUNDS); + inPos += 8; + } + + Array.Copy(input, inOff + inPos, m_buf, 0, remaining); + m_bufPos = remaining; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public void BlockUpdate(ReadOnlySpan input) + { + if (m_squeezing) + throw new InvalidOperationException("attempt to absorb while squeezing"); + + int available = 8 - m_bufPos; + if (input.Length < available) + { + input.CopyTo(m_buf.AsSpan(m_bufPos)); + m_bufPos += input.Length; + return; + } + + if (m_bufPos > 0) + { + input[..available].CopyTo(m_buf.AsSpan(m_bufPos)); + x0 ^= Pack.BE_To_UInt64(m_buf); + P(ASCON_PB_ROUNDS); + input = input[available..]; + } + + while (input.Length >= 8) + { + x0 ^= Pack.BE_To_UInt64(input); + P(ASCON_PB_ROUNDS); + input = input[8..]; + } + + input.CopyTo(m_buf); + m_bufPos = input.Length; + } +#endif + + public int DoFinal(byte[] output, int outOff) + { + return OutputFinal(output, outOff, GetDigestSize()); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int DoFinal(Span output) + { + int digestSize = GetDigestSize(); + + Check.OutputLength(output, digestSize, "output buffer is too short"); + + return OutputFinal(output[..digestSize]); + } +#endif + + public 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 + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int OutputFinal(Span output) + { + int length = Output(output); + + Reset(); + + return length; + } +#endif + + public int Output(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 Output(output.AsSpan(outOff, outLen)); +#else + int result = outLen; + + if (!m_squeezing) + { + FinishAbsorbing(); + + if (outLen >= 8) + { + Pack.UInt64_To_BE(x0, output, outOff); + outOff += 8; + outLen -= 8; + } + else + { + Pack.UInt64_To_BE(x0, m_buf); + m_bufPos = 0; + } + } + + if (m_bufPos < 8) + { + int available = 8 - m_bufPos; + if (outLen <= available) + { + Array.Copy(m_buf, m_bufPos, output, outOff, outLen); + m_bufPos += outLen; + return result; + } + + Array.Copy(m_buf, m_bufPos, output, outOff, available); + outOff += available; + outLen -= available; + //m_bufPos = 8; + } + + while (outLen >= 8) + { + P(ASCON_PB_ROUNDS); + Pack.UInt64_To_BE(x0, output, outOff); + outOff += 8; + outLen -= 8; + } + + if (outLen > 0) + { + P(ASCON_PB_ROUNDS); + Pack.UInt64_To_BE(x0, m_buf); + Array.Copy(m_buf, 0, output, outOff, outLen); + } + + m_bufPos = outLen; + return result; +#endif + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public int Output(Span output) + { + int result = output.Length; + + if (!m_squeezing) + { + FinishAbsorbing(); + + if (output.Length >= 8) + { + Pack.UInt64_To_BE(x0, output); + output = output[8..]; + } + else + { + Pack.UInt64_To_BE(x0, m_buf); + m_bufPos = 0; + } + } + + if (m_bufPos < 8) + { + int available = 8 - m_bufPos; + if (output.Length <= available) + { + output.CopyFrom(m_buf.AsSpan(m_bufPos)); + m_bufPos += output.Length; + return result; + } + + output[..available].CopyFrom(m_buf.AsSpan(m_bufPos)); + output = output[available..]; + //m_bufPos = 8; + } + + while (output.Length >= 8) + { + P(ASCON_PB_ROUNDS); + Pack.UInt64_To_BE(x0, output); + output = output[8..]; + } + + if (!output.IsEmpty) + { + P(ASCON_PB_ROUNDS); + Pack.UInt64_To_BE(x0, m_buf); + output.CopyFrom(m_buf); + } + + m_bufPos = output.Length; + return result; + } +#endif + + public void Reset() + { + Array.Clear(m_buf, 0, m_buf.Length); + m_bufPos = 0; + m_squeezing = false; + + switch (m_asconParameters) + { + case AsconParameters.AsconXof: + x0 = 13077933504456348694UL; + x1 = 3121280575360345120UL; + x2 = 7395939140700676632UL; + x3 = 6533890155656471820UL; + x4 = 5710016986865767350UL; + break; + case AsconParameters.AsconXofA: + x0 = 4940560291654768690UL; + x1 = 14811614245468591410UL; + x2 = 17849209150987444521UL; + x3 = 2623493988082852443UL; + x4 = 12162917349548726079UL; + break; + default: + throw new InvalidOperationException(); + } + } + + private void FinishAbsorbing() + { + m_buf[m_bufPos] = 0x80; + x0 ^= Pack.BE_To_UInt64(m_buf, 0) & (ulong.MaxValue << (56 - (m_bufPos << 3))); + + P(12); + + m_bufPos = 8; + m_squeezing = true; + } + + private void P(int nr) + { + //if (nr >= 8) + { + if (nr == 12) + { + ROUND(0xf0UL); + ROUND(0xe1UL); + ROUND(0xd2UL); + ROUND(0xc3UL); + } + ROUND(0xb4UL); + ROUND(0xa5UL); + } + ROUND(0x96UL); + ROUND(0x87UL); + ROUND(0x78UL); + ROUND(0x69UL); + ROUND(0x5aUL); + ROUND(0x4bUL); + } + +#if NETSTANDARD1_0_OR_GREATER || NETCOREAPP1_0_OR_GREATER + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void ROUND(ulong c) + { + ulong t0 = x0 ^ x1 ^ x2 ^ x3 ^ c ^ (x1 & (x0 ^ x2 ^ x4 ^ c)); + ulong t1 = x0 ^ x2 ^ x3 ^ x4 ^ c ^ ((x1 ^ x2 ^ c) & (x1 ^ x3)); + ulong t2 = x1 ^ x2 ^ x4 ^ c ^ (x3 & x4); + ulong t3 = x0 ^ x1 ^ x2 ^ c ^ (~x0 & (x3 ^ x4)); + ulong t4 = x1 ^ x3 ^ x4 ^ ((x0 ^ x4) & x1); + x0 = t0 ^ Longs.RotateRight(t0, 19) ^ Longs.RotateRight(t0, 28); + x1 = t1 ^ Longs.RotateRight(t1, 39) ^ Longs.RotateRight(t1, 61); + x2 = ~(t2 ^ Longs.RotateRight(t2, 1) ^ Longs.RotateRight(t2, 6)); + x3 = t3 ^ Longs.RotateRight(t3, 10) ^ Longs.RotateRight(t3, 17); + x4 = t4 ^ Longs.RotateRight(t4, 7) ^ Longs.RotateRight(t4, 41); + } + } +} diff --git a/crypto/test/src/crypto/test/AsconTest.cs b/crypto/test/src/crypto/test/AsconTest.cs index 95dd9c210..59e5aaa6d 100644 --- a/crypto/test/src/crypto/test/AsconTest.cs +++ b/crypto/test/src/crypto/test/AsconTest.cs @@ -24,13 +24,13 @@ namespace Org.BouncyCastle.Crypto.Tests { ImplTestVectorsHash(AsconDigest.AsconParameters.AsconHashA, "asconhasha"); ImplTestVectorsHash(AsconDigest.AsconParameters.AsconHash, "asconhash"); - ImplTestVectorsHash(AsconDigest.AsconParameters.AsconXof, "asconxof"); - ImplTestVectorsHash(AsconDigest.AsconParameters.AsconXofA, "asconxofa"); + ImplTestVectorsXof(AsconXof.AsconParameters.AsconXof, "asconxof"); + ImplTestVectorsXof(AsconXof.AsconParameters.AsconXofA, "asconxofa"); ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconHashA), 32); ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconHash), 32); - ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconXof), 32); - ImplTestExceptions(new AsconDigest(AsconDigest.AsconParameters.AsconXofA), 32); + ImplTestExceptions(new AsconXof(AsconXof.AsconParameters.AsconXof), 32); + ImplTestExceptions(new AsconXof(AsconXof.AsconParameters.AsconXofA), 32); AsconEngine asconEngine = new AsconEngine(AsconEngine.AsconParameters.ascon80pq); ImplTestExceptions(asconEngine); @@ -439,11 +439,12 @@ namespace Org.BouncyCastle.Crypto.Tests "GetOutputSize of " + asconEngine.AlgorithmName + " is incorrect for decryption"); } - private void ImplTestVectorsHash(AsconDigest.AsconParameters AsconParameters, string filename) + private void ImplTestVectorsHash(AsconDigest.AsconParameters asconParameters, string filename) { - AsconDigest Ascon = new AsconDigest(AsconParameters); + AsconDigest ascon = new AsconDigest(asconParameters); var buf = new Dictionary(); - using (var src = new StreamReader(SimpleTest.GetTestDataAsStream("crypto.ascon."+filename+"_LWC_HASH_KAT_256.txt"))) + using (var src = new StreamReader( + SimpleTest.GetTestDataAsStream("crypto.ascon." + filename + "_LWC_HASH_KAT_256.txt"))) { Dictionary map = new Dictionary(); string line; @@ -456,11 +457,50 @@ namespace Org.BouncyCastle.Crypto.Tests byte[] expected = Hex.Decode(map["MD"]); map.Clear(); - Ascon.BlockUpdate(ptByte, 0, ptByte.Length); - byte[] hash = new byte[Ascon.GetDigestSize()]; - Ascon.DoFinal(hash, 0); + ascon.BlockUpdate(ptByte, 0, ptByte.Length); + byte[] hash = new byte[ascon.GetDigestSize()]; + ascon.DoFinal(hash, 0); Assert.True(Arrays.AreEqual(expected, hash)); - Ascon.Reset(); + ascon.Reset(); + } + else + { + if (data.Length >= 3) + { + map[data[0].Trim()] = data[2].Trim(); + } + else + { + map[data[0].Trim()] = ""; + } + } + } + } + } + + private void ImplTestVectorsXof(AsconXof.AsconParameters asconParameters, string filename) + { + AsconXof ascon = new AsconXof(asconParameters); + var buf = new Dictionary(); + using (var src = new StreamReader( + SimpleTest.GetTestDataAsStream("crypto.ascon." + filename + "_LWC_HASH_KAT_256.txt"))) + { + Dictionary map = new Dictionary(); + string line; + while ((line = src.ReadLine()) != null) + { + var data = line.Split(' '); + if (data.Length == 1) + { + byte[] ptByte = Hex.Decode(map["Msg"]); + byte[] expected = Hex.Decode(map["MD"]); + map.Clear(); + + ascon.BlockUpdate(ptByte, 0, ptByte.Length); + byte[] hash = new byte[ascon.GetDigestSize()]; + ascon.DoFinal(hash, 0); + Assert.True(Arrays.AreEqual(expected, hash)); + ascon.Reset(); } else { @@ -501,5 +541,30 @@ namespace Org.BouncyCastle.Crypto.Tests //expected } } + + private void ImplTestExceptions(AsconXof asconXof, int digestSize) + { + Assert.AreEqual(digestSize, asconXof.GetDigestSize(), + asconXof.AlgorithmName + ": digest size is not correct"); + + try + { + asconXof.BlockUpdate(new byte[1], 1, 1); + Assert.Fail(asconXof.AlgorithmName + ": input for BlockUpdate is too short"); + } + catch (DataLengthException) + { + //expected + } + try + { + asconXof.DoFinal(new byte[digestSize - 1], 2); + Assert.Fail(asconXof.AlgorithmName + ": output for DoFinal is too short"); + } + catch (OutputLengthException) + { + //expected + } + } } } -- cgit 1.4.1