summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crypto/src/crypto/modes/CcmBlockCipher.cs213
-rw-r--r--crypto/src/crypto/modes/ChaCha20Poly1305.cs77
-rw-r--r--crypto/src/crypto/modes/EAXBlockCipher.cs72
-rw-r--r--crypto/src/crypto/modes/GCMBlockCipher.cs19
-rw-r--r--crypto/src/crypto/modes/GcmSivBlockCipher.cs135
-rw-r--r--crypto/src/crypto/modes/IAeadCipher.cs4
-rw-r--r--crypto/src/crypto/modes/KCcmBlockCipher.cs185
-rw-r--r--crypto/src/crypto/modes/OCBBlockCipher.cs98
-rw-r--r--crypto/src/util/Arrays.cs18
9 files changed, 751 insertions, 70 deletions
diff --git a/crypto/src/crypto/modes/CcmBlockCipher.cs b/crypto/src/crypto/modes/CcmBlockCipher.cs
index 256cc1b13..46e7b9c55 100644
--- a/crypto/src/crypto/modes/CcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/CcmBlockCipher.cs
@@ -149,10 +149,11 @@ namespace Org.BouncyCastle.Crypto.Modes
             return 0;
         }
 
-        public virtual int DoFinal(
-            byte[]	outBytes,
-            int		outOff)
+        public virtual int DoFinal(byte[] outBytes, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(outBytes.AsSpan(outOff));
+#else
             byte[] input = data.GetBuffer();
             int inLen = Convert.ToInt32(data.Length);
 
@@ -161,7 +162,22 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset();
 
             return len;
+#endif
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            byte[] input = data.GetBuffer();
+            int inLen = Convert.ToInt32(data.Length);
+
+            int len = ProcessPacket(input.AsSpan(0, inLen), output);
+
+            Reset();
+
+            return len;
         }
