summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-09-03 01:32:06 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-09-03 01:32:06 +0700
commit06b3437c22c3afe1d1f5bb60c13c6b0cddf70591 (patch)
tree0c7e6e1c98558aa0a1b7dbf65e261e5078426a51
parentStream modernization (diff)
downloadBouncyCastle.NET-ed25519-06b3437c22c3afe1d1f5bb60c13c6b0cddf70591.tar.xz
Improve span-based GCM code
-rw-r--r--crypto/src/crypto/macs/GMac.cs14
-rw-r--r--crypto/src/crypto/modes/GCMBlockCipher.cs144
-rw-r--r--crypto/src/crypto/modes/gcm/GcmUtilities.cs8
3 files changed, 153 insertions, 13 deletions
diff --git a/crypto/src/crypto/macs/GMac.cs b/crypto/src/crypto/macs/GMac.cs
index aa124bb04..e1555f1e6 100644
--- a/crypto/src/crypto/macs/GMac.cs
+++ b/crypto/src/crypto/macs/GMac.cs
@@ -109,11 +109,15 @@ namespace Org.BouncyCastle.Crypto.Macs
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public int DoFinal(Span<byte> output)
         {
-            // TODO[span] call cipher.DoFinal(Span<byte) when available
-            byte[] tmp = new byte[GetMacSize()];
-            int result = DoFinal(tmp, 0);
-            tmp.CopyTo(output);
-            return result;
+            try
+            {
+                return cipher.DoFinal(output);
+            }
+            catch (InvalidCipherTextException e)
+            {
+                // Impossible in encrypt mode
+                throw new InvalidOperationException(e.ToString());
+            }
         }
 #endif
 
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs
index 05db1c2c4..2ab406fc3 100644
--- a/crypto/src/crypto/modes/GCMBlockCipher.cs
+++ b/crypto/src/crypto/modes/GCMBlockCipher.cs
@@ -638,6 +638,9 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         public int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             CheckStatus();
 
             if (totalLength == 0)
@@ -744,20 +747,118 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset(false);
 
             return resultLen;
+#endif
         }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public virtual int DoFinal(Span<byte> output)
         {
-            // TODO[span] Implement efficiently
+            CheckStatus();
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            int extra = bufOff;
+
+            if (forEncryption)
+            {
+                Check.OutputLength(output, extra + macSize, "output buffer too short");
+            }
+            else
+            {
+                if (extra < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                extra -= macSize;
+
+                Check.OutputLength(output, extra, "output buffer too short");
+            }
+
+            if (extra > 0)
+            {
+                ProcessPartial(bufBlock.AsSpan(0, extra), output);
+            }
+
+            atLength += (uint)atBlockPos;
 
-            int outputLen = GetOutputSize(0);
-            Check.OutputLength(output, outputLen, "output buffer too short");
+            if (atLength > atLengthPre)
+            {
+                /*
+                 *  Some AAD was sent after the cipher started. We determine the difference b/w the hash value
+                 *  we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at).
+                 *  Then we carry this difference forward by multiplying by H^c, where c is the number of (full or
+                 *  partial) cipher-text blocks produced, and adjust the current hash.
+                 */
 
-            byte[] bytes = new byte[outputLen];
-            int len = DoFinal(bytes, 0);
-            bytes[..len].CopyTo(output);
-            return len;
+                // Finish hash for partial AAD block
+                if (atBlockPos > 0)
+                {
+                    gHASHPartial(S_at, atBlock, 0, atBlockPos);
+                }
+
+                // Find the difference between the AAD hashes
+                if (atLengthPre > 0)
+                {
+                    GcmUtilities.Xor(S_at, S_atPre);
+                }
+
+                // Number of cipher-text blocks produced
+                long c = (long)(((totalLength * 8) + 127) >> 7);
+
+                // Calculate the adjustment factor
+                byte[] H_c = new byte[16];
+                if (exp == null)
+                {
+                    exp = new BasicGcmExponentiator();
+                    exp.Init(H);
+                }
+                exp.ExponentiateX(c, H_c);
+
+                // Carry the difference forward
+                GcmUtilities.Multiply(S_at, H_c);
+
+                // Adjust the current hash
+                GcmUtilities.Xor(S, S_at);
+            }
+
+            // Final gHASH
+            Span<byte> X = stackalloc byte[BlockSize];
+            Pack.UInt64_To_BE(atLength * 8UL, X);
+            Pack.UInt64_To_BE(totalLength * 8UL, X[8..]);
+
+            gHASHBlock(S, X);
+
+            // T = MSBt(GCTRk(J0,S))
+            Span<byte> tag = stackalloc byte[BlockSize];
+            cipher.ProcessBlock(J0, tag);
+            GcmUtilities.Xor(tag, S);
+
+            int resultLen = extra;
+
+            // We place into macBlock our calculated value for T
+            this.macBlock = new byte[macSize];
+            tag[..macSize].CopyTo(macBlock);
+
+            if (forEncryption)
+            {
+                // Append T to the message
+                macBlock.CopyTo(output[bufOff..]);
+                resultLen += macSize;
+            }
+            else
+            {
+                // Retrieve the T value from the message and compare to calculated one
+                Span<byte> msgMac = stackalloc byte[macSize];
+                bufBlock.AsSpan(extra, macSize).CopyTo(msgMac);
+                if (!Arrays.ConstantTimeAreEqual(this.macBlock, msgMac))
+                    throw new InvalidCipherTextException("mac check in GCM failed");
+            }
+
+            Reset(false);
+
+            return resultLen;
         }
 #endif
 
@@ -1110,6 +1211,26 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             cipher.ProcessBlock(counter, block);
         }
