summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-08-31 01:48:36 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-08-31 01:48:36 +0700
commitf01393e0ce47b124d711e7c3512bd2fab50e98bf (patch)
treecd27c8543ef6a99b43af92dc710485481c2a0f08
parentFix exceptions (diff)
downloadBouncyCastle.NET-ed25519-f01393e0ce47b124d711e7c3512bd2fab50e98bf.tar.xz
Span-based variants for IAeadCipher.ProcessByte(s)
-rw-r--r--crypto/src/crypto/engines/ChaCha7539Engine.cs65
-rw-r--r--crypto/src/crypto/modes/CcmBlockCipher.cs30
-rw-r--r--crypto/src/crypto/modes/ChaCha20Poly1305.cs195
-rw-r--r--crypto/src/crypto/modes/EAXBlockCipher.cs104
-rw-r--r--crypto/src/crypto/modes/GCMBlockCipher.cs505
-rw-r--r--crypto/src/crypto/modes/GcmSivBlockCipher.cs74
-rw-r--r--crypto/src/crypto/modes/IAeadCipher.cs8
-rw-r--r--crypto/src/crypto/modes/KCcmBlockCipher.cs21
-rw-r--r--crypto/src/crypto/modes/OCBBlockCipher.cs69
-rw-r--r--crypto/src/util/Spans.cs18
10 files changed, 950 insertions, 139 deletions
diff --git a/crypto/src/crypto/engines/ChaCha7539Engine.cs b/crypto/src/crypto/engines/ChaCha7539Engine.cs
index d1dd9755b..a438c0bfb 100644
--- a/crypto/src/crypto/engines/ChaCha7539Engine.cs
+++ b/crypto/src/crypto/engines/ChaCha7539Engine.cs
@@ -81,16 +81,24 @@ namespace Org.BouncyCastle.Crypto.Engines
 
 			while (inLen >= 128)
             {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ProcessBlocks2(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
+#else
 				ProcessBlocks2(inBuf, inOff, outBuf, outOff);
-				inOff += 128;
+#endif
+                inOff += 128;
 				inLen -= 128;
 				outOff += 128;
 			}
 
 			if (inLen >= 64)
 			{
-				ImplProcessBlock(inBuf, inOff, outBuf, outOff);
-				inOff += 64;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                ImplProcessBlock(inBuf.AsSpan(inOff), outBuf.AsSpan(outOff));
+#else
+                ImplProcessBlock(inBuf, inOff, outBuf, outOff);
+#endif
+                inOff += 64;
 				inLen -= 64;
 				outOff += 64;
 			}
@@ -111,7 +119,8 @@ namespace Org.BouncyCastle.Crypto.Engines
 			// TODO Prevent re-use if encrypting
 		}
 
-		internal void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
             if (!initialised)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -120,10 +129,10 @@ namespace Org.BouncyCastle.Crypto.Engines
 
             Debug.Assert(index == 0);
 
-			ImplProcessBlock(inBytes, inOff, outBytes, outOff);
+            ImplProcessBlock(input, output);
         }
 
-        internal void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        internal void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
             if (!initialised)
                 throw new InvalidOperationException(AlgorithmName + " not initialised");
@@ -135,18 +144,57 @@ namespace Org.BouncyCastle.Crypto.Engines
 #if NETCOREAPP3_0_OR_GREATER
             if (Avx2.IsSupported)
             {
-                ImplProcessBlocks2_X86_Avx2(rounds, engineState, inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+                ImplProcessBlocks2_X86_Avx2(rounds, engineState, input, output);
                 return;
             }
 
             if (Sse2.IsSupported)
             {
-                ImplProcessBlocks2_X86_Sse2(rounds, engineState, inBytes.AsSpan(inOff), outBytes.AsSpan(outOff));
+                ImplProcessBlocks2_X86_Sse2(rounds, engineState, input, output);
                 return;
             }
 #endif
 
             {
+				ImplProcessBlock(input, output);
+				ImplProcessBlock(input[64..], output[64..]);
+			}
+		}
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal void ImplProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            ChaChaEngine.ChachaCore(rounds, engineState, keyStream);
+            AdvanceCounter();
+
+            for (int i = 0; i < 64; ++i)
+            {
+                output[i] = (byte)(keyStream[i] ^ input[i]);
+            }
+        }
+#else
+		internal void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+            if (LimitExceeded(64U))
+                throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
+
+            Debug.Assert(index == 0);
+
+			ImplProcessBlock(inBytes, inOff, outBytes, outOff);
+        }
+
+        internal void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            if (!initialised)
+                throw new InvalidOperationException(AlgorithmName + " not initialised");
+            if (LimitExceeded(128U))
+                throw new MaxBytesExceededException("2^38 byte limit per IV would be exceeded; Change IV");
+
+            Debug.Assert(index == 0);
+
+            {
 				ImplProcessBlock(inBytes, inOff, outBytes, outOff);
 				ImplProcessBlock(inBytes, inOff + 64, outBytes, outOff + 64);
 			}
@@ -165,6 +213,7 @@ namespace Org.BouncyCastle.Crypto.Engines
 				outBuf[outOff + i] = (byte)(keyStream[i] ^ inBuf[inOff + i]);
 			}
 		}
