summary refs log tree commit diff
path: root/crypto/src
diff options
context:
space:
mode:
authorgefeili <gli@keyfactor.com>2023-02-06 14:37:18 +1030
committergefeili <gli@keyfactor.com>2023-02-06 14:37:18 +1030
commit4e7ceed5e30cde7d8554bfa63285131c3762ed10 (patch)
tree83e5967c6db62d814d25282d29809a85684cd479 /crypto/src
parentInitial push for Sparkle (diff)
downloadBouncyCastle.NET-ed25519-4e7ceed5e30cde7d8554bfa63285131c3762ed10.tar.xz
Complete Sparkle tests
Diffstat (limited to 'crypto/src')
-rw-r--r--crypto/src/crypto/digests/SparkleDigest.cs14
-rw-r--r--crypto/src/crypto/engines/SparkleEngine.cs294
2 files changed, 220 insertions, 88 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;
+        }
     }
 }