+#endif
 
         public virtual void Reset()
         {
@@ -341,8 +357,106 @@ namespace Org.BouncyCastle.Crypto.Modes
             return outputLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int inLen = input.Length;
+
+            // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+            // Need to keep the CTR and CBC Mac parts around and reset
+            if (keyParam == null)
+                throw new InvalidOperationException("CCM cipher unitialized.");
+
+            int n = nonce.Length;
+            int q = 15 - n;
+            if (q < 4)
+            {
+                int limitLen = 1 << (8 * q);
+                if (inLen >= limitLen)
+                    throw new InvalidOperationException("CCM packet too large for choice of q.");
+            }
+
+            byte[] iv = new byte[BlockSize];
+            iv[0] = (byte)((q - 1) & 0x7);
+            nonce.CopyTo(iv, 1);
+
+            IBlockCipher ctrCipher = new SicBlockCipher(cipher);
+            ctrCipher.Init(forEncryption, new ParametersWithIV(keyParam, iv));
+
+            int outputLen;
+            int index = 0;
+            Span<byte> block = stackalloc byte[BlockSize];
+
+            if (forEncryption)
+            {
+                outputLen = inLen + macSize;
+                Check.OutputLength(output, outputLen, "output buffer too short");
+
+                CalculateMac(input, macBlock);
+
+                byte[] encMac = new byte[BlockSize];
+                ctrCipher.ProcessBlock(macBlock, encMac);   // S0
+
+                while (index < (inLen - BlockSize))                 // S1...
+                {
+                    ctrCipher.ProcessBlock(input[index..], output[index..]);
+                    index += BlockSize;
+                }
+
+                input[index..].CopyTo(block);
+
+                ctrCipher.ProcessBlock(block, block);
+
+                block[..(inLen - index)].CopyTo(output[index..]);
+
+                encMac.AsSpan(0, macSize).CopyTo(output[inLen..]);
+            }
+            else
+            {
+                if (inLen < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                outputLen = inLen - macSize;
+                Check.OutputLength(output, outputLen, "output buffer too short");
+
+                input[outputLen..].CopyTo(macBlock);
+
+                ctrCipher.ProcessBlock(macBlock, macBlock);
+
+                for (int i = macSize; i != macBlock.Length; i++)
+                {
+                    macBlock[i] = 0;
+                }
+
+                while (index < (outputLen - BlockSize))
+                {
+                    ctrCipher.ProcessBlock(input[index..], output[index..]);
+                    index += BlockSize;
+                }
+
+                input[index..outputLen].CopyTo(block);
+
+                ctrCipher.ProcessBlock(block, block);
+
+                block[..(outputLen - index)].CopyTo(output[index..]);
+
+                Span<byte> calculatedMacBlock = stackalloc byte[BlockSize];
+
+                CalculateMac(output[..outputLen], calculatedMacBlock);
+
+                if (!Arrays.ConstantTimeAreEqual(macBlock, calculatedMacBlock))
+                    throw new InvalidCipherTextException("mac check in CCM failed");
+            }
+
+            return outputLen;
+        }
+#endif
+
         private int CalculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return CalculateMac(data.AsSpan(dataOff, dataLen), macBlock);
+#else
             IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
 
             cMac.Init(keyParam);
@@ -429,8 +543,101 @@ namespace Org.BouncyCastle.Crypto.Modes
             cMac.BlockUpdate(data, dataOff, dataLen);
 
             return cMac.DoFinal(macBlock, 0);
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int CalculateMac(ReadOnlySpan<byte> data, Span<byte> macBlock)
+        {
+            IMac cMac = new CbcBlockCipherMac(cipher, macSize * 8);
+
+            cMac.Init(keyParam);
+
+            //
+            // build b0
+            //
+            byte[] b0 = new byte[16];
+
+            if (HasAssociatedText())
+            {
+                b0[0] |= 0x40;
+            }
+
+            b0[0] |= (byte)((((cMac.GetMacSize() - 2) / 2) & 0x7) << 3);
+
+            b0[0] |= (byte)(((15 - nonce.Length) - 1) & 0x7);
+
+            Array.Copy(nonce, 0, b0, 1, nonce.Length);
+
+            int q = data.Length;
+            int count = 1;
+            while (q > 0)
+            {
+                b0[b0.Length - count] = (byte)(q & 0xff);
+                q >>= 8;
+                count++;
+            }
+
+            cMac.BlockUpdate(b0, 0, b0.Length);
+
+            //
+            // process associated text
+            //
+            if (HasAssociatedText())
+            {
+                int extra;
+
+                int textLength = GetAssociatedTextLength();
+                if (textLength < ((1 << 16) - (1 << 8)))
+                {
+                    cMac.Update((byte)(textLength >> 8));
+                    cMac.Update((byte)textLength);
+
+                    extra = 2;
+                }
+                else // can't go any higher than 2^32
+                {
+                    cMac.Update((byte)0xff);
+                    cMac.Update((byte)0xfe);
+                    cMac.Update((byte)(textLength >> 24));
+                    cMac.Update((byte)(textLength >> 16));
+                    cMac.Update((byte)(textLength >> 8));
+                    cMac.Update((byte)textLength);
+
+                    extra = 6;
+                }
+
+                if (initialAssociatedText != null)
+                {
+                    cMac.BlockUpdate(initialAssociatedText, 0, initialAssociatedText.Length);
+                }
+                if (associatedText.Length > 0)
+                {
+                    byte[] input = associatedText.GetBuffer();
+                    int len = Convert.ToInt32(associatedText.Length);
+
+                    cMac.BlockUpdate(input, 0, len);
+                }
+
+                extra = (extra + textLength) % 16;
+                if (extra != 0)
+                {
+                    for (int i = extra; i < 16; ++i)
+                    {
+                        cMac.Update((byte)0x00);
+                    }
+                }
+            }
+
+            //
+            // add the text
+            //
+            cMac.BlockUpdate(data);
+
+            return cMac.DoFinal(macBlock);
+        }
+#endif
+
         private int GetMacSize(bool forEncryption, int requestedMacBits)
         {
             if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15)))
diff --git a/crypto/src/crypto/modes/ChaCha20Poly1305.cs b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
index 385977fd5..9e30dc510 100644
--- a/crypto/src/crypto/modes/ChaCha20Poly1305.cs
+++ b/crypto/src/crypto/modes/ChaCha20Poly1305.cs
@@ -397,6 +397,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 DoFinal(outBytes.AsSpan(outOff));
+#else
             CheckData();
 
             Array.Clear(mMac, 0, MacSize);
@@ -423,9 +426,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 FinishData(State.DecFinal);
 
                 if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
-                {
                     throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
-                }
 
                 break;
             }
@@ -453,8 +454,69 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset(false, true);
 
             return resultLen;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            CheckData();