+#endif
 
 #if NETCOREAPP3_0_OR_GREATER
 		[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/crypto/src/crypto/modes/CcmBlockCipher.cs b/crypto/src/crypto/modes/CcmBlockCipher.cs
index 46e7b9c55..0a2adb57d 100644
--- a/crypto/src/crypto/modes/CcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/CcmBlockCipher.cs
@@ -125,22 +125,23 @@ namespace Org.BouncyCastle.Crypto.Modes
         }
 #endif
 
-        public virtual int ProcessByte(
-            byte	input,
-            byte[]	outBytes,
-            int		outOff)
+        public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
         {
             data.WriteByte(input);
 
             return 0;
         }
 
-        public virtual int ProcessBytes(
-            byte[]	inBytes,
-            int		inOff,
-            int		inLen,
-            byte[]	outBytes,
-            int		outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            data.WriteByte(input);
+
+            return 0;
+        }
+#endif
+
+        public virtual int ProcessBytes(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
         {
             Check.DataLength(inBytes, inOff, inLen, "input buffer too short");
 
@@ -149,6 +150,15 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            data.Write(input);
+
+            return 0;
+        }
+#endif
+
         public virtual int DoFinal(byte[] outBytes, int outOff)
         {
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
diff --git a/crypto/src/crypto/modes/ChaCha20Poly1305.cs b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
index 9e30dc510..299387cdf 100644
--- a/crypto/src/crypto/modes/ChaCha20Poly1305.cs
+++ b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
@@ -234,7 +234,11 @@ namespace Org.BouncyCastle.Crypto.Modes
                 if (++mBufPos == mBuf.Length)
                 {
                     mPoly1305.BlockUpdate(mBuf, 0, BufSize);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    ProcessBlock(mBuf, outBytes.AsSpan(outOff));
+#else
                     ProcessBlock(mBuf, 0, outBytes, outOff);
+#endif
                     Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
                     this.mBufPos = MacSize;
                     return BufSize;
@@ -247,7 +251,11 @@ namespace Org.BouncyCastle.Crypto.Modes
                 mBuf[mBufPos] = input;
                 if (++mBufPos == BufSize)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    ProcessBlock(mBuf, outBytes.AsSpan(outOff));
+#else
                     ProcessBlock(mBuf, 0, outBytes, outOff);
+#endif
                     mPoly1305.BlockUpdate(outBytes, outOff, BufSize);
                     this.mBufPos = 0;
                     return BufSize;
@@ -260,6 +268,46 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            CheckData();
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                mBuf[mBufPos] = input;
+                if (++mBufPos == mBuf.Length)
+                {
+                    mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                    ProcessBlock(mBuf, output);
+                    Array.Copy(mBuf, BufSize, mBuf, 0, MacSize);
+                    this.mBufPos = MacSize;
+                    return BufSize;
+                }
+
+                return 0;
+            }
+            case State.EncData:
+            {
+                mBuf[mBufPos] = input;
+                if (++mBufPos == BufSize)
+                {
+                    ProcessBlock(mBuf, output);
+                    mPoly1305.BlockUpdate(output[..BufSize]);
+                    this.mBufPos = 0;
+                    return BufSize;
+                }
+
+                return 0;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
         {
             if (null == inBytes)
@@ -280,6 +328,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             if (outOff < 0)
                 throw new ArgumentException("cannot be negative", "outOff");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff));
+#else
             CheckData();
 
             int resultLen = 0;
@@ -388,8 +439,120 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            CheckData();
+
+            int resultLen = 0;
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                int available = mBuf.Length - mBufPos;
+                if (input.Length < available)
+                {
+                    input.CopyTo(mBuf.AsSpan(mBufPos));
+                    mBufPos += input.Length;
+                    break;
+                }
+
+                if (mBufPos >= BufSize)
+                {
+                    mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                    ProcessBlock(mBuf, output);
+                    Array.Copy(mBuf, BufSize, mBuf, 0, mBufPos -= BufSize);
+                    resultLen = BufSize;
+
+                    available += BufSize;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(mBuf.AsSpan(mBufPos));
+                        mBufPos += input.Length;
+                        break;
+                    }
+                }
+
+                int inLimit1 = mBuf.Length;
+                int inLimit2 = inLimit1 + BufSize;
+
+                available = BufSize - mBufPos;
+                input[..available].CopyTo(mBuf.AsSpan(mBufPos));
+                mPoly1305.BlockUpdate(mBuf.AsSpan(0, BufSize));
+                ProcessBlock(mBuf, output[resultLen..]);
+                input = input[available..];
+                resultLen += BufSize;
+
+                while (input.Length >= inLimit2)
+                {
+                    mPoly1305.BlockUpdate(input[..(BufSize * 2)]);
+                    ProcessBlocks2(input, output[resultLen..]);
+                    input = input[(BufSize * 2)..];
+                    resultLen += BufSize * 2;
+                }
+
+                if (input.Length >= inLimit1)
+                {
+                    mPoly1305.BlockUpdate(input[..BufSize]);
+                    ProcessBlock(input, output[resultLen..]);
+                    input = input[BufSize..];
+                    resultLen += BufSize;
+                }
+
+                mBufPos = input.Length;
+                input.CopyTo(mBuf);
+                break;
+            }
+            case State.EncData:
+            {
+                int available = BufSize - mBufPos;
+                if (input.Length < available)
+                {
+                    input.CopyTo(mBuf.AsSpan(mBufPos));
+                    mBufPos += input.Length;
+                    break;
+                }
+
+                if (mBufPos > 0)
+                {
+                    input[..available].CopyTo(mBuf.AsSpan(mBufPos));
+                    ProcessBlock(mBuf, output);
+                    input = input[available..];
+                    resultLen = BufSize;
+                }
+
+                while (input.Length >= BufSize * 2)
+                {
+                    ProcessBlocks2(input, output[resultLen..]);
+                    input = input[(BufSize * 2)..];
+                    resultLen += BufSize * 2;
+                }
+
+                if (input.Length >= BufSize)
+                {
+                    ProcessBlock(input, output[resultLen..]);
+                    input = input[BufSize..];
+                    resultLen += BufSize;
+                }
+
+                mPoly1305.BlockUpdate(output[..resultLen]);
+
+                mBufPos = input.Length;
+                input.CopyTo(mBuf);
+                break;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+
+            return resultLen;
+        }
+#endif
+
         public virtual int DoFinal(byte[] outBytes, int outOff)
         {
             if (null == outBytes)
@@ -621,25 +784,25 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        private void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBytes, outOff, 64, "output buffer too short");
+            Check.OutputLength(output, 64, "output buffer too short");
 
