diff options
-rw-r--r-- | crypto/src/crypto/digests/SparkleDigest.cs | 14 | ||||
-rw-r--r-- | crypto/src/crypto/engines/SparkleEngine.cs | 294 | ||||
-rw-r--r-- | crypto/test/src/crypto/test/SparkleTest.cs | 391 |
3 files changed, 601 insertions, 98 deletions
diff --git a/crypto/src/crypto/digests/SparkleDigest.cs b/crypto/src/crypto/digests/SparkleDigest.cs index 33c6079c8..b8134dd91 100644 --- a/crypto/src/crypto/digests/SparkleDigest.cs +++ b/crypto/src/crypto/digests/SparkleDigest.cs @@ -18,7 +18,7 @@ namespace Org.BouncyCastle.Crypto.Digests ESCH256, ESCH384 } - + private string algorithmName; private readonly uint[] state; private MemoryStream message = new MemoryStream(); private readonly int DIGEST_BYTES; @@ -41,12 +41,14 @@ namespace Org.BouncyCastle.Crypto.Digests SPARKLE_STATE = 384; SPARKLE_STEPS_SLIM = 7; SPARKLE_STEPS_BIG = 11; + algorithmName = "ESCH-256"; break; case SparkleParameters.ESCH384: ESCH_DIGEST_LEN = 384; SPARKLE_STATE = 512; SPARKLE_STEPS_SLIM = 8; SPARKLE_STEPS_BIG = 12; + algorithmName = "ESCH-384"; break; default: throw new ArgumentException("Invalid definition of SCHWAEMM instance"); @@ -129,12 +131,20 @@ namespace Org.BouncyCastle.Crypto.Digests public void BlockUpdate(byte[] input, int inOff, int len) { + if (inOff + len > input.Length) + { + throw new DataLengthException(algorithmName + " input buffer too short"); + } message.Write(input, inOff, len); } public int DoFinal(byte[] output, int outOff) { + if (outOff + DIGEST_BYTES > output.Length) + { + throw new OutputLengthException(algorithmName + " input buffer too short"); + } byte[] input = message.GetBuffer(); int inlen = (int)message.Length, i, inOff = 0; uint tmpx, tmpy; @@ -214,7 +224,7 @@ namespace Org.BouncyCastle.Crypto.Digests return DIGEST_BYTES; } - public String AlgorithmName => "Sparkle Hash"; + public string AlgorithmName => algorithmName; public void Update(byte input) diff --git a/crypto/src/crypto/engines/SparkleEngine.cs b/crypto/src/crypto/engines/SparkleEngine.cs index 5322ee464..bbdce7ef8 100644 --- a/crypto/src/crypto/engines/SparkleEngine.cs +++ b/crypto/src/crypto/engines/SparkleEngine.cs @@ -13,7 +13,7 @@ using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Engines { - public class SparkleEngine : IAeadCipher + public class SparkleEngine : IAeadBlockCipher { public enum SparkleParameters { @@ -22,17 +22,22 @@ namespace Org.BouncyCastle.Crypto.Engines SCHWAEMM192_192, SCHWAEMM256_256 } + private string algorithmName; + private bool forEncryption; private readonly uint[] state; private readonly uint[] k; private readonly uint[] npub; - private readonly byte[] tag; + private byte[] tag; + private bool initialised; private bool encrypted; private bool aadFinished; private readonly MemoryStream aadData = new MemoryStream(); + private readonly MemoryStream message = new MemoryStream(); private readonly int SCHWAEMM_KEY_LEN; private readonly int SCHWAEMM_NONCE_LEN; private readonly int SPARKLE_STEPS_SLIM; private readonly int SPARKLE_STEPS_BIG; + private readonly int KEY_BYTES; private readonly int KEY_WORDS; private readonly int TAG_WORDS; private readonly int TAG_BYTES; @@ -61,6 +66,7 @@ namespace Org.BouncyCastle.Crypto.Engines SPARKLE_CAPACITY = 128; SPARKLE_STEPS_SLIM = 7; SPARKLE_STEPS_BIG = 10; + algorithmName = "SCHWAEMM128-128"; break; case SparkleParameters.SCHWAEMM256_128: SCHWAEMM_KEY_LEN = 128; @@ -70,6 +76,7 @@ namespace Org.BouncyCastle.Crypto.Engines SPARKLE_CAPACITY = 128; SPARKLE_STEPS_SLIM = 7; SPARKLE_STEPS_BIG = 11; + algorithmName = "SCHWAEMM256-128"; break; case SparkleParameters.SCHWAEMM192_192: SCHWAEMM_KEY_LEN = 192; @@ -79,6 +86,7 @@ namespace Org.BouncyCastle.Crypto.Engines SPARKLE_CAPACITY = 192; SPARKLE_STEPS_SLIM = 7; SPARKLE_STEPS_BIG = 11; + algorithmName = "SCHWAEMM192-192"; break; case SparkleParameters.SCHWAEMM256_256: SCHWAEMM_KEY_LEN = 256; @@ -88,11 +96,13 @@ namespace Org.BouncyCastle.Crypto.Engines SPARKLE_CAPACITY = 256; SPARKLE_STEPS_SLIM = 8; SPARKLE_STEPS_BIG = 12; + algorithmName = "SCHWAEMM256-256"; break; default: throw new ArgumentException("Invalid definition of SCHWAEMM instance"); } KEY_WORDS = SCHWAEMM_KEY_LEN >> 5; + KEY_BYTES = SCHWAEMM_KEY_LEN >> 3; TAG_WORDS = SCHWAEMM_TAG_LEN >> 5; TAG_BYTES = SCHWAEMM_TAG_LEN >> 3; STATE_BRANS = SPARKLE_STATE >> 6; @@ -109,6 +119,7 @@ namespace Org.BouncyCastle.Crypto.Engines tag = new byte[TAG_BYTES]; k = new uint[KEY_WORDS]; npub = new uint[RATE_WORDS]; + initialised = false; } private uint ROT(uint x, int n) @@ -186,8 +197,15 @@ namespace Org.BouncyCastle.Crypto.Engines // only authenticated but not encrypted, into the state (in blocks of size // RATE_BYTES). Note that this function MUST NOT be called when the length of // the associated data is 0. - void ProcessAssocData(uint[] state, byte[] input, int inlen) + void ProcessAssocData(uint[] state) { + int inlen = (int)aadData.Length; + if (aadFinished || inlen == 0) + { + return; + } + aadFinished = true; + byte[] input = aadData.GetBuffer(); // Main Authentication Loop int inOff = 0, i, j; uint tmp; @@ -241,13 +259,14 @@ namespace Org.BouncyCastle.Crypto.Engines // ('input' and 'output' can be the same array, i.e. they can have the same start // address). Note that this function MUST NOT be called when the length of the // plaintext is 0. - void ProcessPlainText(uint[] state, byte[] output, byte[] input, int inOff, int inlen) + private int ProcessPlainText(uint[] state, byte[] output, byte[] input, int inOff, int inlen) { // Main Encryption Loop int outOff = 0, i, j; uint tmp1, tmp2; uint[] in32 = Pack.LE_To_UInt32(input, inOff, input.Length >> 2); uint[] out32 = new uint[output.Length >> 2]; + int rv = 0; while (inlen > RATE_BYTES) { // combined Rho and rate-whitening operation @@ -259,8 +278,16 @@ namespace Org.BouncyCastle.Crypto.Engines { tmp1 = state[i]; tmp2 = state[j]; - state[i] = state[j] ^ in32[i + (inOff >> 2)] ^ state[RATE_WORDS + i]; - state[j] ^= tmp1 ^ in32[j + (inOff >> 2)] ^ state[RATE_WORDS + CAP_INDEX(j)]; + if (forEncryption) + { + state[i] = state[j] ^ in32[i + (inOff >> 2)] ^ state[RATE_WORDS + i]; + state[j] ^= tmp1 ^ in32[j + (inOff >> 2)] ^ state[RATE_WORDS + CAP_INDEX(j)]; + } + else + { + state[i] ^= state[j] ^ in32[i + (inOff >> 2)] ^ state[RATE_WORDS + i]; + state[j] = tmp1 ^ in32[j + (inOff >> 2)] ^ state[RATE_WORDS + CAP_INDEX(j)]; + } out32[i] = in32[i] ^ tmp1; out32[j] = in32[j] ^ tmp2; } @@ -270,49 +297,18 @@ namespace Org.BouncyCastle.Crypto.Engines inlen -= RATE_BYTES; outOff += RATE_BYTES; inOff += RATE_BYTES; + rv += RATE_BYTES; + encrypted = true; } - // Encryption of Last Block - // addition of ant M2 or M3 to the state - state[STATE_WORDS - 1] ^= ((inlen < RATE_BYTES) ? _M2 : _M3); - // combined Rho and rate-whitening (incl. padding) - // Rho and rate-whitening for the encryption of the last plaintext block. Since - // this last block may require padding, it is always copied to a buffer. - uint[] buffer = new uint[RATE_WORDS]; - for (i = 0; i < inlen; ++i) - { - buffer[i >> 2] |= (input[inOff++] & 0xffu) << ((i & 3) << 3); - } - if (inlen < RATE_BYTES) - { // padding - buffer[i >> 2] |= 0x80u << ((i & 3) << 3); - } - for (i = 0, j = RATE_WORDS / 2; i < RATE_WORDS / 2; i++, j++) - { - tmp1 = state[i]; - tmp2 = state[j]; - state[i] = state[j] ^ buffer[i] ^ state[RATE_WORDS + i]; - state[j] ^= tmp1 ^ buffer[j] ^ state[RATE_WORDS + CAP_INDEX(j)]; - buffer[i] ^= tmp1; - buffer[j] ^= tmp2; - } - for (i = 0; i < inlen; ++i) - { - output[outOff++] = (byte)(buffer[i >> 2] >> ((i & 3) << 3)); - } - // execute SPARKLE with big number of steps - sparkle_opt(state, STATE_BRANS, SPARKLE_STEPS_BIG); + return rv; } public void Init(bool forEncryption, ICipherParameters param) { - /** - * Sparkle encryption and decryption is completely symmetrical, so the - * 'forEncryption' is irrelevant. - */ + this.forEncryption = forEncryption; if (!(param is ParametersWithIV)) { - throw new ArgumentException( - "Sparkle init parameters must include an IV"); + throw new ArgumentException(algorithmName + " init parameters must include an IV"); } ParametersWithIV ivParams = (ParametersWithIV)param; @@ -320,36 +316,48 @@ namespace Org.BouncyCastle.Crypto.Engines if (iv == null || iv.Length != SCHWAEMM_NONCE_LEN >> 3) { - throw new ArgumentException( - "Sparkle requires exactly 16 bytes of IV"); + throw new ArgumentException(algorithmName + " requires exactly 16 bytes of IV"); } Pack.LE_To_UInt32(iv, 0, npub, 0, RATE_WORDS); if (!(ivParams.Parameters is KeyParameter)) { - throw new ArgumentException( - "Sparkle init parameters must include a key"); + throw new ArgumentException(algorithmName + " init parameters must include a key"); } KeyParameter key = (KeyParameter)ivParams.Parameters; byte[] key8 = key.GetKey(); if (key8.Length != SCHWAEMM_KEY_LEN >> 3) { - throw new ArgumentException("Sparkle key must be 128 bits long"); + throw new ArgumentException(algorithmName + " key must be 128 bits long"); } Pack.LE_To_UInt32(key8, 0, k, 0, KEY_WORDS); - + initialised = true; reset(false); } public void ProcessAadByte(byte input) { + if (encrypted) + { + throw new ArgumentException(algorithmName + ": AAD cannot be added after reading a full block(" + + GetBlockSize() + " bytes) of input for " + (forEncryption ? "encryption" : "decryption")); + } aadData.Write(new byte[] { input }, 0, 1); } public void ProcessAadBytes(byte[] input, int inOff, int len) { + if (encrypted) + { + throw new ArgumentException(algorithmName + ": AAD cannot be added after reading a full block(" + + GetBlockSize() + " bytes) of input for " + (forEncryption ? "encryption" : "decryption")); + } + if (inOff + len > input.Length) + { + throw new DataLengthException(algorithmName + " input buffer too short"); + } aadData.Write(input, inOff, len); } @@ -362,19 +370,33 @@ namespace Org.BouncyCastle.Crypto.Engines public int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff) { - if (encrypted) + if (!initialised) { - throw new ArgumentException("Sparkle has processed encryption/decryption"); + throw new ArgumentException(algorithmName + " Need call init function before encryption/decryption"); } - byte[] ad = aadData.GetBuffer(); - int adsize = (int)aadData.Length; - if (adsize != 0) + if (inOff + len > input.Length) { - ProcessAssocData(state, ad, adsize); + throw new DataLengthException(algorithmName + " input buffer too short"); } - if (len != 0) + message.Write(input, inOff, len); + len = 0; + if ((forEncryption && (int)message.Length > GetBlockSize()) || + (!forEncryption && (int)message.Length - TAG_BYTES > GetBlockSize())) { - ProcessPlainText(state, output, input, inOff, len); + len = ((int)message.Length - (forEncryption ? 0 : TAG_BYTES)); + if (len / RATE_BYTES * RATE_BYTES + outOff > output.Length) + { + throw new OutputLengthException(algorithmName + " output buffer is too short"); + } + byte[] m = message.GetBuffer(); + ProcessAssocData(state); + if (len != 0) + { + len = ProcessPlainText(state, output, m, 0, len); + } + int mlen = (int)message.Length; + message.SetLength(0); + message.Write(m, len, mlen - len); } return len; } @@ -382,29 +404,97 @@ namespace Org.BouncyCastle.Crypto.Engines public int DoFinal(byte[] output, int outOff) { - GetMac(); - Array.Copy(tag, 0, output, outOff, TAG_BYTES); - reset(false); - return TAG_BYTES; - } - - public byte[] GetMac() - { - if (!aadFinished) + if (!initialised) { - // the key to the capacity part of the state. - uint[] buffer = new uint[TAG_WORDS]; - // to prevent (potentially) unaligned memory accesses - Array.Copy(k, 0, buffer, 0, KEY_WORDS); - // add key to the capacity-part of the state - for (int i = 0; i < KEY_WORDS; i++) + throw new ArgumentException(algorithmName + " needs call init function before dofinal"); + } + int inlen = (int)message.Length - (forEncryption ? 0 : TAG_BYTES); + if ((forEncryption && inlen + TAG_BYTES + outOff > output.Length) || + (!forEncryption && inlen + outOff > output.Length)) + { + throw new OutputLengthException("output buffer is too short"); + } + ProcessAssocData(state); + int i, j; + uint tmp1, tmp2; + byte[] input = message.GetBuffer(); + int inOff = 0; + if (encrypted || inlen != 0) + { + // Encryption of Last Block + // addition of ant M2 or M3 to the state + state[STATE_WORDS - 1] ^= ((inlen < RATE_BYTES) ? _M2 : _M3); + // combined Rho and rate-whitening (incl. padding) + // Rho and rate-whitening for the encryption of the last plaintext block. Since + // this last block may require padding, it is always copied to a buffer. + uint[] buffer = new uint[RATE_WORDS]; + for (i = 0; i < inlen; ++i) { - state[RATE_WORDS + i] ^= buffer[i]; + buffer[i >> 2] |= (input[inOff++] & 0xffu) << ((i & 3) << 3); } - aadFinished = true; + if (inlen < RATE_BYTES) + { + if (!forEncryption) + { + int tmp = (i & 3) << 3; + buffer[i >> 2] |= (state[i >> 2] >> tmp) << tmp; + tmp = (i >> 2) + 1; + Array.Copy(state, tmp, buffer, tmp, RATE_WORDS - tmp); + } + buffer[i >> 2] ^= 0x80u << ((i & 3) << 3); + } + for (i = 0, j = RATE_WORDS / 2; i < RATE_WORDS / 2; i++, j++) + { + tmp1 = state[i]; + tmp2 = state[j]; + if (forEncryption) + { + state[i] = state[j] ^ buffer[i] ^ state[RATE_WORDS + i]; + state[j] ^= tmp1 ^ buffer[j] ^ state[RATE_WORDS + CAP_INDEX(j)]; + } + else + { + state[i] ^= state[j] ^ buffer[i] ^ state[RATE_WORDS + i]; + state[j] = tmp1 ^ buffer[j] ^ state[RATE_WORDS + CAP_INDEX(j)]; + } + buffer[i] ^= tmp1; + buffer[j] ^= tmp2; + } + for (i = 0; i < inlen; ++i) + { + output[outOff++] = (byte)(buffer[i >> 2] >> ((i & 3) << 3)); + } + // execute SPARKLE with big number of steps + sparkle_opt(state, STATE_BRANS, SPARKLE_STEPS_BIG); + } + // add key to the capacity-part of the state + for (i = 0; i < KEY_WORDS; i++) + { + state[RATE_WORDS + i] ^= k[i]; } - encrypted = true; + tag = new byte[TAG_BYTES]; Pack.UInt32_To_LE(state, RATE_WORDS, TAG_WORDS, tag, 0); + if (forEncryption) + { + Array.Copy(tag, 0, output, outOff, TAG_BYTES); + inlen += TAG_BYTES; + } + else + { + for (i = 0; i < TAG_BYTES; ++i) + { + if (tag[i] != input[inlen + i]) + { + throw new ArgumentException(algorithmName + " mac does not match"); + } + } + } + reset(false); + return inlen; + } + + public byte[] GetMac() + { return tag; } @@ -423,6 +513,10 @@ namespace Org.BouncyCastle.Crypto.Engines public void Reset() { + if (!initialised) + { + throw new ArgumentException(algorithmName + " needs call init function before reset"); + } reset(true); } @@ -430,7 +524,7 @@ namespace Org.BouncyCastle.Crypto.Engines { if (clearMac) { - Arrays.Fill(tag, (byte)0); + tag = null; } // The Initialize function loads nonce and key into the state and executes the // SPARKLE permutation with the big number of steps. @@ -441,10 +535,18 @@ namespace Org.BouncyCastle.Crypto.Engines // execute SPARKLE with big number of steps sparkle_opt(state, STATE_BRANS, SPARKLE_STEPS_BIG); aadData.SetLength(0); + message.SetLength(0); encrypted = false; aadFinished = false; } - public string AlgorithmName => "Sparkle AEAD"; + public string AlgorithmName => algorithmName; + + public IBlockCipher UnderlyingCipher => throw new NotImplementedException(); + + public int GetBlockSize() + { + return RATE_BYTES; + } @@ -457,27 +559,47 @@ namespace Org.BouncyCastle.Crypto.Engines public int ProcessByte(byte input, Span<byte> output) { byte[] rv = new byte[1]; - ProcessBytes(new byte[] { input }, 0, 1, rv, 0); - rv.AsSpan(0, 1).CopyTo(output); - return 1; + int len = ProcessBytes(new byte[] { input }, 0, 1, rv, 0); + rv.AsSpan(0, len).CopyTo(output); + return len; + } public int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output) { byte[] rv = new byte[input.Length]; - ProcessBytes(input.ToArray(), 0, rv.Length, rv, 0); - rv.AsSpan(0, rv.Length).CopyTo(output); - return rv.Length; + int len = ProcessBytes(input.ToArray(), 0, rv.Length, rv, 0); + rv.AsSpan(0, len).CopyTo(output); + return len; } public int DoFinal(Span<byte> output) { - byte[] tag = GetMac(); - tag.AsSpan(0, tag.Length).CopyTo(output); - reset(false); - return tag.Length; + byte[] rv; + if (forEncryption) + { + rv = new byte[message.Length + TAG_BYTES]; + } + else + { + rv = new byte[message.Length - TAG_BYTES]; + } + int len = DoFinal(rv, 0); + rv.AsSpan(0, len).CopyTo(output); + return rv.Length; + } #endif + + public int GetKeyBytesSize() + { + return KEY_BYTES; + } + + public int GetIVBytesSize() + { + return RATE_BYTES; + } } } diff --git a/crypto/test/src/crypto/test/SparkleTest.cs b/crypto/test/src/crypto/test/SparkleTest.cs index 5028a1b15..ae7e5b3e7 100644 --- a/crypto/test/src/crypto/test/SparkleTest.cs +++ b/crypto/test/src/crypto/test/SparkleTest.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.IO; using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Crypto.Modes; namespace BouncyCastle.Crypto.Tests { @@ -23,6 +24,20 @@ namespace BouncyCastle.Crypto.Tests [Test] public override void PerformTest() { + SparkleEngine sparkle = new SparkleEngine(SparkleEngine.SparkleParameters.SCHWAEMM128_128); + testExceptions(sparkle, sparkle.GetKeyBytesSize(), sparkle.GetIVBytesSize(), sparkle.GetBlockSize()); + testParameters(sparkle, 16, 16, 16, 16); + sparkle = new SparkleEngine(SparkleEngine.SparkleParameters.SCHWAEMM192_192); + testExceptions(sparkle, sparkle.GetKeyBytesSize(), sparkle.GetIVBytesSize(), sparkle.GetBlockSize()); + testParameters(sparkle, 24, 24, 24, 24); + sparkle = new SparkleEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_128); + testExceptions(sparkle, sparkle.GetKeyBytesSize(), sparkle.GetIVBytesSize(), sparkle.GetBlockSize()); + testParameters(sparkle, 16, 32, 16, 32); + sparkle = new SparkleEngine(SparkleEngine.SparkleParameters.SCHWAEMM256_256); + testExceptions(sparkle, sparkle.GetKeyBytesSize(), sparkle.GetIVBytesSize(), sparkle.GetBlockSize()); + testParameters(sparkle, 32, 32, 32, 32); + testExceptions(new SparkleDigest(SparkleDigest.SparkleParameters.ESCH256), 32); + testExceptions(new SparkleDigest(SparkleDigest.SparkleParameters.ESCH384), 48); testVectors("128_128", SparkleEngine.SparkleParameters.SCHWAEMM128_128); testVectors("192_192", SparkleEngine.SparkleParameters.SCHWAEMM192_192); testVectors("128_256", SparkleEngine.SparkleParameters.SCHWAEMM256_128); @@ -31,7 +46,7 @@ namespace BouncyCastle.Crypto.Tests testVectors("384", SparkleDigest.SparkleParameters.ESCH384); } - private void testVectors(String filename, SparkleEngine.SparkleParameters SparkleType) + private void testVectors(string filename, SparkleEngine.SparkleParameters SparkleType) { SparkleEngine Sparkle = new SparkleEngine(SparkleType); ICipherParameters param; @@ -49,19 +64,22 @@ namespace BouncyCastle.Crypto.Tests data = line.Split(' '); if (data.Length == 1) { - //if (!map["Count"].Equals("2")) + //if (!map["Count"].Equals("562")) //{ // continue; //} - param = new ParametersWithIV(new KeyParameter(Hex.Decode(map["Key"])), Hex.Decode(map["Nonce"])); + byte[] key = Hex.Decode(map["Key"]); + byte[] nonce = Hex.Decode(map["Nonce"]); + byte[] ad = Hex.Decode(map["AD"]); + byte[] pt = Hex.Decode(map["PT"]); + byte[] ct = Hex.Decode(map["CT"]); + param = new ParametersWithIV(new KeyParameter(key), nonce); Sparkle.Init(true, param); - adByte = Hex.Decode(map["AD"]); - Sparkle.ProcessAadBytes(adByte, 0, adByte.Length); - ptByte = Hex.Decode(map["PT"]); - rv = new byte[Sparkle.GetOutputSize(ptByte.Length)]; - Sparkle.ProcessBytes(ptByte, 0, ptByte.Length, rv, 0); + Sparkle.ProcessAadBytes(ad, 0, ad.Length); + rv = new byte[Sparkle.GetOutputSize(pt.Length)]; + int len = Sparkle.ProcessBytes(pt, 0, pt.Length, rv, 0); //byte[] mac = new byte[16]; - Sparkle.DoFinal(rv, ptByte.Length); + Sparkle.DoFinal(rv, len); //foreach(byte b in Hex.Decode(map["CT"])) //{ // Console.Write(b.ToString("X2")); @@ -72,10 +90,21 @@ namespace BouncyCastle.Crypto.Tests // Console.Write(b.ToString("X2")); //} //Console.WriteLine(); - Assert.True(Arrays.AreEqual(rv, Hex.Decode(map["CT"]))); + Assert.True(Arrays.AreEqual(rv, ct)); + Sparkle.Reset(); + Sparkle.Init(false, param); + //Decrypt + Sparkle.ProcessAadBytes(ad, 0, ad.Length); + rv = new byte[pt.Length + 16]; + len = Sparkle.ProcessBytes(ct, 0, ct.Length, rv, 0); + Sparkle.DoFinal(rv, len); + byte[] pt_recovered = new byte[pt.Length]; + Array.Copy(rv, 0, pt_recovered, 0, pt.Length); + Assert.True(Arrays.AreEqual(pt, pt_recovered)); //Console.WriteLine(map["Count"] + " pass"); map.Clear(); Sparkle.Reset(); + } else { @@ -136,5 +165,347 @@ namespace BouncyCastle.Crypto.Tests } Console.WriteLine("Sparkle Hash pass"); } + + private void testExceptions(IAeadBlockCipher aeadBlockCipher, int keysize, int ivsize, int blocksize) + { + ICipherParameters param; + byte[] k = new byte[keysize]; + byte[] iv = new byte[ivsize]; + byte[] m = new byte[0]; + byte[] c1 = new byte[aeadBlockCipher.GetOutputSize(m.Length)]; + param = new ParametersWithIV(new KeyParameter(k), iv); + try + { + aeadBlockCipher.ProcessBytes(m, 0, m.Length, c1, 0); + Assert.Fail(aeadBlockCipher.AlgorithmName + " need to be initialed before ProcessBytes"); + } + catch (ArgumentException e) + { + //expected + } + + try + { + aeadBlockCipher.ProcessByte((byte)0, c1, 0); + Assert.Fail(aeadBlockCipher.AlgorithmName + " need to be initialed before ProcessByte"); + } + catch (ArgumentException e) + { + //expected + } + + try + { + aeadBlockCipher.Reset(); + Assert.Fail(aeadBlockCipher.AlgorithmName + " need to be initialed before reset"); + } + catch (ArgumentException e) + { + //expected + } + + try + { + aeadBlockCipher.DoFinal(c1, m.Length); + Assert.Fail(aeadBlockCipher.AlgorithmName + " need to be initialed before dofinal"); + } + catch (ArgumentException e) + { + //expected + } + + try + { + aeadBlockCipher.GetMac(); + aeadBlockCipher.GetOutputSize(0); + aeadBlockCipher.GetUpdateOutputSize(0); + } + catch (ArgumentException e) + { + //expected + Assert.Fail(aeadBlockCipher.AlgorithmName + " functions can be called before initialisation"); + } + Random rand = new Random(); + int randomNum; + while ((randomNum = rand.Next(100)) == keysize) ; + byte[] k1 = new byte[randomNum]; + while ((randomNum = rand.Next(100)) == ivsize) ; + byte[] iv1 = new byte[randomNum]; + try + { + aeadBlockCipher.Init(true, new ParametersWithIV(new KeyParameter(k1), iv)); + Assert.Fail(aeadBlockCipher.AlgorithmName + " k size does not match"); + } + catch (ArgumentException e) + { + //expected + } + try + { + aeadBlockCipher.Init(true, new ParametersWithIV(new KeyParameter(k), iv1)); + Assert.Fail(aeadBlockCipher.AlgorithmName + "iv size does not match"); + } + catch (ArgumentException e) + { + //expected + } + + + aeadBlockCipher.Init(true, param); + try + { + aeadBlockCipher.DoFinal(c1, m.Length); + } + catch (Exception e) + { + Assert.Fail(aeadBlockCipher.AlgorithmName + " allows no input for AAD and plaintext"); + } + byte[] mac2 = aeadBlockCipher.GetMac(); + if (mac2 == null) + { + Assert.Fail("mac should not be empty after dofinal"); + } + if (!Arrays.AreEqual(mac2, c1)) + { + Assert.Fail("mac should be equal when calling dofinal and getMac"); + } + aeadBlockCipher.ProcessAadByte((byte)0); + byte[] mac1 = new byte[aeadBlockCipher.GetOutputSize(0)]; + aeadBlockCipher.DoFinal(mac1, 0); + if (Arrays.AreEqual(mac1, mac2)) + { + Assert.Fail("mac should not match"); + } + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessBytes(new byte[blocksize+1], 0, blocksize+1, new byte[blocksize+1], 0); + try + { + aeadBlockCipher.ProcessAadByte((byte)0); + Assert.Fail("ProcessAadByte(s) cannot be called after encryption/decryption"); + } + catch (ArgumentException e) + { + //expected + } + try + { + aeadBlockCipher.ProcessAadBytes(new byte[] { 0 }, 0, 1); + Assert.Fail("ProcessAadByte(s) cannot be called once only"); + } + catch (ArgumentException e) + { + //expected + } + + aeadBlockCipher.Reset(); + try + { + aeadBlockCipher.ProcessAadBytes(new byte[] { 0 }, 1, 1); + Assert.Fail("input for ProcessAadBytes is too short"); + } + catch (DataLengthException e) + { + //expected + } + try + { + aeadBlockCipher.ProcessBytes(new byte[] { 0 }, 1, 1, c1, 0); + Assert.Fail("input for ProcessBytes is too short"); + } + catch (DataLengthException e) + { + //expected + } + try + { + aeadBlockCipher.ProcessBytes(new byte[blocksize+1], 0, blocksize+1, new byte[blocksize+1], blocksize >> 1); + Assert.Fail("output for ProcessBytes is too short"); + } + catch (OutputLengthException e) + { + //expected + } + try + { + aeadBlockCipher.DoFinal(new byte[2], 2); + Assert.Fail("output for dofinal is too short"); + } + catch (DataLengthException e) + { + //expected + } + + mac1 = new byte[aeadBlockCipher.GetOutputSize(0)]; + mac2 = new byte[aeadBlockCipher.GetOutputSize(0)]; + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessAadBytes(new byte[] { 0, 0 }, 0, 2); + aeadBlockCipher.DoFinal(mac1, 0); + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessAadByte((byte)0); + aeadBlockCipher.ProcessAadByte((byte)0); + aeadBlockCipher.DoFinal(mac2, 0); + if (!Arrays.AreEqual(mac1, mac2)) + { + Assert.Fail("mac should match for the same AAD with different ways of inputing"); + } + + byte[] c2 = new byte[aeadBlockCipher.GetOutputSize(10)]; + byte[] c3 = new byte[aeadBlockCipher.GetOutputSize(10) + 2]; + byte[] aad2 = { 0, 1, 2, 3, 4 }; + byte[] aad3 = { 0, 0, 1, 2, 3, 4, 5 }; + byte[] m2 = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + byte[] m3 = { 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + byte[] m4 = new byte[m2.Length]; + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + int offset = aeadBlockCipher.ProcessBytes(m2, 0, m2.Length, c2, 0); + aeadBlockCipher.DoFinal(c2, offset); + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessAadBytes(aad3, 1, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(m3, 1, m2.Length, c3, 1); + aeadBlockCipher.DoFinal(c3, offset + 1); + byte[] c3_partial = new byte[c2.Length]; + Array.Copy(c3, 1, c3_partial, 0, c2.Length); + if (!Arrays.AreEqual(c2, c3_partial)) + { + Assert.Fail("mac should match for the same AAD and message with different offset for both input and output"); + } + aeadBlockCipher.Reset(); + aeadBlockCipher.Init(false, param); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(c2, 0, c2.Length, m4, 0); + aeadBlockCipher.DoFinal(m4, offset); + if (!Arrays.AreEqual(m2, m4)) + { + Assert.Fail("The encryption and decryption does not recover the plaintext"); + } + Console.WriteLine(aeadBlockCipher.AlgorithmName + " test Exceptions pass"); + c2[c2.Length - 1] ^= 1; + aeadBlockCipher.Reset(); + aeadBlockCipher.Init(false, param); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(c2, 0, c2.Length, m4, 0); + try + { + aeadBlockCipher.DoFinal(m4, offset); + Assert.Fail("The decryption should fail"); + } + catch (ArgumentException e) + { + //expected; + } + c2[c2.Length - 1] ^= 1; + + byte[] m7 = new byte[blocksize * 2]; + for (int i = 0; i < m7.Length; ++i) + { + m7[i] = (byte)rand.Next(); + } + byte[] c7 = new byte[aeadBlockCipher.GetOutputSize(m7.Length)]; + byte[] c8 = new byte[c7.Length]; + byte[] c9 = new byte[c7.Length]; + aeadBlockCipher.Init(true, param); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(m7, 0, m7.Length, c7, 0); + aeadBlockCipher.DoFinal(c7, offset); + aeadBlockCipher.Reset(); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(m7, 0, blocksize, c8, 0); + offset += aeadBlockCipher.ProcessBytes(m7, blocksize, m7.Length - blocksize, c8, offset); + aeadBlockCipher.DoFinal(c8, offset); + aeadBlockCipher.Reset(); + int split = rand.Next(blocksize * 2); + aeadBlockCipher.ProcessAadBytes(aad2, 0, aad2.Length); + offset = aeadBlockCipher.ProcessBytes(m7, 0, split, c9, 0); + offset += aeadBlockCipher.ProcessBytes(m7, split, m7.Length - split, c9, offset); + aeadBlockCipher.DoFinal(c9, offset); + if (!Arrays.AreEqual(c7, c8) || !Arrays.AreEqual(c7, c9)) + { + Assert.Fail("Splitting input of plaintext should output the same ciphertext"); + } +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + Span<byte> c4_1 = new byte[c2.Length]; + Span<byte> c4_2 = new byte[c2.Length]; + ReadOnlySpan<byte> m5 = new ReadOnlySpan<byte>(m2); + ReadOnlySpan<byte> aad4 = new ReadOnlySpan<byte>(aad2); + aeadBlockCipher.Init(true, param); + aeadBlockCipher.ProcessAadBytes(aad4); + offset = aeadBlockCipher.ProcessBytes(m5, c4_1); + aeadBlockCipher.DoFinal(c4_2); + byte[] c5 = new byte[c2.Length]; + Array.Copy(c4_1.ToArray(), 0, c5, 0, offset); + Array.Copy(c4_2.ToArray(), 0, c5, offset, c5.Length - offset); + if (!Arrays.AreEqual(c2, c5)) + { + Assert.Fail("mac should match for the same AAD and message with different offset for both input and output"); + } + aeadBlockCipher.Reset(); + aeadBlockCipher.Init(false, param); + Span<byte> m6_1 = new byte[m2.Length]; + Span<byte> m6_2 = new byte[m2.Length]; + ReadOnlySpan<byte> c6 = new ReadOnlySpan<byte>(c2); + aeadBlockCipher.ProcessAadBytes(aad4); + offset = aeadBlockCipher.ProcessBytes(c6, m6_1); + aeadBlockCipher.DoFinal(m6_2); + byte[] m6 = new byte[m2.Length]; + Array.Copy(m6_1.ToArray(), 0, m6, 0, offset); + Array.Copy(m6_2.ToArray(), 0, m6, offset, m6.Length - offset); + if (!Arrays.AreEqual(m2, m6)) + { + Assert.Fail("mac should match for the same AAD and message with different offset for both input and output"); + } +#endif + + } + + private void testParameters(SparkleEngine Sparkle, int keySize, int ivSize, int macSize, int blockSize) + { + if (Sparkle.GetKeyBytesSize() != keySize) + { + Assert.Fail("key bytes of " + Sparkle.AlgorithmName + " is not correct"); + } + if (Sparkle.GetIVBytesSize() != ivSize) + { + Assert.Fail("iv bytes of " + Sparkle.AlgorithmName + " is not correct"); + } + if (Sparkle.GetOutputSize(0) != macSize) + { + Assert.Fail("mac bytes of " + Sparkle.AlgorithmName + " is not correct"); + } + if (Sparkle.GetBlockSize() != blockSize) + { + Assert.Fail("block size of " + Sparkle.AlgorithmName + " is not correct"); + } + Console.WriteLine(Sparkle.AlgorithmName + " test Parameters pass"); + } + + private void testExceptions(IDigest digest, int digestsize) + { + if (digest.GetDigestSize() != digestsize) + { + Assert.Fail(digest.AlgorithmName + ": digest size is not correct"); + } + + try + { + digest.BlockUpdate(new byte[1], 1, 1); + Assert.Fail(digest.AlgorithmName + ": input for update is too short"); + } + catch (DataLengthException e) + { + //expected + } + try + { + digest.DoFinal(new byte[digest.GetDigestSize() - 1], 2); + Assert.Fail(digest.AlgorithmName + ": output for dofinal is too short"); + } + catch (DataLengthException e) + { + //expected + } + Console.WriteLine(digest.AlgorithmName + " test Exceptions pass"); + } + } } |