+
+            Array.Clear(mMac, 0, MacSize);
+
+            int resultLen = 0;
+
+            switch (mState)
+            {
+            case State.DecData:
+            {
+                if (mBufPos < MacSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                resultLen = mBufPos - MacSize;
+
+                Check.OutputLength(output, resultLen, "output buffer too short");
+
+                if (resultLen > 0)
+                {
+                    mPoly1305.BlockUpdate(mBuf, 0, resultLen);
+                    ProcessData(mBuf.AsSpan(0, resultLen), output);
+                }
+
+                FinishData(State.DecFinal);
+
+                if (!Arrays.ConstantTimeAreEqual(MacSize, mMac, 0, mBuf, resultLen))
+                    throw new InvalidCipherTextException("mac check in ChaCha20Poly1305 failed");
+
+                break;
+            }
+            case State.EncData:
+            {
+                resultLen = mBufPos + MacSize;
+
+                Check.OutputLength(output, resultLen, "output buffer too short");
+
+                if (mBufPos > 0)
+                {
+                    ProcessData(mBuf.AsSpan(0, mBufPos), output);
+                    mPoly1305.BlockUpdate(output[..mBufPos]);
+                }
+
+                FinishData(State.EncFinal);
+
+                mMac.AsSpan(0, MacSize).CopyTo(output[mBufPos..]);
+                break;
+            }
+            default:
+                throw new InvalidOperationException();
+            }
+
+            Reset(false, true);
+
+            return resultLen;
+        }
+#endif
+
         public virtual byte[] GetMac()
         {
             return Arrays.Clone(mMac);
@@ -577,6 +639,16 @@ namespace Org.BouncyCastle.Crypto.Modes
             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");
+
+            mChacha20.ProcessBytes(input, output);
+
+            this.mDataCount = IncrementCount(mDataCount, (uint)input.Length, DataLimit);
+        }
+#else
         private void ProcessData(byte[] inBytes, int inOff, int inLen, byte[] outBytes, int outOff)
         {
             Check.OutputLength(outBytes, outOff, inLen, "output buffer too short");
@@ -585,6 +657,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             this.mDataCount = IncrementCount(mDataCount, (uint)inLen, DataLimit);
         }
+#endif
 
         private void Reset(bool clearMac, bool resetCipher)
         {
diff --git a/crypto/src/crypto/modes/EAXBlockCipher.cs b/crypto/src/crypto/modes/EAXBlockCipher.cs
index ffe32ec68..440b5f439 100644
--- a/crypto/src/crypto/modes/EAXBlockCipher.cs
+++ b/crypto/src/crypto/modes/EAXBlockCipher.cs
@@ -238,11 +238,12 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
 		}
 