-            mChacha20.ProcessBlock(inBytes, inOff, outBytes, outOff);
+            mChacha20.ProcessBlock(input, output);
 
             this.mDataCount = IncrementCount(mDataCount, 64U, DataLimit);
         }
 
-        private void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        private void ProcessBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBytes, outOff, 128, "output buffer too short");
+            Check.OutputLength(output, 128, "output buffer too short");
 
-            mChacha20.ProcessBlocks2(inBytes, inOff, outBytes, outOff);
+            mChacha20.ProcessBlocks2(input, output);
 
             this.mDataCount = IncrementCount(mDataCount, 128U, DataLimit);
         }
 
-#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         private void ProcessData(ReadOnlySpan<byte> input, Span<byte> output)
         {
             Check.OutputLength(output, input.Length, "output buffer too short");
@@ -649,6 +812,24 @@ namespace Org.BouncyCastle.Crypto.Modes
             this.mDataCount = IncrementCount(mDataCount, (uint)input.Length, DataLimit);
         }
 #else
+        private void ProcessBlock(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            Check.OutputLength(outBytes, outOff, 64, "output buffer too short");
+
+            mChacha20.ProcessBlock(inBytes, inOff, outBytes, outOff);
+
+            this.mDataCount = IncrementCount(mDataCount, 64U, DataLimit);
+        }
+
+        private void ProcessBlocks2(byte[] inBytes, int inOff, byte[] outBytes, int outOff)
+        {
+            Check.OutputLength(outBytes, outOff, 128, "output buffer too short");
+
+            mChacha20.ProcessBlocks2(inBytes, inOff, outBytes, outOff);
+
+            this.mDataCount = IncrementCount(mDataCount, 128U, DataLimit);
+        }
+
         private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
         {
             Check.OutputLength(outBytes, outOff, inLen, "output buffer too short");
diff --git a/crypto/src/crypto/modes/EAXBlockCipher.cs b/crypto/src/crypto/modes/EAXBlockCipher.cs
index 440b5f439..e63826159 100644
--- a/crypto/src/crypto/modes/EAXBlockCipher.cs
+++ b/crypto/src/crypto/modes/EAXBlockCipher.cs
@@ -209,25 +209,33 @@ namespace Org.BouncyCastle.Crypto.Modes
 		}
 #endif
 
-		public virtual int ProcessByte(
-			byte	input,
-			byte[]	outBytes,
-			int		outOff)
+        public virtual int ProcessByte(byte input, byte[] outBytes, int outOff)
 		{
             InitCipher();
 
-            return Process(input, outBytes, outOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return Process(input, Spans.FromNullable(outBytes, outOff));
+#else
+			return Process(input, outBytes, outOff);
+#endif
 		}
 
-        public virtual int ProcessBytes(
-			byte[]	inBytes,
-			int		inOff,
-			int		len,
-			byte[]	outBytes,
-			int		outOff)
-		{
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
             InitCipher();
 
+            return Process(input, output);
+        }
+#endif
+
+        public virtual int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff)
+        {
+            InitCipher();
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return ProcessBytes(inBytes.AsSpan(inOff, len), Spans.FromNullable(outBytes, outOff));
+#else
             int resultLen = 0;
 
 			for (int i = 0; i != len; i++)
@@ -236,9 +244,27 @@ namespace Org.BouncyCastle.Crypto.Modes
 			}
 
             return resultLen;
+#endif
 		}
 
-		public virtual int DoFinal(byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            InitCipher();
+
+			int len = input.Length;
+            int resultLen = 0;
+
+            for (int i = 0; i != len; i++)
+            {
+                resultLen += Process(input[i], output[resultLen..]);
+            }
+
+            return resultLen;
+        }
+#endif
+
+        public virtual int DoFinal(byte[] outBytes, int outOff)
 		{
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
 			return DoFinal(outBytes.AsSpan(outOff));
@@ -389,12 +415,49 @@ namespace Org.BouncyCastle.Crypto.Modes
             return totalData < macSize ? 0 : totalData - macSize;
         }
 