+
+        private void ProcessPartial(Span<byte> partialBlock, Span<byte> output)
+        {
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
+            GetNextCtrBlock(ctrBlock);
+
+            if (forEncryption)
+            {
+                GcmUtilities.Xor(partialBlock, ctrBlock, partialBlock.Length);
+                gHASHPartial(S, partialBlock);
+            }
+            else
+            {
+                gHASHPartial(S, partialBlock);
+                GcmUtilities.Xor(partialBlock, ctrBlock, partialBlock.Length);
+            }
+
+            partialBlock.CopyTo(output);
+            totalLength += (uint)partialBlock.Length;
+        }
 #else
         private void DecryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
         {
@@ -1316,7 +1437,6 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             cipher.ProcessBlock(counter, 0, block, 0);
         }
-#endif
 
         private void ProcessPartial(byte[] buf, int off, int len, byte[] output, int outOff)
         {
@@ -1337,6 +1457,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             Array.Copy(buf, off, output, outOff, len);
             totalLength += (uint)len;
         }
+#endif
 
         private void gHASH(byte[] Y, byte[] b, int len)
         {
@@ -1354,6 +1475,13 @@ namespace Org.BouncyCastle.Crypto.Modes
             GcmUtilities.Xor(Y, b);
             multiplier.MultiplyH(Y);
         }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void gHASHPartial(byte[] Y, ReadOnlySpan<byte> b)
+        {
+            GcmUtilities.Xor(Y, b, b.Length);
+            multiplier.MultiplyH(Y);
+        }
 #else
         private void gHASHBlock(byte[] Y, byte[] b)
         {
diff --git a/crypto/src/crypto/modes/gcm/GcmUtilities.cs b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
index 78a1f0860..1aa437fcd 100644
--- a/crypto/src/crypto/modes/gcm/GcmUtilities.cs
+++ b/crypto/src/crypto/modes/gcm/GcmUtilities.cs
@@ -288,6 +288,14 @@ namespace Org.BouncyCastle.Crypto.Modes.Gcm
             }
             while (i < 16);
         }
+
+        internal static void Xor(Span<byte> x, ReadOnlySpan<byte> y, int len)
+        {
+            for (int i = 0; i < len; ++i)
+            {
+                x[i] ^= y[i];
+            }
+        }
 #endif
 
         private static ulong ImplMul64(ulong x, ulong y)