-		public virtual int DoFinal(
-			byte[]	outBytes,
-			int		outOff)
+		public virtual int DoFinal(byte[] outBytes, int outOff)
 		{
-            InitCipher();
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			return DoFinal(outBytes.AsSpan(outOff));
+#else
+			InitCipher();
 
             int extra = bufOff;
 			byte[] tmp = new byte[bufBlock.Length];
@@ -251,7 +252,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 			if (forEncryption)
 			{
-                Check.OutputLength(outBytes, outOff, extra + macSize, "Output buffer too short");
+                Check.OutputLength(outBytes, outOff, extra + macSize, "output buffer too short");
 
                 cipher.ProcessBlock(bufBlock, 0, tmp, 0);
 
@@ -272,7 +273,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 if (extra < macSize)
                     throw new InvalidCipherTextException("data too short");
 
-                Check.OutputLength(outBytes, outOff, extra - macSize, "Output buffer too short");
+                Check.OutputLength(outBytes, outOff, extra - macSize, "output buffer too short");
 
                 if (extra > macSize)
 				{
@@ -292,9 +293,66 @@ namespace Org.BouncyCastle.Crypto.Modes
 
 				return extra - macSize;
 			}
+#endif
+		}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+		{
+            InitCipher();
+
+            int extra = bufOff;
+			Span<byte> tmp = stackalloc byte[bufBlock.Length];
+
+            bufOff = 0;
+
+			if (forEncryption)
+			{
+                Check.OutputLength(output, extra + macSize, "output buffer too short");
+
+                cipher.ProcessBlock(bufBlock, tmp);
+
+				tmp[..extra].CopyTo(output);
+
+				mac.BlockUpdate(tmp[..extra]);
+
+				CalculateMac();
+
+				macBlock.AsSpan(0, macSize).CopyTo(output[extra..]);
+
+				Reset(false);
+
+				return extra + macSize;
+			}
+			else
+			{
+                if (extra < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                Check.OutputLength(output, extra - macSize, "output buffer too short");
+
+                if (extra > macSize)
+				{
+					mac.BlockUpdate(bufBlock.AsSpan(0, extra - macSize));
+
+					cipher.ProcessBlock(bufBlock, tmp);
+
+					tmp[..(extra - macSize)].CopyTo(output);
+				}
+
+				CalculateMac();
+
+				if (!VerifyMac(bufBlock, extra - macSize))
+					throw new InvalidCipherTextException("mac check in EAX failed");
+
+				Reset(false);
+
+				return extra - macSize;
+			}
 		}
+#endif
 
-		public virtual byte[] GetMac()
+        public virtual byte[] GetMac()
 		{
 			byte[] mac = new byte[macSize];
 
diff --git a/crypto/src/crypto/modes/GCMBlockCipher.cs b/crypto/src/crypto/modes/GCMBlockCipher.cs
index bf9c14e28..c2b2cf86d 100644
--- a/crypto/src/crypto/modes/GCMBlockCipher.cs
+++ b/crypto/src/crypto/modes/GCMBlockCipher.cs
@@ -510,7 +510,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             if (forEncryption)
             {
-                Check.OutputLength(output, outOff, extra + macSize, "Output buffer too short");
+                Check.OutputLength(output, outOff, extra + macSize, "output buffer too short");
             }
             else
             {
@@ -519,7 +519,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 extra -= macSize;
 
-                Check.OutputLength(output, outOff, extra, "Output buffer too short");
+                Check.OutputLength(output, outOff, extra, "output buffer too short");
             }
 
             if (extra > 0)
@@ -607,6 +607,21 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            // TODO[span] Implement efficiently
+
+            int outputLen = GetOutputSize(0);
+            Check.OutputLength(output, outputLen, "output buffer too short");
+
+            byte[] bytes = new byte[outputLen];
+            int len = DoFinal(bytes, 0);
+            bytes[..len].CopyTo(output);
+            return len;
+        }
+#endif
+
         public virtual void Reset()
         {
             Reset(true);
diff --git a/crypto/src/crypto/modes/GcmSivBlockCipher.cs b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
index 284a952a6..d2f17809d 100644
--- a/crypto/src/crypto/modes/GcmSivBlockCipher.cs
+++ b/crypto/src/crypto/modes/GcmSivBlockCipher.cs
@@ -295,8 +295,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         public virtual void ProcessAadBytes(byte[] pData, int pOffset, int pLen)
         {
-            /* Check input buffer */
-            CheckBuffer(pData, pOffset, pLen, false);
+            Check.DataLength(pData, pOffset, pLen, "input buffer too short");
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
             ProcessAadBytes(pData.AsSpan(pOffset, pLen));
@@ -345,8 +344,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             /* Check that we have initialised */
             CheckStatus(pLen);
 
-            /* Check input buffer */
-            CheckBuffer(pData, pOffset, pLen, false);
+            Check.DataLength(pData, pOffset, pLen, "input buffer too short");
 
             /* Store the data */
             if (forEncryption)
@@ -365,20 +363,22 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         public virtual int DoFinal(byte[] pOutput, int pOffset)
         {
+            Check.OutputLength(pOutput, pOffset, GetOutputSize(0), "output buffer too short");
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(pOutput.AsSpan(pOffset));
+#else
             /* Check that we have initialised */
             CheckStatus(0);
 
-            /* Check output buffer */
-            CheckBuffer(pOutput, pOffset, GetOutputSize(0), true);
-
             /* If we are encrypting */
             if (forEncryption)
             {
                 /* Derive the tag */
-                byte[] myTag = calculateTag();
+                byte[] myTag = CalculateTag();
 
                 /* encrypt the plain text */
-                int myDataLen = BUFLEN + encryptPlain(myTag, pOutput, pOffset);
+                int myDataLen = BUFLEN + EncryptPlain(myTag, pOutput, pOffset);
 
                 /* Add the tag to the output */
                 Array.Copy(myTag, 0, pOutput, pOffset + Convert.ToInt32(thePlain.Length), BUFLEN);
@@ -392,7 +392,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             else
             {
                 /* decrypt to plain text */
-                decryptPlain();
+                DecryptPlain();
 
                 /* Release plain text */
                 int myDataLen = Streams.WriteBufTo(thePlain, pOutput, pOffset);
@@ -401,8 +401,54 @@ namespace Org.BouncyCastle.Crypto.Modes
                 ResetStreams();
                 return myDataLen;
             }
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            /* Check that we have initialised */
+            CheckStatus(0);
+
+            Check.OutputLength(output, GetOutputSize(0), "output buffer too short");
+
+            /* If we are encrypting */
+            if (forEncryption)
+            {
+                /* Derive the tag */
+                byte[] myTag = CalculateTag();
+
+                /* encrypt the plain text */
+                int myDataLen = BUFLEN + EncryptPlain(myTag, output);
+
+                /* Add the tag to the output */
+                myTag.AsSpan(0, BUFLEN).CopyTo(output[Convert.ToInt32(thePlain.Length)..]);
+
+                /* Reset the streams */
+                ResetStreams();
+                return myDataLen;
+
+                /* else we are decrypting */
+            }
+            else
+            {
+                /* decrypt to plain text */
+                DecryptPlain();
+
+                /* Release plain text */
+                if (!thePlain.TryGetBuffer(out var buffer))
+                    throw new InvalidOperationException();
+
+                buffer.AsSpan().CopyTo(output);
+                int myDataLen = buffer.Count;
+
+                /* Reset the streams */
+                ResetStreams();
+                return myDataLen;
+            }
+        }
+#endif
+
         public virtual byte[] GetMac()
         {
             throw new InvalidOperationException();
@@ -468,37 +514,43 @@ namespace Org.BouncyCastle.Crypto.Modes
             return pBuffer == null ? 0 : pBuffer.Length;
         }
 
-        /**
-        * Check buffer.
-        * @param pBuffer the buffer
-        * @param pOffset the offset
-        * @param pLen the length
-        * @param pOutput is this an output buffer?
-        */
-        private static void CheckBuffer(byte[] pBuffer, int pOffset, int pLen, bool pOutput)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int EncryptPlain(byte[] pCounter, Span<byte> target)
         {
-            /* Access lengths */
-            int myBufLen = bufLength(pBuffer);
-            int myLast = pOffset + pLen;
+            byte[] thePlainBuf = thePlain.GetBuffer();
+            int thePlainLen = Convert.ToInt32(thePlain.Length);
 
-            /* Check for negative values and buffer overflow */
-            bool badLen = pLen < 0 || pOffset < 0 || myLast < 0;
-            if (badLen || myLast > myBufLen)
+            byte[] mySrc = thePlainBuf;
+            byte[] myCounter = Arrays.Clone(pCounter);
+            myCounter[BUFLEN - 1] |= MASK;
+            byte[] myMask = new byte[BUFLEN];
+            long myRemaining = thePlainLen;
+            int myOff = 0;
+
+            /* While we have data to process */
+            while (myRemaining > 0)
             {
-                throw pOutput
-                ? new OutputLengthException("output buffer too short.")
-                : new DataLengthException("input buffer too short.");
+                /* Generate the next mask */
+                theCipher.ProcessBlock(myCounter, 0, myMask, 0);
+
+                /* Xor data into mask */
+                int myLen = (int)System.Math.Min(BUFLEN, myRemaining);
+                xorBlock(myMask, mySrc, myOff, myLen);
+
+                /* Copy encrypted data to output */
+                myMask.AsSpan(0, myLen).CopyTo(target[myOff..]);
+
+                /* Adjust counters */
+                myRemaining -= myLen;
+                myOff += myLen;
+                incrementCounter(myCounter);
             }
-        }
 
-        /**
-        * encrypt data stream.
-        * @param pCounter the counter
-        * @param pTarget the target buffer
-        * @param pOffset the target offset
-        * @return the length of data encrypted
-        */
-        private int encryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
+            /* Return the amount of data processed */
+            return thePlainLen;
+        }
+#else
+        private int EncryptPlain(byte[] pCounter, byte[] pTarget, int pOffset)
         {
             byte[] thePlainBuf = thePlain.GetBuffer();
             int thePlainLen = Convert.ToInt32(thePlain.Length);
@@ -532,12 +584,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             /* Return the amount of data processed */
             return thePlainLen;
         }
+#endif
 
-        /**
-        * decrypt data stream.
-        * @throws InvalidCipherTextException on data too short or mac check failed
-        */
-        private void decryptPlain()
+        private void DecryptPlain()
         {
             byte[] theEncDataBuf = theEncData.GetBuffer();
             int theEncDataLen = Convert.ToInt32(theEncData.Length);
@@ -579,7 +628,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             }
 
             /* Derive and check the tag */
-            byte[] myTag = calculateTag();
+            byte[] myTag = CalculateTag();
             if (!Arrays.ConstantTimeAreEqual(myTag, myExpected))
             {
                 Reset();
@@ -591,7 +640,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         * calculate tag.
         * @return the calculated tag
         */
-        private byte[] calculateTag()
+        private byte[] CalculateTag()
         {
             /* Complete the hash */
             theDataHasher.completeHash();
diff --git a/crypto/src/crypto/modes/IAeadCipher.cs b/crypto/src/crypto/modes/IAeadCipher.cs
index c61e13b01..f80f3a247 100644
--- a/crypto/src/crypto/modes/IAeadCipher.cs
+++ b/crypto/src/crypto/modes/IAeadCipher.cs
@@ -83,6 +83,10 @@ namespace Org.BouncyCastle.Crypto.Modes
         */
         int DoFinal(byte[] outBytes, int outOff);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int DoFinal(Span<byte> output);
+#endif
+
         /**
         * Return the value of the MAC associated with the last stream processed.
         *
diff --git a/crypto/src/crypto/modes/KCcmBlockCipher.cs b/crypto/src/crypto/modes/KCcmBlockCipher.cs
index afa68a794..db86cf890 100644
--- a/crypto/src/crypto/modes/KCcmBlockCipher.cs
+++ b/crypto/src/crypto/modes/KCcmBlockCipher.cs
@@ -248,6 +248,9 @@ namespace Org.BouncyCastle.Crypto.Modes
             Check.DataLength(input, inOff, len, "input buffer too short");
             Check.OutputLength(output, outOff, len, "output buffer too short");
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return ProcessPacket(input.AsSpan(inOff, len), output.AsSpan(outOff));
+#else
             if (associatedText.Length > 0)
             {
                 byte[] aad = associatedText.GetBuffer();
@@ -268,7 +271,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 int totalLength = len;
                 while (totalLength > 0)
                 {
-                    ProcessBlock(input, inOff, len, output, outOff);
+                    ProcessBlock(input, inOff, output, outOff);
                     totalLength -= engine.GetBlockSize();
                     inOff += engine.GetBlockSize();
                     outOff += engine.GetBlockSize();
@@ -302,7 +305,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 for (int blockNum = 0; blockNum<blocks; blockNum++)
                 {
-                    ProcessBlock(input, inOff, len, output, outOff);
+                    ProcessBlock(input, inOff, output, outOff);
 
                     inOff += engine.GetBlockSize();
                     outOff += engine.GetBlockSize();
@@ -350,43 +353,190 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 return len - macSize;
             }
+#endif
         }
 
-        private void ProcessBlock(byte[] input, int inOff, int len, byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int ProcessPacket(ReadOnlySpan<byte> input, Span<byte> output)
         {
+            int len = input.Length;
+            Check.OutputLength(output, len, "output buffer too short");
+
+            if (associatedText.Length > 0)
+            {
+                byte[] aad = associatedText.GetBuffer();
+                int aadLen = Convert.ToInt32(associatedText.Length);
+
+                int dataLen = Convert.ToInt32(data.Length) - (forEncryption ? 0 : macSize);
+
+                ProcessAAD(aad, 0, aadLen, dataLen);
+            }
+
+            int blockSize = engine.GetBlockSize(), index = 0;
+            if (forEncryption)
+            {
+                Check.DataLength(len % blockSize != 0, "partial blocks not supported");
+
+                CalculateMac(input);
+                engine.ProcessBlock(nonce, s);
+
+                int totalLength = len;
+                while (totalLength > 0)
+                {
+                    ProcessBlock(input[index..], output[index..]);
+                    totalLength -= blockSize;
+                    index += blockSize;
+                }
+
+                for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                {
+                    s[byteIndex] += counter[byteIndex];
+                }
+
+                engine.ProcessBlock(s, buffer);
+
+                for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+                {
+                    output[index + byteIndex] = (byte)(buffer[byteIndex] ^ macBlock[byteIndex]);
+                }
+
+                Array.Copy(macBlock, 0, mac, 0, macSize);
+
+                Reset();
+
+                return len + macSize;
+            }
+            else
+            {
+                Check.DataLength((len - macSize) % blockSize != 0, "partial blocks not supported");
+
+                engine.ProcessBlock(nonce, 0, s, 0);
+
+                int blocks = len / engine.GetBlockSize();
+
+                for (int blockNum = 0; blockNum < blocks; blockNum++)
+                {
+                    ProcessBlock(input[index..], output[index..]);
+                    index += blockSize;
+                }
+
+                if (len > index)
+                {
+                    for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                    {
+                        s[byteIndex] += counter[byteIndex];
+                    }
+
+                    engine.ProcessBlock(s, buffer);
+
+                    for (int byteIndex = 0; byteIndex < macSize; byteIndex++)
+                    {
+                        output[index + byteIndex] = (byte)(buffer[byteIndex] ^ input[index + byteIndex]);
+                    }
+                    index += macSize;
+                }
+
+                for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+                {
+                    s[byteIndex] += counter[byteIndex];
+                }
+
+                engine.ProcessBlock(s, buffer);
+
+                output[(index - macSize)..index].CopyTo(buffer);
 
+                CalculateMac(output[..(index - macSize)]);
+
+                Array.Copy(macBlock, 0, mac, 0, macSize);
+
+                Span<byte> calculatedMac = stackalloc byte[macSize];
+
+                buffer.AsSpan(0, macSize).CopyTo(calculatedMac);
+
+                if (!Arrays.ConstantTimeAreEqual(mac.AsSpan(0, macSize), calculatedMac))
+                    throw new InvalidCipherTextException("mac check failed");
+
+                Reset();
+
+                return len - macSize;
+            }
+        }
+#endif
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void CalculateMac(ReadOnlySpan<byte> authText)
+        {
+            int blockSize = engine.GetBlockSize();
+
+            while (!authText.IsEmpty)
+            {
+                for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
+                {
+                    macBlock[byteIndex] ^= authText[byteIndex];
+                }
+
+                engine.ProcessBlock(macBlock, macBlock);
+
+                authText = authText[blockSize..];
+            }
+        }
+
+        private void ProcessBlock(ReadOnlySpan<byte> input, Span<byte> output)
+        {
             for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
             {
                 s[byteIndex] += counter[byteIndex];
             }
 
-            engine.ProcessBlock(s, 0, buffer, 0);
+            engine.ProcessBlock(s, buffer);
 
-            for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+            int blockSize = engine.GetBlockSize();
+            for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
             {
-                output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+                output[byteIndex] = (byte)(buffer[byteIndex] ^ input[byteIndex]);
             }
         }
-
+#else
         private void CalculateMac(byte[] authText, int authOff, int len)
         {
+            int blockSize = engine.GetBlockSize();
             int totalLen = len;
             while (totalLen > 0)
             {
-                for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+                for (int byteIndex = 0; byteIndex < blockSize; byteIndex++)
                 {
                     macBlock[byteIndex] ^= authText[authOff + byteIndex];
                 }
 
                 engine.ProcessBlock(macBlock, 0, macBlock, 0);
 
-                totalLen -= engine.GetBlockSize();
-                authOff += engine.GetBlockSize();
+                totalLen -= blockSize;
+                authOff += blockSize;
             }
         }
 
+        private void ProcessBlock(byte[] input, int inOff, byte[] output, int outOff)
+        {
+
+            for (int byteIndex = 0; byteIndex < counter.Length; byteIndex++)
+            {
+                s[byteIndex] += counter[byteIndex];
+            }
+
+            engine.ProcessBlock(s, 0, buffer, 0);
+
+            for (int byteIndex = 0; byteIndex < engine.GetBlockSize(); byteIndex++)
+            {
+                output[outOff + byteIndex] = (byte)(buffer[byteIndex] ^ input[inOff + byteIndex]);
+            }
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             byte[] buf = data.GetBuffer();
             int bufLen = Convert.ToInt32(data.Length);
 
@@ -395,8 +545,23 @@ namespace Org.BouncyCastle.Crypto.Modes
             Reset();
 
             return len;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            byte[] buf = data.GetBuffer();
+            int bufLen = Convert.ToInt32(data.Length);
+
+            int len = ProcessPacket(buf.AsSpan(0, bufLen), output);
+
+            Reset();
+
+            return len;
+        }
+#endif
+
         public virtual byte[] GetMac()
         {
             return Arrays.Clone(mac);
diff --git a/crypto/src/crypto/modes/OCBBlockCipher.cs b/crypto/src/crypto/modes/OCBBlockCipher.cs
index db6aa39ae..8281c96c1 100644
--- a/crypto/src/crypto/modes/OCBBlockCipher.cs
+++ b/crypto/src/crypto/modes/OCBBlockCipher.cs
@@ -331,6 +331,9 @@ namespace Org.BouncyCastle.Crypto.Modes
 
         public virtual int DoFinal(byte[] output, int outOff)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            return DoFinal(output.AsSpan(outOff));
+#else
             /*
              * For decryption, get the tag from the end of the message
              */
@@ -371,7 +374,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
                 Xor(mainBlock, Pad);
 
-                Check.OutputLength(output, outOff, mainBlockPos, "Output buffer too short");
+                Check.OutputLength(output, outOff, mainBlockPos, "output buffer too short");
                 Array.Copy(mainBlock, 0, output, outOff, mainBlockPos);
 
                 if (!forEncryption)
@@ -399,7 +402,7 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             if (forEncryption)
             {
-                Check.OutputLength(output, outOff, resultLen + macSize, "Output buffer too short");
+                Check.OutputLength(output, outOff, resultLen + macSize, "output buffer too short");
 
                 // Append tag to the message
                 Array.Copy(macBlock, 0, output, outOff + resultLen, macSize);
@@ -415,8 +418,99 @@ 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)
+        {
+            /*
+             * For decryption, get the tag from the end of the message
+             */
+            byte[] tag = null;
+            if (!forEncryption)
+            {
+                if (mainBlockPos < macSize)
+                    throw new InvalidCipherTextException("data too short");
+
+                mainBlockPos -= macSize;
+                tag = new byte[macSize];
+                Array.Copy(mainBlock, mainBlockPos, tag, 0, macSize);
+            }
+
+            /*
+             * HASH: Process any final partial block; compute final hash value
+             */
+            if (hashBlockPos > 0)
+            {
+                OCB_extend(hashBlock, hashBlockPos);
+                UpdateHASH(L_Asterisk);
+            }
+
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block
+             */
+            if (mainBlockPos > 0)
+            {
+                if (forEncryption)
+                {
+                    OCB_extend(mainBlock, mainBlockPos);
+                    Xor(Checksum, mainBlock);
+                }
+
+                Xor(OffsetMAIN, L_Asterisk);
+
+                byte[] Pad = new byte[16];
+                hashCipher.ProcessBlock(OffsetMAIN, 0, Pad, 0);
+
+                Xor(mainBlock, Pad);
+
+                Check.OutputLength(output, mainBlockPos, "output buffer too short");
+                mainBlock.AsSpan(0, mainBlockPos).CopyTo(output);
+
+                if (!forEncryption)
+                {
+                    OCB_extend(mainBlock, mainBlockPos);
+                    Xor(Checksum, mainBlock);
+                }
+            }
+
+            /*
+             * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag
+             */
+            Xor(Checksum, OffsetMAIN);
+            Xor(Checksum, L_Dollar);
+            hashCipher.ProcessBlock(Checksum, 0, Checksum, 0);
+            Xor(Checksum, Sum);
+
+            this.macBlock = new byte[macSize];
+            Array.Copy(Checksum, 0, macBlock, 0, macSize);
+
+            /*
+             * Validate or append tag and reset this cipher for the next run
+             */
+            int resultLen = mainBlockPos;
+
+            if (forEncryption)
+            {
+                // Append tag to the message
+                Check.OutputLength(output, resultLen + macSize, "output buffer too short");
+                macBlock.AsSpan(0, macSize).CopyTo(output[resultLen..]);
+                resultLen += macSize;
+            }
+            else
+            {
+                // Compare the tag from the message with the calculated one
+                if (!Arrays.ConstantTimeAreEqual(macBlock, tag))
+                    throw new InvalidCipherTextException("mac check in OCB failed");
+            }
+
+            Reset(false);
+
+            return resultLen;
+        }
+#endif
+
         public virtual void Reset()
         {
             Reset(true);
diff --git a/crypto/src/util/Arrays.cs b/crypto/src/util/Arrays.cs
index 7a1e80115..d3dae98a9 100644
--- a/crypto/src/util/Arrays.cs
+++ b/crypto/src/util/Arrays.cs
@@ -124,11 +124,27 @@ namespace Org.BouncyCastle.Utilities
             int d = 0;
             for (int i = 0; i < len; ++i)
             {
-                d |= (a[aOff + i] ^ b[bOff + i]);
+                d |= a[aOff + i] ^ b[bOff + i];
             }
             return 0 == d;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static bool ConstantTimeAreEqual(Span<byte> a, Span<byte> b)
+        {
+            if (a.Length != b.Length)
+                throw new ArgumentException("Spans to compare must have equal length");
+
+            int d = 0;
+            for (int i = 0, count = a.Length; i < count; ++i)
+            {
+                d |= a[i] ^ b[i];
+            }
+            return 0 == d;
+
+        }
+#endif
+
         public static bool AreEqual(
             int[]	a,
             int[]	b)