-		private int Process(
-			byte	b,
-			byte[]	outBytes,
-			int		outOff)
-		{
-			bufBlock[bufOff++] = b;
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int Process(byte b, Span<byte> output)
+        {
+            bufBlock[bufOff++] = b;
+
+            if (bufOff == bufBlock.Length)
+            {
+                Check.OutputLength(output, blockSize, "output buffer too short");
+
+                // TODO Could move the ProcessByte(s) calls to here
+                //InitCipher();
+
+                int size;
+
+                if (forEncryption)
+                {
+                    size = cipher.ProcessBlock(bufBlock, output);
+
+					mac.BlockUpdate(output[..blockSize]);
+                }
+                else
+                {
+                    mac.BlockUpdate(bufBlock.AsSpan(0, blockSize));
+
+                    size = cipher.ProcessBlock(bufBlock, output);
+                }
+
+                bufOff = 0;
+                if (!forEncryption)
+                {
+                    Array.Copy(bufBlock, blockSize, bufBlock, 0, macSize);
+                    bufOff = macSize;
+                }
+
+                return size;
+            }
+
+            return 0;
+        }
+#else
+        private int Process(byte b, byte[] outBytes, int outOff)
+        {
+            bufBlock[bufOff++] = b;
 
 			if (bufOff == bufBlock.Length)
 			{
@@ -430,8 +493,9 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 			return 0;
 		}
+#endif
 
-		private bool VerifyMac(byte[] mac, int off)
+        private bool VerifyMac(byte[] mac, int off)
 		{
             int nonEqual = 0;
 
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs
index c2b2cf86d..2cc7ff62d 100644
--- a/crypto/src/crypto/modes/GCMBlockCipher.cs
+++ b/crypto/src/crypto/modes/GCMBlockCipher.cs
@@ -379,12 +379,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 if (forEncryption)
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    EncryptBlock(bufBlock, output.AsSpan(outOff));
+#else
                     EncryptBlock(bufBlock, 0, output, outOff);
+#endif
                     bufOff = 0;
                 }
                 else
                 {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    DecryptBlock(bufBlock, output.AsSpan(outOff));
+#else
                     DecryptBlock(bufBlock, 0, output, outOff);
+#endif
                     Array.Copy(bufBlock, BlockSize, bufBlock, 0, macSize);
                     bufOff = macSize;
                 }
@@ -393,12 +401,40 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            CheckStatus();
+
+            bufBlock[bufOff] = input;
+            if (++bufOff == bufBlock.Length)
+            {
+                if (forEncryption)
+                {
+                    EncryptBlock(bufBlock, output);
+                    bufOff = 0;
+                }
+                else
+                {
+                    DecryptBlock(bufBlock, output);
+                    Array.Copy(bufBlock, BlockSize, bufBlock, 0, macSize);
+                    bufOff = macSize;
+                }
+                return BlockSize;
+            }
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
             CheckStatus();
 
             Check.DataLength(input, inOff, len, "input buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(input.AsSpan(inOff, len), Spans.FromNullable(output, outOff));
+#else
             int resultLen = 0;
 
             if (forEncryption)
@@ -495,7 +531,108 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            CheckStatus();
+
+            int resultLen = 0;
+
+            if (forEncryption)
+            {
+                if (bufOff > 0)
+                {
+                    int available = BlockSize - bufOff;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(bufBlock.AsSpan(bufOff));
+                        bufOff += input.Length;
+                        return 0;
+                    }
+
+                    input[..available].CopyTo(bufBlock.AsSpan(bufOff));
+                    EncryptBlock(bufBlock, output);
+                    input = input[available..];
+                    resultLen = BlockSize;
+                    //bufOff = 0;
+                }
+
+                while (input.Length >= BlockSize * 2)
+                {
+                    EncryptBlocks2(input, output[resultLen..]);
+                    input = input[(BlockSize * 2)..];
+                    resultLen += BlockSize * 2;
+                }
+
+                if (input.Length >= BlockSize)
+                {
+                    EncryptBlock(input, output[resultLen..]);
+                    input = input[BlockSize..];
+                    resultLen += BlockSize;
+                }
+
+                bufOff = input.Length;
+                input.CopyTo(bufBlock);
+            }
+            else
+            {
+                int available = bufBlock.Length - bufOff;
+                if (input.Length < available)
+                {
+                    input.CopyTo(bufBlock.AsSpan(bufOff));
+                    bufOff += input.Length;
+                    return 0;
+                }
+
+                if (bufOff >= BlockSize)
+                {
+                    DecryptBlock(bufBlock, output);
+                    Array.Copy(bufBlock, BlockSize, bufBlock, 0, bufOff -= BlockSize);
+                    resultLen = BlockSize;
+
+                    available += BlockSize;
+                    if (input.Length < available)
+                    {
+                        input.CopyTo(bufBlock.AsSpan(bufOff));
+                        bufOff += input.Length;
+                        return resultLen;
+                    }
+                }
+
+                int inLimit1 = bufBlock.Length;
+                int inLimit2 = inLimit1 + BlockSize;
+
+                available = BlockSize - bufOff;
+                input[..available].CopyTo(bufBlock.AsSpan(bufOff));
+                DecryptBlock(bufBlock, output[resultLen..]);
+                input = input[available..];
+                resultLen += BlockSize;
+                //bufOff = 0;
+
+                while (input.Length >= inLimit2)
+                {
+                    DecryptBlocks2(input, output[resultLen..]);
+                    input = input[(BlockSize * 2)..];
+                    resultLen += BlockSize * 2;
+                }
+
+                if (input.Length >= inLimit1)
+                {
+                    DecryptBlock(input, output[resultLen..]);
+                    input = input[BlockSize..];
+                    resultLen += BlockSize;
+                }
+
+                bufOff = input.Length;
+                input.CopyTo(bufBlock);
+            }
+
+            return resultLen;
         }
+#endif
 
         public int DoFinal(byte[] output, int outOff)
         {
@@ -670,29 +807,30 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
-        private void DecryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void DecryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t0);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -700,20 +838,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = inBuf[inOff + i + 0];
-                    byte c1 = inBuf[inOff + i + 1];
-                    byte c2 = inBuf[inOff + i + 2];
-                    byte c3 = inBuf[inOff + i + 3];
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
-                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
-                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
-                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
                 }
             }
             multiplier.MultiplyH(S);
