summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-09-01 21:23:15 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-09-01 21:23:15 +0700
commit7ef966bd9e5075f4dd6260cbdf94565d57af5338 (patch)
treeabfb156a78e0c0cf7e76e1c9ddd0845e523d7cdf
parentSpan-base variants for IBufferedCipher (diff)
downloadBouncyCastle.NET-ed25519-7ef966bd9e5075f4dd6260cbdf94565d57af5338.tar.xz
Span-based variant of IBufferedCipher.ProcessByte
- also some followup for earlier IBufferedCipher changes
-rw-r--r--crypto/src/crypto/BufferedAeadBlockCipher.cs18
-rw-r--r--crypto/src/crypto/BufferedAeadCipher.cs12
-rw-r--r--crypto/src/crypto/BufferedAsymmetricBlockCipher.cs22
-rw-r--r--crypto/src/crypto/BufferedBlockCipher.cs28
-rw-r--r--crypto/src/crypto/BufferedCipherBase.cs6
-rw-r--r--crypto/src/crypto/BufferedIesCipher.cs19
-rw-r--r--crypto/src/crypto/BufferedStreamCipher.cs109
-rw-r--r--crypto/src/crypto/IBufferedCipher.cs6
-rw-r--r--crypto/src/crypto/modes/CtsBlockCipher.cs156
-rw-r--r--crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs157
-rw-r--r--crypto/test/src/crypto/test/BlockCipherMonteCarloTest.cs42
-rw-r--r--crypto/test/src/crypto/test/BlockCipherVectorTest.cs28
12 files changed, 458 insertions, 145 deletions
diff --git a/crypto/src/crypto/BufferedAeadBlockCipher.cs b/crypto/src/crypto/BufferedAeadBlockCipher.cs
index 92eab9dd4..bf453feea 100644
--- a/crypto/src/crypto/BufferedAeadBlockCipher.cs
+++ b/crypto/src/crypto/BufferedAeadBlockCipher.cs
@@ -97,12 +97,9 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
-		{
-			return cipher.ProcessByte(input, output, outOff);
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            return cipher.ProcessByte(input, output, outOff);
 		}
 
 		public override byte[] ProcessByte(
@@ -124,7 +121,14 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		public override byte[] ProcessBytes(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            return cipher.ProcessByte(input, output);
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
diff --git a/crypto/src/crypto/BufferedAeadCipher.cs b/crypto/src/crypto/BufferedAeadCipher.cs
index aba64f0f4..fb3408e12 100644
--- a/crypto/src/crypto/BufferedAeadCipher.cs
+++ b/crypto/src/crypto/BufferedAeadCipher.cs
@@ -96,10 +96,7 @@ namespace Org.BouncyCastle.Crypto
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessByte(
-            byte input,
-            byte[] output,
-            int outOff)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
         {
             return cipher.ProcessByte(input, output, outOff);
         }
@@ -123,6 +120,13 @@ namespace Org.BouncyCastle.Crypto
             return outBytes;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            return cipher.ProcessByte(input, output);
+        }
+#endif
+
         public override byte[] ProcessBytes(
             byte[] input,
             int inOff,
diff --git a/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs b/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
index 83c2fe70c..be933a028 100644
--- a/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
+++ b/crypto/src/crypto/BufferedAsymmetricBlockCipher.cs
@@ -90,7 +90,27 @@ namespace Org.BouncyCastle.Crypto
 			return null;
 		}
 
-		public override byte[] ProcessBytes(
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            if (bufOff >= buffer.Length)
+                throw new DataLengthException("attempt to process message too long for cipher");
+
+            buffer[bufOff++] = input;
+            return 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            if (bufOff >= buffer.Length)
+                throw new DataLengthException("attempt to process message too long for cipher");
+
+            buffer[bufOff++] = input;
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
diff --git a/crypto/src/crypto/BufferedBlockCipher.cs b/crypto/src/crypto/BufferedBlockCipher.cs
index 3b000ed59..eaaae255e 100644
--- a/crypto/src/crypto/BufferedBlockCipher.cs
+++ b/crypto/src/crypto/BufferedBlockCipher.cs
@@ -126,10 +126,7 @@ namespace Org.BouncyCastle.Crypto
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessByte(byte input, byte[] output, int outOff)
 		{
 			buf[bufOff++] = input;
 
@@ -164,7 +161,24 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes;
 		}
 
-		public override byte[] ProcessBytes(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            buf[bufOff++] = input;
+
+            if (bufOff == buf.Length)
+            {
+				Check.OutputLength(output, buf.Length, "output buffer too short");
+
+                bufOff = 0;
+                return cipher.ProcessBlock(buf, output);
+            }
+
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
@@ -230,7 +244,7 @@ namespace Org.BouncyCastle.Crypto
 			if (length >= gapLen)
 			{
 				Array.Copy(input, inOff, buf, bufOff, gapLen);
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+				resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
 				bufOff = 0;
 				length -= gapLen;
 				inOff += gapLen;
@@ -265,7 +279,7 @@ namespace Org.BouncyCastle.Crypto
             if (input.Length >= gapLen)
             {
 				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
-                resultLen += cipher.ProcessBlock(buf, output);
+                resultLen = cipher.ProcessBlock(buf, output);
                 bufOff = 0;
 				input = input[gapLen..];
                 while (input.Length >= buf.Length)
diff --git a/crypto/src/crypto/BufferedCipherBase.cs b/crypto/src/crypto/BufferedCipherBase.cs
index 4b3069d0d..2e9026b14 100644
--- a/crypto/src/crypto/BufferedCipherBase.cs
+++ b/crypto/src/crypto/BufferedCipherBase.cs
@@ -32,7 +32,11 @@ namespace Org.BouncyCastle.Crypto
 			return outBytes.Length;
 		}
 
-		public virtual byte[] ProcessBytes(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public abstract int ProcessByte(byte input, Span<byte> output);
+#endif
+
+        public virtual byte[] ProcessBytes(
 			byte[] input)
 		{
 			return ProcessBytes(input, 0, input.Length);
diff --git a/crypto/src/crypto/BufferedIesCipher.cs b/crypto/src/crypto/BufferedIesCipher.cs
index 1aff47ab4..6497f7204 100644
--- a/crypto/src/crypto/BufferedIesCipher.cs
+++ b/crypto/src/crypto/BufferedIesCipher.cs
@@ -60,14 +60,27 @@ namespace Org.BouncyCastle.Crypto
 			return 0;
 		}
 
-		public override byte[] ProcessByte(
-			byte input)
+		public override byte[] ProcessByte(byte input)
 		{
 			buffer.WriteByte(input);
 			return null;
 		}
 
-		public override byte[] ProcessBytes(
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            buffer.WriteByte(input);
+            return 0;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            buffer.WriteByte(input);
+            return 0;
+        }
+#endif
+
+        public override byte[] ProcessBytes(
 			byte[]	input,
 			int		inOff,
 			int		length)
diff --git a/crypto/src/crypto/BufferedStreamCipher.cs b/crypto/src/crypto/BufferedStreamCipher.cs
index 8307429cb..8ee41c1e5 100644
--- a/crypto/src/crypto/BufferedStreamCipher.cs
+++ b/crypto/src/crypto/BufferedStreamCipher.cs
@@ -7,32 +7,29 @@ namespace Org.BouncyCastle.Crypto
 	public class BufferedStreamCipher
 		: BufferedCipherBase
 	{
-		private readonly IStreamCipher cipher;
+		private readonly IStreamCipher m_cipher;
 
-		public BufferedStreamCipher(
-			IStreamCipher cipher)
+		public BufferedStreamCipher(IStreamCipher cipher)
 		{
 			if (cipher == null)
 				throw new ArgumentNullException("cipher");
 
-			this.cipher = cipher;
+			this.m_cipher = cipher;
 		}
 
 		public override string AlgorithmName
 		{
-			get { return cipher.AlgorithmName; }
+			get { return m_cipher.AlgorithmName; }
 		}
 
-		public override void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
-		{
-			if (parameters is ParametersWithRandom)
+        public override void Init(bool forEncryption, ICipherParameters parameters)
+        {
+            if (parameters is ParametersWithRandom withRandom)
 			{
-				parameters = ((ParametersWithRandom) parameters).Parameters;
+				parameters = withRandom.Parameters;
 			}
 
-			cipher.Init(forEncryption, parameters);
+			m_cipher.Init(forEncryption, parameters);
 		}
 
 		public override int GetBlockSize()
@@ -40,115 +37,101 @@ namespace Org.BouncyCastle.Crypto
 			return 0;
 		}
 
-		public override int GetOutputSize(
-			int inputLen)
+		public override int GetOutputSize(int inputLen)
 		{
 			return inputLen;
 		}
 
-		public override int GetUpdateOutputSize(
-			int inputLen)
+		public override int GetUpdateOutputSize(int inputLen)
 		{
 			return inputLen;
 		}
 
-		public override byte[] ProcessByte(
-			byte input)
+		public override byte[] ProcessByte(byte input)
 		{
-			return new byte[]{ cipher.ReturnByte(input) };
+			return new byte[]{ m_cipher.ReturnByte(input) };
 		}
 
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
-		{
-			if (outOff >= output.Length)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
+        {
+            if (outOff >= output.Length)
 				throw new DataLengthException("output buffer too short");
 
-			output[outOff] = cipher.ReturnByte(input);
+			output[outOff] = m_cipher.ReturnByte(input);
 			return 1;
 		}
 
-		public override byte[] ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length)
-		{
-			if (length < 1)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            output[0] = m_cipher.ReturnByte(input);
+            return 1;
+        }
+#endif
+
+        public override byte[] ProcessBytes(byte[] input, int inOff, int length)
+        {
+            if (length < 1)
 				return null;
 
 			byte[] output = new byte[length];
-			cipher.ProcessBytes(input, inOff, length, output, 0);
+			m_cipher.ProcessBytes(input, inOff, length, output, 0);
 			return output;
 		}
 
-		public override int ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
-		{
-			if (length < 1)
+        public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
+        {
+            if (length < 1)
 				return 0;
 
-			if (length > 0)
-			{
-				cipher.ProcessBytes(input, inOff, length, output, outOff);
-			}
-
+			m_cipher.ProcessBytes(input, inOff, length, output, outOff);
 			return length;
 		}
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
 		public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
 		{
-			cipher.ProcessBytes(input, output);
+			m_cipher.ProcessBytes(input, output);
 			return input.Length;
 		}
 #endif
 
 		public override byte[] DoFinal()
 		{
-			Reset();
+			m_cipher.Reset();
 
 			return EmptyBuffer;
 		}
 
-		public override byte[] DoFinal(
-			byte[]	input,
-			int		inOff,
-			int		length)
+		public override byte[] DoFinal(byte[] input, int inOff, int length)
 		{
 			if (length < 1)
 				return EmptyBuffer;
 
-			byte[] output = ProcessBytes(input, inOff, length);
-
-			Reset();
-
-			return output;
+            byte[] output = new byte[length];
+            m_cipher.ProcessBytes(input, inOff, length, output, 0);
+            m_cipher.Reset();
+            return output;
 		}
 
 #if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         public override int DoFinal(Span<byte> output)
 		{
-			Reset();
+			m_cipher.Reset();
 			return 0;
 		}
 
-        public virtual int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
+        public override int DoFinal(ReadOnlySpan<byte> input, Span<byte> output)
         {
-            int len = ProcessBytes(input, output);
-			Reset();
-            return len;
+            m_cipher.ProcessBytes(input, output);
+            m_cipher.Reset();
+            return input.Length;
         }
 #endif
 
         public override void Reset()
 		{
-			cipher.Reset();
+			m_cipher.Reset();
 		}
 	}
 }
diff --git a/crypto/src/crypto/IBufferedCipher.cs b/crypto/src/crypto/IBufferedCipher.cs
index ddfb524c9..4471c42c9 100644
--- a/crypto/src/crypto/IBufferedCipher.cs
+++ b/crypto/src/crypto/IBufferedCipher.cs
@@ -23,7 +23,11 @@ namespace Org.BouncyCastle.Crypto
 		byte[] ProcessByte(byte input);
 		int ProcessByte(byte input, byte[] output, int outOff);
 
-		byte[] ProcessBytes(byte[] input);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        int ProcessByte(byte input, Span<byte> output);
+#endif
+
+        byte[] ProcessBytes(byte[] input);
 		byte[] ProcessBytes(byte[] input, int inOff, int length);
 		int ProcessBytes(byte[] input, byte[] output, int outOff);
 		int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff);
diff --git a/crypto/src/crypto/modes/CtsBlockCipher.cs b/crypto/src/crypto/modes/CtsBlockCipher.cs
index b1b00c5fa..5a1682568 100644
--- a/crypto/src/crypto/modes/CtsBlockCipher.cs
+++ b/crypto/src/crypto/modes/CtsBlockCipher.cs
@@ -1,9 +1,6 @@
 using System;
 using System.Diagnostics;
 
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Crypto.Parameters;
-
 namespace Org.BouncyCastle.Crypto.Modes
 {
     /**
@@ -80,10 +77,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessByte(
-            byte	input,
-            byte[]	output,
-            int		outOff)
+        public override int ProcessByte(byte input, byte[] output, int outOff)
         {
             int resultLen = 0;
 
@@ -101,7 +95,27 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessByte(byte input, Span<byte> output)
+        {
+            int resultLen = 0;
+
+            if (bufOff == buf.Length)
+            {
+                resultLen = cipher.ProcessBlock(buf, output);
+                Debug.Assert(resultLen == blockSize);
+
+                Array.Copy(buf, blockSize, buf, 0, blockSize);
+                bufOff = blockSize;
+            }
+
+            buf[bufOff++] = input;
+
+            return resultLen;
+        }
+#endif
+
+        /**
         * process an array of bytes, producing output if necessary.
         *
         * @param in the input byte array.
@@ -113,12 +127,7 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception DataLengthException if there isn't enough space in out.
         * @exception InvalidOperationException if the cipher isn't initialised.
         */
-        public override int ProcessBytes(
-            byte[]	input,
-            int		inOff,
-            int		length,
-            byte[]	output,
-            int		outOff)
+        public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
         {
             if (length < 0)
                 throw new ArgumentException("Can't have a negative input length!");
@@ -138,7 +147,7 @@ namespace Org.BouncyCastle.Crypto.Modes
             {
                 Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-                resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+                resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
                 Array.Copy(buf, blockSize, buf, 0, blockSize);
 
                 bufOff = blockSize;
@@ -164,6 +173,49 @@ namespace Org.BouncyCastle.Crypto.Modes
             return resultLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+        {
+            int blockSize = GetBlockSize();
+            int outLength = GetUpdateOutputSize(input.Length);
+
+            if (outLength > 0)
+            {
+                Check.OutputLength(output, outLength, "output buffer too short");
+            }
+
+            int resultLen = 0;
+            int gapLen = buf.Length - bufOff;
+
+            if (input.Length > gapLen)
+            {
+                input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+                resultLen = cipher.ProcessBlock(buf, output);
+                Array.Copy(buf, blockSize, buf, 0, blockSize);
+
+                bufOff = blockSize;
+
+                input = input[gapLen..];
+
+                while (input.Length > blockSize)
+                {
+                    input[..blockSize].CopyTo(buf.AsSpan(bufOff));
+                    resultLen += cipher.ProcessBlock(buf, output[resultLen..]);
+                    Array.Copy(buf, blockSize, buf, 0, blockSize);
+
+                    input = input[blockSize..];
+                }
+            }
+
+            input.CopyTo(buf.AsSpan(bufOff));
+
+            bufOff += input.Length;
+
+            return resultLen;
+        }
+#endif
+
         /**
         * Process the last block in the buffer.
         *
@@ -177,14 +229,10 @@ namespace Org.BouncyCastle.Crypto.Modes
         * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
         * case the exception will never Get thrown).
         */
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             if (bufOff + outOff > output.Length)
-            {
-                throw new DataLengthException("output buffer too small in doFinal");
-            }
+                throw new DataLengthException("output buffer too small in DoFinal");
 
             int blockSize = cipher.GetBlockSize();
             int length = bufOff - blockSize;
@@ -195,9 +243,7 @@ namespace Org.BouncyCastle.Crypto.Modes
                 cipher.ProcessBlock(buf, 0, block, 0);
 
 				if (bufOff < blockSize)
-				{
 					throw new DataLengthException("need at least one block of input for CTS");
-				}
 
                 for (int i = bufOff; i != buf.Length; i++)
                 {
@@ -244,5 +290,69 @@ namespace Org.BouncyCastle.Crypto.Modes
 
             return offset;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (bufOff > output.Length)
+                throw new DataLengthException("output buffer too small in DoFinal");
+
+            int blockSize = cipher.GetBlockSize();
+            int length = bufOff - blockSize;
+            Span<byte> block = stackalloc byte[blockSize];
+
+            if (forEncryption)
+            {
+                cipher.ProcessBlock(buf, block);
+
+                if (bufOff < blockSize)
+                    throw new DataLengthException("need at least one block of input for CTS");
+
+                for (int i = bufOff; i != buf.Length; i++)
+                {
+                    buf[i] = block[i - blockSize];
+                }
+
+                for (int i = blockSize; i != bufOff; i++)
+                {
+                    buf[i] ^= block[i - blockSize];
+                }
+
+                IBlockCipher c = (cipher is CbcBlockCipher)
+                    ? ((CbcBlockCipher)cipher).GetUnderlyingCipher()
+                    : cipher;
+
+                c.ProcessBlock(buf.AsSpan(blockSize), output);
+
+                block[..length].CopyTo(output[blockSize..]);
+            }
+            else
+            {
+                Span<byte> lastBlock = stackalloc byte[blockSize];
+
+                IBlockCipher c = (cipher is CbcBlockCipher)
+                    ? ((CbcBlockCipher)cipher).GetUnderlyingCipher()
+                    : cipher;
+
+                c.ProcessBlock(buf, block);
+
+                for (int i = blockSize; i != bufOff; i++)
+                {
+                    lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
+                }
+
+                buf.AsSpan(blockSize, length).CopyTo(block);
+
+                cipher.ProcessBlock(block, output);
+                lastBlock[..length].CopyTo(output[blockSize..]);
+            }
+
+            int offset = bufOff;
+
+            Reset();
+
+            return offset;
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs b/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
index 5d2f8cf15..a2d4dcc32 100644
--- a/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
+++ b/crypto/src/crypto/paddings/PaddedBufferedBlockCipher.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
 
@@ -25,8 +24,8 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @param padding the padding type.
 		*/
 		public PaddedBufferedBlockCipher(
-			IBlockCipher		cipher,
-			IBlockCipherPadding	padding)
+			IBlockCipher cipher,
+			IBlockCipherPadding padding)
 		{
 			this.cipher = cipher;
 			this.padding = padding;
@@ -42,7 +41,9 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		*/
 		public PaddedBufferedBlockCipher(
 			IBlockCipher cipher)
-			: this(cipher, new Pkcs7Padding())    { }
+			: this(cipher, new Pkcs7Padding())
+		{
+		}
 
 		/**
 		* initialise the cipher.
@@ -54,8 +55,8 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* inappropriate.
 		*/
 		public override void Init(
-			bool				forEncryption,
-			ICipherParameters	parameters)
+			bool forEncryption,
+			ICipherParameters parameters)
 		{
 			this.forEncryption = forEncryption;
 
@@ -110,8 +111,8 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		public override int GetUpdateOutputSize(
 			int length)
 		{
-			int total       = length + bufOff;
-			int leftOver    = total % buf.Length;
+			int total = length + bufOff;
+			int leftOver = total % buf.Length;
 
 			if (leftOver == 0)
 			{
@@ -131,10 +132,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessByte(
-			byte	input,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessByte(byte input, byte[] output, int outOff)
 		{
 			int resultLen = 0;
 
@@ -149,6 +147,23 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			return resultLen;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int ProcessByte(byte input, Span<byte> output)
+		{
+			int resultLen = 0;
+
+			if (bufOff == buf.Length)
+			{
+				resultLen = cipher.ProcessBlock(buf, output);
+				bufOff = 0;
+			}
+
+			buf[bufOff++] = input;
+
+			return resultLen;
+		}
+#endif
+
 		/**
 		* process an array of bytes, producing output if necessary.
 		*
@@ -161,24 +176,17 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* @exception DataLengthException if there isn't enough space in out.
 		* @exception InvalidOperationException if the cipher isn't initialised.
 		*/
-		public override int ProcessBytes(
-			byte[]	input,
-			int		inOff,
-			int		length,
-			byte[]	output,
-			int		outOff)
+		public override int ProcessBytes(byte[] input, int inOff, int length, byte[] output, int outOff)
 		{
 			if (length < 0)
-			{
 				throw new ArgumentException("Can't have a negative input length!");
-			}
 
 			int blockSize = GetBlockSize();
 			int outLength = GetUpdateOutputSize(length);
 
 			if (outLength > 0)
 			{
-                Check.OutputLength(output, outOff, outLength, "output buffer too short");
+				Check.OutputLength(output, outOff, outLength, "output buffer too short");
 			}
 
 			int resultLen = 0;
@@ -188,7 +196,7 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			{
 				Array.Copy(input, inOff, buf, bufOff, gapLen);
 
-				resultLen += cipher.ProcessBlock(buf, 0, output, outOff);
+				resultLen = cipher.ProcessBlock(buf, 0, output, outOff);
 
 				bufOff = 0;
 				length -= gapLen;
@@ -210,7 +218,46 @@ namespace Org.BouncyCastle.Crypto.Paddings
 			return resultLen;
 		}
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int ProcessBytes(ReadOnlySpan<byte> input, Span<byte> output)
+		{
+			int blockSize = GetBlockSize();
+			int outLength = GetUpdateOutputSize(input.Length);
+
+			if (outLength > 0)
+			{
+				Check.OutputLength(output, outLength, "output buffer too short");
+			}
+
+			int resultLen = 0;
+			int gapLen = buf.Length - bufOff;
+
+			if (input.Length > gapLen)
+			{
+				input[..gapLen].CopyTo(buf.AsSpan(bufOff));
+
+				resultLen = cipher.ProcessBlock(buf, output);
+
+				bufOff = 0;
+				input = input[gapLen..];
+
+				while (input.Length > buf.Length)
+				{
+					resultLen += cipher.ProcessBlock(input, output[resultLen..]);
+
+					input = input[blockSize..];
+				}
+			}
+
+			input.CopyTo(buf.AsSpan(bufOff));
+
+			bufOff += input.Length;
+
+			return resultLen;
+		}
+#endif
+
+        /**
 		* Process the last block in the buffer. If the buffer is currently
 		* full and padding needs to be added a call to doFinal will produce
 		* 2 * GetBlockSize() bytes.
@@ -224,11 +271,9 @@ namespace Org.BouncyCastle.Crypto.Paddings
 		* initialised.
 		* @exception InvalidCipherTextException if padding is expected and not found.
 		*/
-		public override int DoFinal(
-			byte[]  output,
-			int     outOff)
-		{
-			int blockSize = cipher.GetBlockSize();
+        public override int DoFinal(byte[] output, int outOff)
+        {
+            int blockSize = cipher.GetBlockSize();
 			int resultLen = 0;
 
 			if (forEncryption)
@@ -280,6 +325,60 @@ namespace Org.BouncyCastle.Crypto.Paddings
 
 			return resultLen;
 		}
-	}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override int DoFinal(Span<byte> output)
+		{
+            int blockSize = cipher.GetBlockSize();
+			int resultLen = 0;
+
+			if (forEncryption)
+			{
+				if (bufOff == blockSize)
+				{
+					if ((2 * blockSize) > output.Length)
+					{
+						Reset();
+
+						throw new OutputLengthException("output buffer too short");
+					}
+
+					resultLen = cipher.ProcessBlock(buf, output);
+					bufOff = 0;
+				}
+
+				padding.AddPadding(buf, bufOff);
+
+				resultLen += cipher.ProcessBlock(buf, output[resultLen..]);
+
+				Reset();
+			}
+			else
+			{
+				if (bufOff != blockSize)
+                {
+                    Reset();
+
+                    throw new DataLengthException("last block incomplete in decryption");
+                }
+
+                resultLen = cipher.ProcessBlock(buf, buf);
+				bufOff = 0;
+
+				try
+				{
+					resultLen -= padding.PadCount(buf);
+
+					buf.AsSpan(0, resultLen).CopyTo(output);
+				}
+				finally
+				{
+					Reset();
+				}
+			}
+
+			return resultLen;
+		}
+#endif
+	}
 }
diff --git a/crypto/test/src/crypto/test/BlockCipherMonteCarloTest.cs b/crypto/test/src/crypto/test/BlockCipherMonteCarloTest.cs
index 1a46a3201..1a7f04702 100644
--- a/crypto/test/src/crypto/test/BlockCipherMonteCarloTest.cs
+++ b/crypto/test/src/crypto/test/BlockCipherMonteCarloTest.cs
@@ -1,7 +1,5 @@
 using System;
-using System.IO;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
 
@@ -65,7 +63,7 @@ namespace Org.BouncyCastle.Crypto.Tests
 				Fail("failed - " + "expected " + Hex.ToHexString(output) + " got " + Hex.ToHexString(outBytes));
 			}
 
-			cipher.Init(false, param);
+            cipher.Init(false, param);
 
 			for (int i = 0; i != iterations; i++)
 			{
@@ -78,6 +76,40 @@ namespace Org.BouncyCastle.Crypto.Tests
 			{
 				Fail("failed reversal");
 			}
-		}
-	}
+
+            // NOTE: .NET Core 2.1 has Span<T>, but is tested against our .NET Standard 2.0 assembly.
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            cipher.Init(true, param);
+
+            Array.Copy(input, 0, outBytes, 0, outBytes.Length);
+
+            for (int i = 0; i != iterations; i++)
+            {
+                int len1 = cipher.ProcessBytes(outBytes, outBytes);
+
+				cipher.DoFinal(outBytes.AsSpan(len1));
+            }
+
+            if (!AreEqual(outBytes, output))
+            {
+                Fail("failed - " + "expected " + Hex.ToHexString(output) + " got " + Hex.ToHexString(outBytes));
+            }
+
+            cipher.Init(false, param);
+
+            for (int i = 0; i != iterations; i++)
+            {
+                int len1 = cipher.ProcessBytes(outBytes, outBytes);
+
+                cipher.DoFinal(outBytes.AsSpan(len1));
+            }
+
+            if (!AreEqual(input, outBytes))
+            {
+                Fail("failed reversal");
+            }
+#endif
+        }
+    }
 }
diff --git a/crypto/test/src/crypto/test/BlockCipherVectorTest.cs b/crypto/test/src/crypto/test/BlockCipherVectorTest.cs
index 1ce9fa477..58a138982 100644
--- a/crypto/test/src/crypto/test/BlockCipherVectorTest.cs
+++ b/crypto/test/src/crypto/test/BlockCipherVectorTest.cs
@@ -53,7 +53,7 @@ namespace Org.BouncyCastle.Crypto.Tests
 
 			int len1 = cipher.ProcessBytes(input, 0, input.Length, outBytes, 0);
 
-				cipher.DoFinal(outBytes, len1);
+			cipher.DoFinal(outBytes, len1);
 
 			if (!AreEqual(outBytes, output))
 			{
@@ -70,6 +70,32 @@ namespace Org.BouncyCastle.Crypto.Tests
 			{
 				Fail("failed reversal got " + Hex.ToHexString(outBytes));
 			}
+
+            // NOTE: .NET Core 2.1 has Span<T>, but is tested against our .NET Standard 2.0 assembly.
+//#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+			cipher.Init(true, param);
+
+			len1 = cipher.ProcessBytes(input, outBytes);
+
+			cipher.DoFinal(outBytes.AsSpan(len1));
+
+			if (!AreEqual(outBytes, output))
+			{
+				Fail("failed - " + "expected " + Hex.ToHexString(output) + " got " + Hex.ToHexString(outBytes));
+			}
+
+			cipher.Init(false, param);
+
+			len2 = cipher.ProcessBytes(output, outBytes);
+
+			cipher.DoFinal(outBytes.AsSpan(len2));
+
+			if (!AreEqual(input, outBytes))
+			{
+				Fail("failed reversal got " + Hex.ToHexString(outBytes));
+			}
+#endif
 		}
 	}
 }