@@ -721,29 +859,29 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize;
         }
 
-        private void DecryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        private void DecryptBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+            Check.OutputLength(output, BlockSize * 2, "output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t0);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -751,39 +889,39 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = inBuf[inOff + i + 0];
-                    byte c1 = inBuf[inOff + i + 1];
-                    byte c2 = inBuf[inOff + i + 2];
-                    byte c3 = inBuf[inOff + i + 3];
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
-                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
-                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
-                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
                 }
             }
             multiplier.MultiplyH(S);
 
-            inOff += BlockSize;
-            outOff += BlockSize;
+            input = input[BlockSize..];
+            output = output[BlockSize..];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t0);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -791,20 +929,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = inBuf[inOff + i + 0];
-                    byte c1 = inBuf[inOff + i + 1];
-                    byte c2 = inBuf[inOff + i + 2];
-                    byte c3 = inBuf[inOff + i + 3];
+                    byte c0 = input[i + 0];
+                    byte c1 = input[i + 1];
+                    byte c2 = input[i + 2];
+                    byte c3 = input[i + 3];
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
-                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
-                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
-                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                    output[i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    output[i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    output[i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    output[i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
                 }
             }
             multiplier.MultiplyH(S);
@@ -812,29 +950,29 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize * 2;
         }
 
-        private void EncryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        private void EncryptBlock(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+            Check.OutputLength(output, BlockSize, "output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t1);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -842,20 +980,20 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = (byte)(ctrBlock[i + 0] ^ inBuf[inOff + i + 0]);
-                    byte c1 = (byte)(ctrBlock[i + 1] ^ inBuf[inOff + i + 1]);
-                    byte c2 = (byte)(ctrBlock[i + 2] ^ inBuf[inOff + i + 2]);
-                    byte c3 = (byte)(ctrBlock[i + 3] ^ inBuf[inOff + i + 3]);
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = c0;
-                    outBuf[outOff + i + 1] = c1;
-                    outBuf[outOff + i + 2] = c2;
-                    outBuf[outOff + i + 3] = c3;
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
                 }
             }
             multiplier.MultiplyH(S);
@@ -863,29 +1001,29 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize;
         }
 
-        private void EncryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        private void EncryptBlocks2(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+            Check.OutputLength(output, BlockSize * 2, "Output buffer too short");
 
             if (totalLength == 0)
             {
                 InitCipher();
             }
 
-            byte[] ctrBlock = new byte[BlockSize];
+            Span<byte> ctrBlock = stackalloc byte[BlockSize];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t1);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -893,39 +1031,39 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
-                    byte c0 = (byte)(ctrBlock[i + 0] ^ inBuf[inOff + i + 0]);
-                    byte c1 = (byte)(ctrBlock[i + 1] ^ inBuf[inOff + i + 1]);
-                    byte c2 = (byte)(ctrBlock[i + 2] ^ inBuf[inOff + i + 2]);
-                    byte c3 = (byte)(ctrBlock[i + 3] ^ inBuf[inOff + i + 3]);
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
 
                     S[i + 0] ^= c0;
                     S[i + 1] ^= c1;
                     S[i + 2] ^= c2;
                     S[i + 3] ^= c3;
 
-                    outBuf[outOff + i + 0] = c0;
-                    outBuf[outOff + i + 1] = c1;
-                    outBuf[outOff + i + 2] = c2;
-                    outBuf[outOff + i + 3] = c3;
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
                 }
             }
             multiplier.MultiplyH(S);
 
-            inOff += BlockSize;
-            outOff += BlockSize;
+            input = input[BlockSize..];
+            output = output[BlockSize..];
 
             GetNextCtrBlock(ctrBlock);
 #if NETCOREAPP3_0_OR_GREATER
             if (Sse2.IsSupported && Unsafe.SizeOf<Vector128<byte>>() == BlockSize)
             {
-                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref inBuf[inOff]);
+                var t0 = Unsafe.ReadUnaligned<Vector128<byte>>(ref Unsafe.AsRef(input[0]));
                 var t1 = Unsafe.ReadUnaligned<Vector128<byte>>(ref ctrBlock[0]);
                 var t2 = Unsafe.ReadUnaligned<Vector128<byte>>(ref S[0]);
 
                 t1 = Sse2.Xor(t1, t0);
                 t2 = Sse2.Xor(t2, t1);
 
-                Unsafe.WriteUnaligned(ref outBuf[outOff], t1);
+                Unsafe.WriteUnaligned(ref output[0], t1);
                 Unsafe.WriteUnaligned(ref S[0], t2);
             }
             else
@@ -933,6 +1071,212 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 for (int i = 0; i < BlockSize; i += 4)
                 {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ input[i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ input[i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ input[i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ input[i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    output[i + 0] = c0;
+                    output[i + 1] = c1;
+                    output[i + 2] = c2;
+                    output[i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize * 2;
+        }
+
+        private void GetNextCtrBlock(Span<byte> block)
+        {
+            if (blocksRemaining == 0)
+                throw new InvalidOperationException("Attempt to process too many blocks");
+
+            blocksRemaining--;
+
+            Pack.UInt32_To_BE(++counter32, counter, 12);
+
+            cipher.ProcessBlock(counter, block);
+        }
+#else
+        private void DecryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = inBuf[inOff + i + 0];
+                    byte c1 = inBuf[inOff + i + 1];
+                    byte c2 = inBuf[inOff + i + 2];
+                    byte c3 = inBuf[inOff + i + 3];
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize;
+        }
+
+        private void DecryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = inBuf[inOff + i + 0];
+                    byte c1 = inBuf[inOff + i + 1];
+                    byte c2 = inBuf[inOff + i + 2];
+                    byte c3 = inBuf[inOff + i + 3];
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            inOff += BlockSize;
+            outOff += BlockSize;
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = inBuf[inOff + i + 0];
+                    byte c1 = inBuf[inOff + i + 1];
+                    byte c2 = inBuf[inOff + i + 2];
+                    byte c3 = inBuf[inOff + i + 3];
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    outBuf[outOff + i + 0] = (byte)(c0 ^ ctrBlock[i + 0]);
+                    outBuf[outOff + i + 1] = (byte)(c1 ^ ctrBlock[i + 1]);
+                    outBuf[outOff + i + 2] = (byte)(c2 ^ ctrBlock[i + 2]);
+                    outBuf[outOff + i + 3] = (byte)(c3 ^ ctrBlock[i + 3]);
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize * 2;
+        }
+
+        private void EncryptBlock(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ inBuf[inOff + i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ inBuf[inOff + i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ inBuf[inOff + i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ inBuf[inOff + i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    outBuf[outOff + i + 0] = c0;
+                    outBuf[outOff + i + 1] = c1;
+                    outBuf[outOff + i + 2] = c2;
+                    outBuf[outOff + i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            totalLength += BlockSize;
+        }
+
+        private void EncryptBlocks2(byte[] inBuf, int inOff, byte[] outBuf, int outOff)
+        {
+            Check.OutputLength(outBuf, outOff, BlockSize * 2, "Output buffer too short");
+
+            if (totalLength == 0)
+            {
+                InitCipher();
+            }
+
+            byte[] ctrBlock = new byte[BlockSize];
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
+                    byte c0 = (byte)(ctrBlock[i + 0] ^ inBuf[inOff + i + 0]);
+                    byte c1 = (byte)(ctrBlock[i + 1] ^ inBuf[inOff + i + 1]);
+                    byte c2 = (byte)(ctrBlock[i + 2] ^ inBuf[inOff + i + 2]);
+                    byte c3 = (byte)(ctrBlock[i + 3] ^ inBuf[inOff + i + 3]);
+
+                    S[i + 0] ^= c0;
+                    S[i + 1] ^= c1;
+                    S[i + 2] ^= c2;
+                    S[i + 3] ^= c3;
+
+                    outBuf[outOff + i + 0] = c0;
+                    outBuf[outOff + i + 1] = c1;
+                    outBuf[outOff + i + 2] = c2;
+                    outBuf[outOff + i + 3] = c3;
+                }
+            }
+            multiplier.MultiplyH(S);
+
+            inOff += BlockSize;
+            outOff += BlockSize;
+
+            GetNextCtrBlock(ctrBlock);
+            {
+                for (int i = 0; i < BlockSize; i += 4)
+                {
                     byte c0 = (byte)(ctrBlock[i + 0] ^ inBuf[inOff + i + 0]);
                     byte c1 = (byte)(ctrBlock[i + 1] ^ inBuf[inOff + i + 1]);
                     byte c2 = (byte)(ctrBlock[i + 2] ^ inBuf[inOff + i + 2]);
@@ -954,6 +1298,19 @@ namespace Org.BouncyCastle.Crypto.Modes
             totalLength += BlockSize * 2;
         }
 
+        private void GetNextCtrBlock(byte[] block)
+        {
+            if (blocksRemaining == 0)
+                throw new InvalidOperationException("Attempt to process too many blocks");
+
+            blocksRemaining--;
+
+            Pack.UInt32_To_BE(++counter32, counter, 12);
+
+            cipher.ProcessBlock(counter, 0, block, 0);
+        }
+#endif
+
         private void ProcessPartial(byte[] buf, int off, int len, byte[] output, int outOff)
         {
             byte[] ctrBlock = new byte[BlockSize];
@@ -1009,18 +1366,6 @@ namespace Org.BouncyCastle.Crypto.Modes
             multiplier.MultiplyH(Y);
         }
 
-        private void GetNextCtrBlock(byte[] block)
-        {
-            if (blocksRemaining == 0)
-                throw new InvalidOperationException("Attempt to process too many blocks");
-
-            blocksRemaining--;
-
-            Pack.UInt32_To_BE(++counter32, counter, 12);
-
-            cipher.ProcessBlock(counter, 0, block, 0);
-        }
-
         private void CheckStatus()
         {
             if (!initialised)
diff --git a/crypto/src/crypto/modes/GcmSivBlockCipher.cs b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
index d2f17809d..2abe5eece 100644
--- a/crypto/src/crypto/modes/GcmSivBlockCipher.cs
+++ b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
@@ -290,7 +290,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             CheckAeadStatus(1);
 
             /* Process the aead */
-            theAEADHasher.updateHash(pByte);
+            theAEADHasher.UpdateHash(pByte);
         }
 
         public virtual void ProcessAadBytes(byte[] pData, int pOffset, int pLen)
@@ -304,7 +304,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             CheckAeadStatus(pLen);
 
             /* Process the aead */
-            theAEADHasher.updateHash(pData, pOffset, pLen);
+            theAEADHasher.UpdateHash(pData, pOffset, pLen);
 #endif
         }
 
@@ -315,7 +315,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             CheckAeadStatus(input.Length);
 
             /* Process the aead */
-            theAEADHasher.updateHash(input);
+            theAEADHasher.UpdateHash(input);
         }
 #endif
 
@@ -328,7 +328,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             if (forEncryption)
             {
                 thePlain.WriteByte(pByte);
-                theDataHasher.updateHash(pByte);
+                theDataHasher.UpdateHash(pByte);
             }
             else
             {
@@ -339,18 +339,43 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
-        public virtual int ProcessBytes(byte[] pData, int pOffset, int pLen, byte[] pOutput, int pOutOffset)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
         {
             /* Check that we have initialised */
-            CheckStatus(pLen);
+            CheckStatus(1);
 
+            /* Store the data */
+            if (forEncryption)
+            {
+                thePlain.WriteByte(input);
+                theDataHasher.UpdateHash(input);
+            }
+            else
+            {
+                theEncData.WriteByte(input);
+            }
+
+            /* No data returned */
+            return 0;
+        }
+#endif
+
+        public virtual int ProcessBytes(byte[] pData, int pOffset, int pLen, byte[] pOutput, int pOutOffset)
+        {
             Check.DataLength(pData, pOffset, pLen, "input buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(pData.AsSpan(pOffset, pLen), Spans.FromNullable(pOutput, pOutOffset));
+#else
+            /* Check that we have initialised */
+            CheckStatus(pLen);
+
             /* Store the data */
             if (forEncryption)
             {
                 thePlain.Write(pData, pOffset, pLen);
-                theDataHasher.updateHash(pData, pOffset, pLen);
+                theDataHasher.UpdateHash(pData, pOffset, pLen);
             }
             else
             {
@@ -359,8 +384,31 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             /* No data returned */
             return 0;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            /* Check that we have initialised */
+            CheckStatus(input.Length);
+
+            /* Store the data */
+            if (forEncryption)
+            {
+                thePlain.Write(input);
+                theDataHasher.UpdateHash(input);
+            }
+            else
+            {
+                theEncData.Write(input);
+            }
+
+            /* No data returned */
+            return 0;
+        }
+#endif
+
         public virtual int DoFinal(byte[] pOutput, int pOffset)
         {
             Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short");
@@ -500,7 +548,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             Arrays.Fill(theGHash, (byte)0);
             if (theInitialAEAD != null)
             {
-                theAEADHasher.updateHash(theInitialAEAD, 0, theInitialAEAD.Length);
+                theAEADHasher.UpdateHash(theInitialAEAD, 0, theInitialAEAD.Length);
             }
         }
 
@@ -619,7 +667,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 /* Write data to plain dataStream */
                 thePlain.Write(myMask, 0, myLen);
-                theDataHasher.updateHash(myMask, 0, myLen);
+                theDataHasher.UpdateHash(myMask, 0, myLen);
 
                 /* Adjust counters */
                 myRemaining -= myLen;
@@ -911,10 +959,10 @@ namespace Org.BouncyCastle.Crypto.Modes
             * update hash.
             * @param pByte the byte
             */
-            internal void updateHash(byte pByte)
+            internal void UpdateHash(byte pByte)
             {
                 theByte[0] = pByte;
-                updateHash(theByte, 0, 1);
+                UpdateHash(theByte, 0, 1);
             }
 
             /**
@@ -923,7 +971,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             * @param pOffset the offset within the buffer
             * @param pLen the length of data
             */
-            internal void updateHash(byte[] pBuffer, int pOffset, int pLen)
+            internal void UpdateHash(byte[] pBuffer, int pOffset, int pLen)
             {
                 /* If we should process the cache */
                 int mySpace = BUFLEN - numActive;
@@ -967,7 +1015,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
-            internal void updateHash(ReadOnlySpan<byte> buffer)
+            internal void UpdateHash(ReadOnlySpan<byte> buffer)
             {
                 int pLen = buffer.Length;
 
diff --git a/crypto/src/crypto/modes/IAeadCipher.cs b/crypto/src/crypto/modes/IAeadCipher.cs
index f80f3a247..5e92c78f3 100644
--- a/crypto/src/crypto/modes/IAeadCipher.cs
+++ b/crypto/src/crypto/modes/IAeadCipher.cs
@@ -59,6 +59,10 @@ namespace Org.BouncyCastle.Crypto.Modes
 		*/
         int ProcessByte(byte input, byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessByte(byte input, Span<byte> output);
+#endif
+
         /**
         * Process a block of bytes from in putting the result into out.
         *
@@ -72,6 +76,10 @@ namespace Org.BouncyCastle.Crypto.Modes
         */
         int ProcessBytes(byte[] inBytes, int inOff, int len, byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output);
+#endif
+
         /**
         * Finish the operation either appending or verifying the MAC at the end of the data.
         *
diff --git a/crypto/src/crypto/modes/KCcmBlockCipher.cs b/crypto/src/crypto/modes/KCcmBlockCipher.cs
index db86cf890..493bf56e1 100644
--- a/crypto/src/crypto/modes/KCcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/KCcmBlockCipher.cs
@@ -7,7 +7,8 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Modes
 {
-    public class KCcmBlockCipher: IAeadBlockCipher
+    public class KCcmBlockCipher
+        : IAeadBlockCipher
     {
         private static readonly int BYTES_IN_INT = 4;
         private static readonly int BITS_IN_BYTE = 8;
@@ -234,6 +235,15 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            data.WriteByte(input);
+
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] input, int inOff, int inLen, byte[] output, int outOff)
         {
             Check.DataLength(input, inOff, inLen, "input buffer too short");
@@ -243,6 +253,15 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            data.Write(input);
+
+            return 0;
+        }
+#endif
+
         public int ProcessPacket(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
             Check.DataLength(input, inOff, len, "input buffer too short");
diff --git a/crypto/src/crypto/modes/OCBBlockCipher.cs b/crypto/src/crypto/modes/OCBBlockCipher.cs
index 8281c96c1..a4eaa08bd 100644
--- a/crypto/src/crypto/modes/OCBBlockCipher.cs
+++ b/crypto/src/crypto/modes/OCBBlockCipher.cs
@@ -312,8 +312,24 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessByte(byte input, Span<byte> output)
+        {
+            mainBlock[mainBlockPos] = input;
+            if (++mainBlockPos == mainBlock.Length)
+            {
+                ProcessMainBlock(output);
+                return BLOCK_SIZE;
+            }
+            return 0;
+        }
+#endif
+
         public virtual int ProcessBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessBytes(input.AsSpan(inOff, len), Spans.FromNullable(output, outOff));
+#else
             int resultLen = 0;
 
             for (int i = 0; i < len; ++i)
@@ -327,7 +343,28 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             return resultLen;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int len = input.Length;
+            int resultLen = 0;
+
+            for (int i = 0; i < len; ++i)
+            {
+                mainBlock[mainBlockPos] = input[i];
+                if (++mainBlockPos == mainBlock.Length)
+                {
+                    ProcessMainBlock(output[resultLen..]);
+                    resultLen += BLOCK_SIZE;
+                }
+            }
+
+            return resultLen;
         }
+#endif
 
         public virtual int DoFinal(byte[] output, int outOff)
         {
@@ -572,6 +609,38 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual void ProcessMainBlock(Span<byte> output)
+        {
+            Check.DataLength(output, BLOCK_SIZE, "output buffer too short");
+
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks
+             */
+
+            if (forEncryption)
+            {
+                Xor(Checksum, mainBlock);
+                mainBlockPos = 0;
+            }
+
+            Xor(OffsetMAIN, GetLSub(OCB_ntz(++mainBlockCount)));
+
+            Xor(mainBlock, OffsetMAIN);
+            mainCipher.ProcessBlock(mainBlock, 0, mainBlock, 0);
+            Xor(mainBlock, OffsetMAIN);
+
+            mainBlock.AsSpan(0, BLOCK_SIZE).CopyTo(output);
+
+            if (!forEncryption)
+            {
+                Xor(Checksum, mainBlock);
+                Array.Copy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize);
+                mainBlockPos = macSize;
+            }
+        }
+#endif
+
         protected virtual void Reset(bool clearMac)
         {
             hashCipher.Reset();
diff --git a/crypto/src/util/Spans.cs b/crypto/src/util/Spans.cs
new file mode 100644
index 000000000..5e1b3737c
--- /dev/null
+++ b/crypto/src/util/Spans.cs
@@ -0,0 +1,18 @@
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System;
+using System.Runtime.CompilerServices;
+
+#nullable enable
+
+namespace Org.BouncyCastle.Utilities
+{
+    internal static class Spans
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static Span<T> FromNullable<T>(T[]? array, int start)
+        {
+            return array == null ? Span<T>.Empty : array.AsSpan(start);
+        }
+    }
+}
+#endif