summary refs log tree commit diff
path: root/crypto
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-08-23 14:08:56 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-08-23 14:08:56 +0700
commit5bae538777cfabc024f318c9d192a062d68d98b3 (patch)
treeda3da3f8c2ed19179f814cdd7716e214464ee141 /crypto
parentFix namespaces, refactoring (diff)
downloadBouncyCastle.NET-ed25519-5bae538777cfabc024f318c9d192a062d68d98b3.tar.xz
Span-based variants for IDigest, IXof
Diffstat (limited to 'crypto')
-rw-r--r--crypto/src/crypto/IDigest.cs84
-rw-r--r--crypto/src/crypto/IXof.cs27
-rw-r--r--crypto/src/crypto/digests/Blake2bDigest.cs132
-rw-r--r--crypto/src/crypto/digests/Blake2sDigest.cs137
-rw-r--r--crypto/src/crypto/digests/CSHAKEDigest.cs19
-rw-r--r--crypto/src/crypto/digests/DSTU7564Digest.cs131
-rw-r--r--crypto/src/crypto/digests/GOST3411Digest.cs54
-rw-r--r--crypto/src/crypto/digests/GOST3411_2012Digest.cs92
-rw-r--r--crypto/src/crypto/digests/GOST3411_2012_256Digest.cs12
-rw-r--r--crypto/src/crypto/digests/GeneralDigest.cs50
-rw-r--r--crypto/src/crypto/digests/Haraka256Digest.cs90
-rw-r--r--crypto/src/crypto/digests/Haraka512Digest.cs122
-rw-r--r--crypto/src/crypto/digests/HarakaBase.cs12
-rw-r--r--crypto/src/crypto/digests/KeccakDigest.cs97
-rw-r--r--crypto/src/crypto/digests/LongDigest.cs289
-rw-r--r--crypto/src/crypto/digests/MD2Digest.cs59
-rw-r--r--crypto/src/crypto/digests/MD4Digest.cs53
-rw-r--r--crypto/src/crypto/digests/MD5Digest.cs32
-rw-r--r--crypto/src/crypto/digests/NonMemoableDigest.cs14
-rw-r--r--crypto/src/crypto/digests/NullDigest.cs35
-rw-r--r--crypto/src/crypto/digests/ParallelHash.cs116
-rw-r--r--crypto/src/crypto/digests/RipeMD128Digest.cs28
-rw-r--r--crypto/src/crypto/digests/RipeMD160Digest.cs29
-rw-r--r--crypto/src/crypto/digests/RipeMD256Digest.cs32
-rw-r--r--crypto/src/crypto/digests/RipeMD320Digest.cs34
-rw-r--r--crypto/src/crypto/digests/SHA3Digest.cs9
-rw-r--r--crypto/src/crypto/digests/SM3Digest.cs34
-rw-r--r--crypto/src/crypto/digests/Sha1Digest.cs34
-rw-r--r--crypto/src/crypto/digests/Sha224Digest.cs44
-rw-r--r--crypto/src/crypto/digests/Sha256Digest.cs59
-rw-r--r--crypto/src/crypto/digests/Sha384Digest.cs25
-rw-r--r--crypto/src/crypto/digests/Sha512Digest.cs21
-rw-r--r--crypto/src/crypto/digests/Sha512tDigest.cs47
-rw-r--r--crypto/src/crypto/digests/ShakeDigest.cs28
-rw-r--r--crypto/src/crypto/digests/ShortenedDigest.cs25
-rw-r--r--crypto/src/crypto/digests/SkeinDigest.cs16
-rw-r--r--crypto/src/crypto/digests/SkeinEngine.cs101
-rw-r--r--crypto/src/crypto/digests/TigerDigest.cs70
-rw-r--r--crypto/src/crypto/digests/TupleHash.cs53
-rw-r--r--crypto/src/crypto/digests/WhirlpoolDigest.cs24
-rw-r--r--crypto/src/crypto/digests/XofUtils.cs50
-rw-r--r--crypto/src/crypto/macs/KMac.cs71
-rw-r--r--crypto/src/pqc/crypto/lms/LMSContext.cs14
-rw-r--r--crypto/src/security/DigestUtilities.cs31
-rw-r--r--crypto/test/src/crypto/test/Blake2bDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/Blake2sDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/CSHAKETest.cs3
-rw-r--r--crypto/test/src/crypto/test/DigestTest.cs59
-rw-r--r--crypto/test/src/crypto/test/Haraka256DigestTest.cs22
-rw-r--r--crypto/test/src/crypto/test/Haraka512DigestTest.cs21
-rw-r--r--crypto/test/src/crypto/test/KeccakDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/ParallelHashTest.cs3
-rw-r--r--crypto/test/src/crypto/test/SHA3DigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/ShakeDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/ShortenedDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/SkeinDigestTest.cs2
-rw-r--r--crypto/test/src/crypto/test/TupleHashTest.cs62
57 files changed, 2364 insertions, 356 deletions
diff --git a/crypto/src/crypto/IDigest.cs b/crypto/src/crypto/IDigest.cs
index 6769dcc42..2ba1705c7 100644
--- a/crypto/src/crypto/IDigest.cs
+++ b/crypto/src/crypto/IDigest.cs
@@ -2,60 +2,52 @@ using System;
 
 namespace Org.BouncyCastle.Crypto
 {
-    /**
-     * interface that a message digest conforms to.
-     */
+    /// <remarks>Base interface for a message digest.</remarks>
     public interface IDigest
     {
-        /**
-         * return the algorithm name
-         *
-         * @return the algorithm name
-         */
+        /// <summary>the algorithm name</summary>
         string AlgorithmName { get; }
 
-		/**
-         * return the size, in bytes, of the digest produced by this message digest.
-         *
-         * @return the size, in bytes, of the digest produced by this message digest.
-         */
-		int GetDigestSize();
-
-		/**
-         * return the size, in bytes, of the internal buffer used by this digest.
-         *
-         * @return the size, in bytes, of the internal buffer used by this digest.
-         */
-		int GetByteLength();
-
-		/**
-         * update the message digest with a single byte.
-         *
-         * @param inByte the input byte to be entered.
-         */
+        /// <summary>Return the size, in bytes, of the digest produced by this message digest.</summary>
+        /// <returns>the size, in bytes, of the digest produced by this message digest.</returns>
+        int GetDigestSize();
+
+        /// <summary>Return the size, in bytes, of the internal buffer used by this digest.</summary>
+        /// <returns>the size, in bytes, of the internal buffer used by this digest.</returns>
+        int GetByteLength();
+
+        /// <summary>Update the message digest with a single byte.</summary>
+        /// <param name="input">the input byte to be entered.</param>
         void Update(byte input);
 
-        /**
-         * update the message digest with a block of bytes.
-         *
-         * @param input the byte array containing the data.
-         * @param inOff the offset into the byte array where the data starts.
-         * @param len the length of the data.
-         */
-        void BlockUpdate(byte[] input, int inOff, int length);
-
-        /**
-         * Close the digest, producing the final digest value. The doFinal
-         * call leaves the digest reset.
-         *
-         * @param output the array the digest is to be copied into.
-         * @param outOff the offset into the out array the digest is to start at.
-         */
+        /// <summary>Update the message digest with a block of bytes.</summary>
+        /// <param name="input">the byte array containing the data.</param>
+        /// <param name="inOff">the offset into the byte array where the data starts.</param>
+        /// <param name="inLen">the length of the data.</param>
+        void BlockUpdate(byte[] input, int inOff, int inLen);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Update the message digest with a span of bytes.</summary>
+        /// <param name="input">the span containing the data.</param>
+        void BlockUpdate(ReadOnlySpan<byte> input);
+#endif
+
+        /// <summary>Close the digest, producing the final digest value.</summary>
+        /// <remarks>This call leaves the digest reset.</remarks>
+        /// <param name="output">the byte array the digest is to be copied into.</param>
+        /// <param name="outOff">the offset into the byte array the digest is to start at.</param>
+        /// <returns>the number of bytes written</returns>
         int DoFinal(byte[] output, int outOff);
 
-        /**
-         * reset the digest back to it's initial state.
-         */
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>Close the digest, producing the final digest value.</summary>
+        /// <remarks>This call leaves the digest reset.</remarks>
+        /// <param name="output">the span the digest is to be copied into.</param>
+        /// <returns>the number of bytes written</returns>
+        int DoFinal(Span<byte> output);
+#endif
+
+        /// <summary>Reset the digest back to its initial state.</summary>
         void Reset();
     }
 }
diff --git a/crypto/src/crypto/IXof.cs b/crypto/src/crypto/IXof.cs
index f76304d48..8cddb2870 100644
--- a/crypto/src/crypto/IXof.cs
+++ b/crypto/src/crypto/IXof.cs
@@ -4,13 +4,13 @@ namespace Org.BouncyCastle.Crypto
 {
     /// <remarks>
     /// With FIPS PUB 202 a new kind of message digest was announced which supported extendable output, or variable digest sizes.
-    /// This interface provides the extra method required to support variable output on a digest implementation.
+    /// This interface provides the extra methods required to support variable output on a digest implementation.
     /// </remarks>
     public interface IXof
         : IDigest
     {
         /// <summary>
-        /// Output the results of the final calculation for this digest to outLen number of bytes.
+        /// Output the results of the final calculation for this XOF to outLen number of bytes.
         /// </summary>
         /// <param name="output">output array to write the output bytes to.</param>
         /// <param name="outOff">offset to start writing the bytes at.</param>
@@ -18,14 +18,33 @@ namespace Org.BouncyCastle.Crypto
         /// <returns>the number of bytes written</returns>
         int DoFinal(byte[] output, int outOff, int outLen);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
         /// <summary>
-        /// Start outputting the results of the final calculation for this digest. Unlike DoFinal, this method
-        /// will continue producing output until the Xof is explicitly reset, or signals otherwise.
+        /// Output the results of the final calculation for this XOF to fill the output span.
+        /// </summary>
+        /// <param name="output">span to fill with the output bytes.</param>
+        /// <returns>the number of bytes written</returns>
+        int OutputFinal(Span<byte> output);
+#endif
+
+        /// <summary>
+        /// Start outputting the results of the final calculation for this XOF. Unlike DoFinal, this method
+        /// will continue producing output until the XOF is explicitly reset, or signals otherwise.
         /// </summary>
         /// <param name="output">output array to write the output bytes to.</param>
         /// <param name="outOff">offset to start writing the bytes at.</param>
         /// <param name="outLen">the number of output bytes requested.</param>
         /// <returns>the number of bytes written</returns>
         int DoOutput(byte[] output, int outOff, int outLen);
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        /// <summary>
+        /// Start outputting the results of the final calculation for this XOF. Unlike OutputFinal, this method
+        /// will continue producing output until the XOF is explicitly reset, or signals otherwise.
+        /// </summary>
+        /// <param name="output">span to fill with the output bytes.</param>
+        /// <returns>the number of bytes written</returns>
+        int Output(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/Blake2bDigest.cs b/crypto/src/crypto/digests/Blake2bDigest.cs
index 1ac9cfa35..ec80a3355 100644
--- a/crypto/src/crypto/digests/Blake2bDigest.cs
+++ b/crypto/src/crypto/digests/Blake2bDigest.cs
@@ -357,11 +357,63 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
 
             // fill the buffer with left bytes, this might be a full block
-            Array.Copy(message, messagePos, buffer, 0, offset + len
-                - messagePos);
+            Array.Copy(message, messagePos, buffer, 0, offset + len - messagePos);
             bufferPos += offset + len - messagePos;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (input.IsEmpty)
+                return;
+
+            int remainingLength = 0; // left bytes of buffer
+
+            if (bufferPos != 0)
+            { // commenced, incomplete buffer
+
+                // complete the buffer:
+                remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+                if (remainingLength < input.Length)
+                { // full buffer + at least 1 byte
+                    input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos));
+                    t0 += BLOCK_LENGTH_BYTES;
+                    if (t0 == 0)
+                    { // if message > 2^64
+                        t1++;
+                    }
+                    Compress(buffer, 0);
+                    bufferPos = 0;
+                    Array.Clear(buffer, 0, buffer.Length);// clear buffer
+                }
+                else
+                {
+                    input.CopyTo(buffer.AsSpan(bufferPos));
+                    bufferPos += input.Length;
+                    return;
+                }
+            }
+
+            // process blocks except last block (also if last block is full)
+            int messagePos;
+            int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES;
+            for (messagePos = remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES)
+            { // block wise 128 bytes
+                // without buffer:
+                t0 += BLOCK_LENGTH_BYTES;
+                if (t0 == 0)
+                {
+                    t1++;
+                }
+                Compress(input[messagePos..]);
+            }
+
+            // fill the buffer with left bytes, this might be a full block
+            input[messagePos..].CopyTo(buffer.AsSpan());
+            bufferPos += input.Length - messagePos;
+        }
+#endif
+
         /**
          * close the digest, producing the final digest value. The doFinal
          * call leaves the digest reset.
@@ -382,19 +434,42 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
-            byte[] bytes = new byte[8];
-            for (int i = 0; i < chainValue.Length && (i * 8 < digestLength); i++)
+            int full = digestLength >> 3, partial = digestLength & 7;
+            Pack.UInt64_To_LE(chainValue, 0, full, output, outOffset);
+            if (partial > 0)
+            {
+                byte[] bytes = new byte[8];
+                Pack.UInt64_To_LE(chainValue[full], bytes, 0);
+                Array.Copy(bytes, 0, output, outOffset + digestLength - partial, partial);
+            }
+
+            Array.Clear(chainValue, 0, chainValue.Length);
+
+            Reset();
+
+            return digestLength;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            f0 = 0xFFFFFFFFFFFFFFFFUL;
+            t0 += (ulong)bufferPos;
+            if (bufferPos > 0 && t0 == 0)
             {
-                Pack.UInt64_To_LE(chainValue[i], bytes, 0);
+                t1++;
+            }
+            Compress(buffer, 0);
+            Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
+            Array.Clear(internalState, 0, internalState.Length);
 
-                if (i * 8 < digestLength - 8)
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 8, 8);
-                }
-                else
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 8, digestLength - (i * 8));
-                }
+            int full = digestLength >> 3, partial = digestLength & 7;
+            Pack.UInt64_To_LE(chainValue.AsSpan(0, full), output);
+            if (partial > 0)
+            {
+                Span<byte> bytes = stackalloc byte[8];
+                Pack.UInt64_To_LE(chainValue[full], bytes);
+                bytes[..partial].CopyTo(output[(digestLength - partial)..]);
             }
 
             Array.Clear(chainValue, 0, chainValue.Length);
@@ -403,6 +478,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             return digestLength;
         }
+#endif
 
         /**
          * Reset the digest back to it's initial state.
@@ -453,6 +529,36 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
+        {
+            InitializeInternalState();
+
+            Span<ulong> m = stackalloc ulong[16];
+            Pack.LE_To_UInt64(message, m);
+
+            for (int round = 0; round < ROUNDS; round++)
+            {
+                // G apply to columns of internalState:m[blake2b_sigma[round][2 * blockPos]] /+1
+                G(m[blake2b_sigma[round, 0]], m[blake2b_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2b_sigma[round, 2]], m[blake2b_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2b_sigma[round, 4]], m[blake2b_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2b_sigma[round, 6]], m[blake2b_sigma[round, 7]], 3, 7, 11, 15);
+                // G apply to diagonals of internalState:
+                G(m[blake2b_sigma[round, 8]], m[blake2b_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2b_sigma[round, 10]], m[blake2b_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2b_sigma[round, 12]], m[blake2b_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2b_sigma[round, 14]], m[blake2b_sigma[round, 15]], 3, 4, 9, 14);
+            }
+
+            // update chain values:
+            for (int offset = 0; offset < chainValue.Length; offset++)
+            {
+                chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
+            }
+        }
+#endif
+
         private void G(ulong m1, ulong m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
diff --git a/crypto/src/crypto/digests/Blake2sDigest.cs b/crypto/src/crypto/digests/Blake2sDigest.cs
index f50419126..187808aa0 100644
--- a/crypto/src/crypto/digests/Blake2sDigest.cs
+++ b/crypto/src/crypto/digests/Blake2sDigest.cs
@@ -381,6 +381,61 @@ namespace Org.BouncyCastle.Crypto.Digests
             bufferPos += offset + len - messagePos;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (input.IsEmpty)
+                return;
+
+            int remainingLength = 0; // left bytes of buffer
+
+            if (bufferPos != 0)
+            { // commenced, incomplete buffer
+
+                // complete the buffer:
+                remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
+                if (remainingLength < input.Length)
+                { // full buffer + at least 1 byte
+                    input[..remainingLength].CopyTo(buffer.AsSpan(bufferPos));
+                    t0 += BLOCK_LENGTH_BYTES;
+                    if (t0 == 0)
+                    { // if message > 2^32
+                        t1++;
+                    }
+                    Compress(buffer, 0);
+                    bufferPos = 0;
+                    Array.Clear(buffer, 0, buffer.Length);// clear buffer
+                }
+                else
+                {
+                    input.CopyTo(buffer.AsSpan(bufferPos));
+                    bufferPos += input.Length;
+                    return;
+                }
+            }
+
+            // process blocks except last block (also if last block is full)
+            int messagePos;
+            int blockWiseLastPos = input.Length - BLOCK_LENGTH_BYTES;
+            for (messagePos = remainingLength;
+                 messagePos < blockWiseLastPos;
+                 messagePos += BLOCK_LENGTH_BYTES)
+            { // block wise 64 bytes
+                // without buffer:
+                t0 += BLOCK_LENGTH_BYTES;
+                if (t0 == 0)
+                {
+                    t1++;
+                }
+                Compress(input[messagePos..]);
+            }
+
+            // fill the buffer with left bytes, this might be a full block
+            input[messagePos..].CopyTo(buffer.AsSpan());
+            bufferPos += input.Length - messagePos;
+        }
+#endif
+
         /**
          * Close the digest, producing the final digest value. The doFinal() call
          * leaves the digest reset. Key, salt and personal string remain.
@@ -402,19 +457,44 @@ namespace Org.BouncyCastle.Crypto.Digests
             Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
             Array.Clear(internalState, 0, internalState.Length);
 
-            byte[] bytes = new byte[4];
-            for (int i = 0; i < chainValue.Length && (i * 4 < digestLength); i++)
+            int full = digestLength >> 2, partial = digestLength & 3;
+            Pack.UInt32_To_LE(chainValue, 0, full, output, outOffset);
+            if (partial > 0)
+            {
+                byte[] bytes = new byte[4];
+                Pack.UInt32_To_LE(chainValue[full], bytes, 0);
+                Array.Copy(bytes, 0, output, outOffset + digestLength - partial, partial);
+            }
+
+            Array.Clear(chainValue, 0, chainValue.Length);
+
+            Reset();
+
+            return digestLength;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            f0 = 0xFFFFFFFFU;
+            t0 += (uint)bufferPos;
+            // bufferPos may be < 64, so (t0 == 0) does not work
+            // for 2^32 < message length > 2^32 - 63
+            if ((t0 < 0) && (bufferPos > -t0))
             {
-                Pack.UInt32_To_LE(chainValue[i], bytes, 0);
+                t1++;
+            }
+            Compress(buffer, 0);
+            Array.Clear(buffer, 0, buffer.Length);// Holds eventually the key if input is null
+            Array.Clear(internalState, 0, internalState.Length);
 
-                if (i * 4 < digestLength - 4)
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 4, 4);
-                }
-                else
-                {
-                    Array.Copy(bytes, 0, output, outOffset + i * 4, digestLength - (i * 4));
-                }
+            int full = digestLength >> 2, partial = digestLength & 3;
+            Pack.UInt32_To_LE(chainValue.AsSpan(0, full), output);
+            if (partial > 0)
+            {
+                Span<byte> bytes = stackalloc byte[4];
+                Pack.UInt32_To_LE(chainValue[full], bytes);
+                bytes[..partial].CopyTo(output[(digestLength - partial)..]);
             }
 
             Array.Clear(chainValue, 0, chainValue.Length);
@@ -423,6 +503,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             return digestLength;
         }
+#endif
 
         /**
          * Reset the digest back to its initial state. The key, the salt and the
@@ -453,9 +534,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             for (int round = 0; round < ROUNDS; round++)
             {
-
-                // G apply to columns of internalState:m[blake2s_sigma[round][2 *
-                // blockPos]] /+1
+                // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
                 G(m[blake2s_sigma[round,0]], m[blake2s_sigma[round,1]], 0, 4, 8, 12);
                 G(m[blake2s_sigma[round,2]], m[blake2s_sigma[round,3]], 1, 5, 9, 13);
                 G(m[blake2s_sigma[round,4]], m[blake2s_sigma[round,5]], 2, 6, 10, 14);
@@ -474,6 +553,36 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> message)
+        {
+            InitializeInternalState();
+
+            Span<uint> m = stackalloc uint[16];
+            Pack.LE_To_UInt32(message, m);
+
+            for (int round = 0; round < ROUNDS; round++)
+            {
+                // G apply to columns of internalState: m[blake2s_sigma[round][2 * blockPos]] /+1
+                G(m[blake2s_sigma[round, 0]], m[blake2s_sigma[round, 1]], 0, 4, 8, 12);
+                G(m[blake2s_sigma[round, 2]], m[blake2s_sigma[round, 3]], 1, 5, 9, 13);
+                G(m[blake2s_sigma[round, 4]], m[blake2s_sigma[round, 5]], 2, 6, 10, 14);
+                G(m[blake2s_sigma[round, 6]], m[blake2s_sigma[round, 7]], 3, 7, 11, 15);
+                // G apply to diagonals of internalState:
+                G(m[blake2s_sigma[round, 8]], m[blake2s_sigma[round, 9]], 0, 5, 10, 15);
+                G(m[blake2s_sigma[round, 10]], m[blake2s_sigma[round, 11]], 1, 6, 11, 12);
+                G(m[blake2s_sigma[round, 12]], m[blake2s_sigma[round, 13]], 2, 7, 8, 13);
+                G(m[blake2s_sigma[round, 14]], m[blake2s_sigma[round, 15]], 3, 4, 9, 14);
+            }
+
+            // update chain values:
+            for (int offset = 0; offset < chainValue.Length; offset++)
+            {
+                chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
+            }
+        }
+#endif
+
         private void G(uint m1, uint m2, int posA, int posB, int posC, int posD)
         {
             internalState[posA] = internalState[posA] + internalState[posB] + m1;
diff --git a/crypto/src/crypto/digests/CSHAKEDigest.cs b/crypto/src/crypto/digests/CSHAKEDigest.cs
index c3b0b7068..fc37b865c 100644
--- a/crypto/src/crypto/digests/CSHAKEDigest.cs
+++ b/crypto/src/crypto/digests/CSHAKEDigest.cs
@@ -95,6 +95,25 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Output(Span<byte> output)
+        {
+            if (diff == null)
+            {
+                return base.Output(output);
+            }
+
+            if (!squeezing)
+            {
+                AbsorbBits(0x00, 2);
+            }
+
+            Squeeze(output);
+
+            return output.Length;
+        }
+#endif
+
         public override void Reset()
         {
             base.Reset();
diff --git a/crypto/src/crypto/digests/DSTU7564Digest.cs b/crypto/src/crypto/digests/DSTU7564Digest.cs
index b2d90799d..901e549be 100644
--- a/crypto/src/crypto/digests/DSTU7564Digest.cs
+++ b/crypto/src/crypto/digests/DSTU7564Digest.cs
@@ -1,11 +1,7 @@
 using System;
 
-using Org.BouncyCastle.Crypto.Engines;
-using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Crypto.Utilities;
-
 using Org.BouncyCastle.Utilities;
-using Org.BouncyCastle.Utilities.Encoders;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
@@ -132,25 +128,103 @@ namespace Org.BouncyCastle.Crypto.Digests
                 --length;
             }
 
-            if (length > 0)
+            while (length >= blockSize)
+            {
+                ProcessBlock(input, inOff);
+                inOff += blockSize;
+                length -= blockSize;
+                ++inputBlocks;
+            }
+
+            while (length > 0)
+            {
+                Update(input[inOff++]);
+                --length;
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            while (bufOff != 0 && input.Length > 0)
             {
-                while (length >= blockSize)
+                Update(input[0]);
+                input = input[1..];
+            }
+
+            while (input.Length >= blockSize)
+            {
+                ProcessBlock(input);
+                input = input[blockSize..];
+                ++inputBlocks;
+            }
+
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
+
+        public virtual int DoFinal(byte[] output, int outOff)
+        {
+            // Apply padding: terminator byte and 96-bit length field
+            {
+                int inputBytes = bufOff;
+                buf[bufOff++] = (byte)0x80;
+
+                int lenPos = blockSize - 12;
+                if (bufOff > lenPos)
                 {
-                    ProcessBlock(input, inOff);
-                    inOff += blockSize;
-                    length -= blockSize;
-                    ++inputBlocks;
+                    while (bufOff < blockSize)
+                    {
+                        buf[bufOff++] = 0;
+                    }
+                    bufOff = 0;
+                    ProcessBlock(buf, 0);
                 }
 
-                while (length > 0)
+                while (bufOff < lenPos)
                 {
-                    Update(input[inOff++]);
-                    --length;
+                    buf[bufOff++] = 0;
+                }
+
+                ulong c = ((inputBlocks & 0xFFFFFFFFUL) * (ulong)blockSize + (uint)inputBytes) << 3;
+                Pack.UInt32_To_LE((uint)c, buf, bufOff);
+                bufOff += 4;
+                c >>= 32;
+                c += ((inputBlocks >> 32) * (ulong)blockSize) << 3;
+                Pack.UInt64_To_LE(c, buf, bufOff);
+                //bufOff += 8;
+                ProcessBlock(buf, 0);
+            }
+
+            {
+                Array.Copy(state, 0, tempState1, 0, columns);
+
+                P(tempState1);
+
+                for (int col = 0; col < columns; ++col)
+                {
+                    state[col] ^= tempState1[col];
                 }
             }
+
+            int neededColumns = hashSize / 8;
+            for (int col = columns - neededColumns; col < columns; ++col)
+            {
+                Pack.UInt64_To_LE(state[col], output, outOff);
+                outOff += 8;
+            }
+
+            Reset();
+
+            return hashSize;
         }
 
-        public virtual int DoFinal(byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
         {
             // Apply padding: terminator byte and 96-bit length field
             {
@@ -179,7 +253,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                 c >>= 32;
                 c += ((inputBlocks >> 32) * (ulong)blockSize) << 3;
                 Pack.UInt64_To_LE(c, buf, bufOff);
-    //            bufOff += 8;
+                //bufOff += 8;
                 ProcessBlock(buf, 0);
             }
 
@@ -197,14 +271,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             int neededColumns = hashSize / 8;
             for (int col = columns - neededColumns; col < columns; ++col)
             {
-                Pack.UInt64_To_LE(state[col], output, outOff);
-                outOff += 8;
+                Pack.UInt64_To_LE(state[col], output);
+                output = output[8..];
             }
 
             Reset();
 
             return hashSize;
         }
+#endif
 
         public virtual void Reset()
         {
@@ -236,6 +311,28 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected virtual void ProcessBlock(ReadOnlySpan<byte> input)
+        {
+            for (int col = 0; col < columns; ++col)
+            {
+                ulong word = Pack.LE_To_UInt64(input);
+                input = input[8..];
+
+                tempState1[col] = state[col] ^ word;
+                tempState2[col] = word;
+            }
+
+            P(tempState1);
+            Q(tempState2);
+
+            for (int col = 0; col < columns; ++col)
+            {
+                state[col] ^= tempState1[col] ^ tempState2[col];
+            }
+        }
+#endif
+
         private void P(ulong[] s)
         {
             for (int round = 0; round < rounds; ++round)
diff --git a/crypto/src/crypto/digests/GOST3411Digest.cs b/crypto/src/crypto/digests/GOST3411Digest.cs
index 123751baa..dc1d376c3 100644
--- a/crypto/src/crypto/digests/GOST3411Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411Digest.cs
@@ -91,10 +91,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			byteCount++;
 		}
 
-		public void BlockUpdate(
-			byte[]	input,
-			int		inOff,
-			int		length)
+		public void BlockUpdate(byte[] input, int inOff, int length)
 		{
 			while ((xBufOff != 0) && (length > 0))
 			{
@@ -123,6 +120,34 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
+		{
+			while ((xBufOff != 0) && (input.Length > 0))
+			{
+				Update(input[0]);
+				input = input[1..];
+			}
+
+			while (input.Length >= xBuf.Length)
+			{
+				input[..xBuf.Length].CopyTo(xBuf.AsSpan());
+
+				sumByteArray(xBuf); // calc sum M
+				processBlock(xBuf, 0);
+				input = input[xBuf.Length..];
+				byteCount += (uint)xBuf.Length;
+			}
+
+			// load in the remainder.
+			while (input.Length > 0)
+			{
+				Update(input[0]);
+				input = input[1..];
+			}
+		}
+#endif
+
 		// (i + 1 + 4(k - 1)) = 8i + k      i = 0-3, k = 1-8
 		private byte[] K = new byte[32];
 
@@ -234,7 +259,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			Array.Copy(S, 0, H, 0, H.Length);
 		}
 
-		private void finish()
+		private void Finish()
 		{
 			ulong bitCount = byteCount * 8;
 			Pack.UInt64_To_LE(bitCount, L);
@@ -248,11 +273,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 			processBlock(Sum, 0);
 		}
 
-		public int DoFinal(
-			byte[]  output,
-			int     outOff)
+		public int DoFinal(byte[] output, int outOff)
 		{
-			finish();
+			Finish();
 
 			H.CopyTo(output, outOff);
 
@@ -261,6 +284,19 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DIGEST_LENGTH;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			Finish();
+
+			H.CopyTo(output);
+
+			Reset();
+
+			return DIGEST_LENGTH;
+		}
+#endif
+
 		/**
 		* reset the chaining variables to the IV values.
 		*/
diff --git a/crypto/src/crypto/digests/GOST3411_2012Digest.cs b/crypto/src/crypto/digests/GOST3411_2012Digest.cs
index 68cb6c035..259f4bcae 100644
--- a/crypto/src/crypto/digests/GOST3411_2012Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411_2012Digest.cs
@@ -1,10 +1,11 @@
 using System;
-using Org.BouncyCastle.Crypto;
+
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-    public abstract class Gost3411_2012Digest:IDigest,IMemoable
+    public abstract class Gost3411_2012Digest
+        : IDigest, IMemoable
     {
         private readonly byte[] IV = new byte[64];
         private readonly byte[] N = new byte[64];
@@ -21,8 +22,8 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         protected Gost3411_2012Digest(byte[] IV)
         {
-            System.Array.Copy(IV,this.IV,64);
-            System.Array.Copy(IV, h, 64);
+            Array.Copy(IV,this.IV,64);
+            Array.Copy(IV, h, 64);
         }
 
         public abstract string AlgorithmName { get; }
@@ -43,7 +44,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
             if (bOff != 64)
             {
-                System.Array.Copy(block, bOff, m, 64 - lenM, lenM);
+                Array.Copy(block, bOff, m, 64 - lenM, lenM);
             }
 
             g_N(h, N, m);
@@ -60,6 +61,39 @@ namespace Org.BouncyCastle.Crypto.Digests
             return 64;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            int lenM = 64 - bOff;
+
+            // At this point it is certain that lenM is smaller than 64
+            for (int i = 0; i != 64 - lenM; i++)
+            {
+                m[i] = 0;
+            }
+
+            m[63 - lenM] = 1;
+
+            if (bOff != 64)
+            {
+                Array.Copy(block, bOff, m, 64 - lenM, lenM);
+            }
+
+            g_N(h, N, m);
+            addMod512(N, lenM * 8);
+            addMod512(Sigma, m);
+            g_N(h, Zero, N);
+            g_N(h, Zero, Sigma);
+
+            reverse(h, tmp);
+
+            tmp.CopyTo(output);
+
+            Reset();
+            return 64;
+        }
+#endif
+
         public int GetByteLength()
         {
             return 64;
@@ -73,7 +107,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             bOff = 64;
             Arrays.Fill(N, (byte)0);
             Arrays.Fill(Sigma, (byte)0);
-            System.Array.Copy(IV, 0, h, 0, 64);
+            Array.Copy(IV, 0, h, 0, 64);
             Arrays.Fill(block, (byte)0);
         }
 
@@ -81,14 +115,14 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             Gost3411_2012Digest o = (Gost3411_2012Digest)other;
 
-            System.Array.Copy(o.IV, 0, this.IV, 0, 64);
-            System.Array.Copy(o.N, 0, this.N, 0, 64);
-            System.Array.Copy(o.Sigma, 0, this.Sigma, 0, 64);
-            System.Array.Copy(o.Ki, 0, this.Ki, 0, 64);
-            System.Array.Copy(o.m, 0, this.m, 0, 64);
-            System.Array.Copy(o.h, 0, this.h, 0, 64);
+            Array.Copy(o.IV, 0, this.IV, 0, 64);
+            Array.Copy(o.N, 0, this.N, 0, 64);
+            Array.Copy(o.Sigma, 0, this.Sigma, 0, 64);
+            Array.Copy(o.Ki, 0, this.Ki, 0, 64);
+            Array.Copy(o.m, 0, this.m, 0, 64);
+            Array.Copy(o.h, 0, this.h, 0, 64);
 
-            System.Array.Copy(o.block, 0, this.block, 0, 64);
+            Array.Copy(o.block, 0, this.block, 0, 64);
             this.bOff = o.bOff;
         }
 
@@ -104,7 +138,6 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-
         public void BlockUpdate(byte[] input, int inOff, int len)
         {
             while (bOff != 64 && len > 0)
@@ -114,7 +147,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
             while (len >= 64)
             {
-                System.Array.Copy(input, inOff, tmp, 0, 64);
+                Array.Copy(input, inOff, tmp, 0, 64);
                 reverse(tmp, block);
                 g_N(h, N, block);
                 addMod512(N, 512);
@@ -130,8 +163,31 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-    
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            while (bOff != 64 && input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+            while (input.Length >= 64)
+            {
+                input[..64].CopyTo(tmp.AsSpan());
+                reverse(tmp, block);
+                g_N(h, N, block);
+                addMod512(N, 512);
+                addMod512(Sigma, block);
 
+                input = input[64..];
+            }
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
 
         private void F(byte[] V)
         {
@@ -317,7 +373,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private void E(byte[] K, byte[] m)
         {
-            System.Array.Copy(K, 0, Ki, 0, 64);
+            Array.Copy(K, 0, Ki, 0, 64);
             xor512(K, m);
             F(K);
             for (int i = 0; i < 11; ++i)
@@ -334,7 +390,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private void g_N(byte[] h, byte[] N, byte[] m)
         {
-            System.Array.Copy(h, 0, tmp, 0, 64);
+            Array.Copy(h, 0, tmp, 0, 64);
 
             xor512(h, N);
             F(h);
diff --git a/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs b/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
index 77cf6c50f..47877b2b5 100644
--- a/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
+++ b/crypto/src/crypto/digests/GOST3411_2012_256Digest.cs
@@ -46,6 +46,18 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return 32;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Span<byte> result = stackalloc byte[64];
+            base.DoFinal(result);
+
+            result[32..].CopyTo(output);
+
+            return 32;
+        }
+#endif
+
         public override IMemoable Copy()
         {
 			return new Gost3411_2012_256Digest(this);
diff --git a/crypto/src/crypto/digests/GeneralDigest.cs b/crypto/src/crypto/digests/GeneralDigest.cs
index c38b35fd3..02bad2cfb 100644
--- a/crypto/src/crypto/digests/GeneralDigest.cs
+++ b/crypto/src/crypto/digests/GeneralDigest.cs
@@ -95,6 +95,50 @@ namespace Org.BouncyCastle.Crypto.Digests
             byteCount += length;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int length = input.Length;
+
+            //
+            // fill the current word
+            //
+            int i = 0;
+            if (xBufOff != 0)
+            {
+                while (i < length)
+                {
+                    xBuf[xBufOff++] = input[i++];
+                    if (xBufOff == 4)
+                    {
+                        ProcessWord(xBuf, 0);
+                        xBufOff = 0;
+                        break;
+                    }
+                }
+            }
+
+            //
+            // process whole words.
+            //
+            int limit = length - 3;
+            for (; i < limit; i += 4)
+            {
+                ProcessWord(input.Slice(i, 4));
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (i < length)
+            {
+                xBuf[xBufOff++] = input[i++];
+            }
+
+            byteCount += length;
+        }
+#endif
+
         public void Finish()
         {
             long    bitLength = (byteCount << 3);
@@ -122,6 +166,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 		}
 
 		internal abstract void ProcessWord(byte[] input, int inOff);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal abstract void ProcessWord(ReadOnlySpan<byte> word);
+#endif
         internal abstract void ProcessLength(long bitLength);
         internal abstract void ProcessBlock();
         public abstract string AlgorithmName { get; }
@@ -129,5 +176,8 @@ namespace Org.BouncyCastle.Crypto.Digests
         public abstract int DoFinal(byte[] output, int outOff);
 		public abstract IMemoable Copy();
 		public abstract void Reset(IMemoable t);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract int DoFinal(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/Haraka256Digest.cs b/crypto/src/crypto/digests/Haraka256Digest.cs
index f7af4bf69..27aea9b29 100644
--- a/crypto/src/crypto/digests/Haraka256Digest.cs
+++ b/crypto/src/crypto/digests/Haraka256Digest.cs
@@ -93,7 +93,58 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DIGEST_SIZE;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int haraka256256(byte[] msg, Span<byte> output)
+        {
+            byte[][] s1 = new byte[2][];
+            s1[0] = new byte[16];
+            s1[1] = new byte[16];
+            byte[][] s2 = new byte[2][];
+            s2[0] = new byte[16];
+            s2[1] = new byte[16];
+
+            Array.Copy(msg, 0, s1[0], 0, 16);
+            Array.Copy(msg, 16, s1[1], 0, 16);
+
+            s1[0] = aesEnc(s1[0], RC[0]);
+            s1[1] = aesEnc(s1[1], RC[1]);
+            s1[0] = aesEnc(s1[0], RC[2]);
+            s1[1] = aesEnc(s1[1], RC[3]);
+            mix256(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[4]);
+            s1[1] = aesEnc(s2[1], RC[5]);
+            s1[0] = aesEnc(s1[0], RC[6]);
+            s1[1] = aesEnc(s1[1], RC[7]);
+            mix256(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[8]);
+            s1[1] = aesEnc(s2[1], RC[9]);
+            s1[0] = aesEnc(s1[0], RC[10]);
+            s1[1] = aesEnc(s1[1], RC[11]);
+            mix256(s1, s2);
 
+            s1[0] = aesEnc(s2[0], RC[12]);
+            s1[1] = aesEnc(s2[1], RC[13]);
+            s1[0] = aesEnc(s1[0], RC[14]);
+            s1[1] = aesEnc(s1[1], RC[15]);
+            mix256(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[16]);
+            s1[1] = aesEnc(s2[1], RC[17]);
+            s1[0] = aesEnc(s1[0], RC[18]);
+            s1[1] = aesEnc(s1[1], RC[19]);
+            mix256(s1, s2);
+
+            s1[0] = Xor(s2[0], msg, 0);
+            s1[1] = Xor(s2[1], msg, 16);
+
+            s1[0].AsSpan(0, 16).CopyTo(output);
+            s1[1].AsSpan(0, 16).CopyTo(output[16..]);
+
+            return DIGEST_SIZE;
+        }
+#endif
 
         public Haraka256Digest()
         {
@@ -106,10 +157,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             this.off = digest.off;
         }
 
-        public string getAlgorithmName()
-        {
-            return "Haraka-256";
-        }
+        public override string AlgorithmName => "Haraka-256";
 
         public override void Update(byte input)
         {
@@ -132,6 +180,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             off += len;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (off + input.Length > 32)
+            {
+                throw new ArgumentException("total input cannot be more than 32 bytes");
+            }
+
+            input.CopyTo(buffer.AsSpan(off));
+            off += input.Length;
+        }
+#endif
+
         public override int DoFinal(byte[] output, int outOff)
         {
             if (off != 32)
@@ -151,6 +212,27 @@ namespace Org.BouncyCastle.Crypto.Digests
             return rv;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (off != 32)
+            {
+                throw new ArgumentException("input must be exactly 32 bytes");
+            }
+
+            if (output.Length < 32)
+            {
+                throw new ArgumentException("output too short to receive digest");
+            }
+
+            int rv = haraka256256(buffer, output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
         public override void Reset()
         {
             off = 0;
diff --git a/crypto/src/crypto/digests/Haraka512Digest.cs b/crypto/src/crypto/digests/Haraka512Digest.cs
index 36bdcbabe..0faeae710 100644
--- a/crypto/src/crypto/digests/Haraka512Digest.cs
+++ b/crypto/src/crypto/digests/Haraka512Digest.cs
@@ -1,5 +1,7 @@
 using System;
 
+using Org.BouncyCastle.Utilities;
+
 namespace Org.BouncyCastle.Crypto.Digests
 {
     public class Haraka512Digest : HarakaBase
@@ -167,10 +169,92 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DIGEST_SIZE;
         }
 
-        public string GetAlgorithmName()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private int Haraka512256(byte[] msg, Span<byte> output)
         {
-            return "Haraka-512";
+            byte[][] s1 = new byte[4][];
+            s1[0] = new byte[16];
+            s1[1] = new byte[16];
+            s1[2] = new byte[16];
+            s1[3] = new byte[16];
+            byte[][] s2 = new byte[4][];
+            s2[0] = new byte[16];
+            s2[1] = new byte[16];
+            s2[2] = new byte[16];
+            s2[3] = new byte[16];
+
+            //-- Unrolled version of above.
+
+            Array.Copy(msg, 0, s1[0], 0, 16);
+            Array.Copy(msg, 16, s1[1], 0, 16);
+            Array.Copy(msg, 32, s1[2], 0, 16);
+            Array.Copy(msg, 48, s1[3], 0, 16);
+
+            s1[0] = aesEnc(s1[0], RC[0]);
+            s1[1] = aesEnc(s1[1], RC[1]);
+            s1[2] = aesEnc(s1[2], RC[2]);
+            s1[3] = aesEnc(s1[3], RC[3]);
+            s1[0] = aesEnc(s1[0], RC[4]);
+            s1[1] = aesEnc(s1[1], RC[5]);
+            s1[2] = aesEnc(s1[2], RC[6]);
+            s1[3] = aesEnc(s1[3], RC[7]);
+            Mix512(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[8]);
+            s1[1] = aesEnc(s2[1], RC[9]);
+            s1[2] = aesEnc(s2[2], RC[10]);
+            s1[3] = aesEnc(s2[3], RC[11]);
+            s1[0] = aesEnc(s1[0], RC[12]);
+            s1[1] = aesEnc(s1[1], RC[13]);
+            s1[2] = aesEnc(s1[2], RC[14]);
+            s1[3] = aesEnc(s1[3], RC[15]);
+            Mix512(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[16]);
+            s1[1] = aesEnc(s2[1], RC[17]);
+            s1[2] = aesEnc(s2[2], RC[18]);
+            s1[3] = aesEnc(s2[3], RC[19]);
+            s1[0] = aesEnc(s1[0], RC[20]);
+            s1[1] = aesEnc(s1[1], RC[21]);
+            s1[2] = aesEnc(s1[2], RC[22]);
+            s1[3] = aesEnc(s1[3], RC[23]);
+            Mix512(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[24]);
+            s1[1] = aesEnc(s2[1], RC[25]);
+            s1[2] = aesEnc(s2[2], RC[26]);
+            s1[3] = aesEnc(s2[3], RC[27]);
+            s1[0] = aesEnc(s1[0], RC[28]);
+            s1[1] = aesEnc(s1[1], RC[29]);
+            s1[2] = aesEnc(s1[2], RC[30]);
+            s1[3] = aesEnc(s1[3], RC[31]);
+            Mix512(s1, s2);
+
+            s1[0] = aesEnc(s2[0], RC[32]);
+            s1[1] = aesEnc(s2[1], RC[33]);
+            s1[2] = aesEnc(s2[2], RC[34]);
+            s1[3] = aesEnc(s2[3], RC[35]);
+            s1[0] = aesEnc(s1[0], RC[36]);
+            s1[1] = aesEnc(s1[1], RC[37]);
+            s1[2] = aesEnc(s1[2], RC[38]);
+            s1[3] = aesEnc(s1[3], RC[39]);
+            Mix512(s1, s2);
+
+            s1[0] = Xor(s2[0], msg, 0);
+            s1[1] = Xor(s2[1], msg, 16);
+            s1[2] = Xor(s2[2], msg, 32);
+            s1[3] = Xor(s2[3], msg, 48);
+
+            s1[0].AsSpan(8, 8).CopyTo(output);
+            s1[1].AsSpan(8, 8).CopyTo(output[8..]);
+            s1[2].AsSpan(0, 8).CopyTo(output[16..]);
+            s1[3].AsSpan(0, 8).CopyTo(output[24..]);
+
+            return DIGEST_SIZE;
         }
+#endif
+
+        public override string AlgorithmName => "Haraka-512";
 
         public override void Update(byte input)
         {
@@ -193,6 +277,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             off += len;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (off + input.Length > 64)
+            {
+                throw new ArgumentException("total input cannot be more than 64 bytes");
+            }
+
+            input.CopyTo(buffer.AsSpan(off));
+            off += input.Length;
+        }
+#endif
+
         public override int DoFinal(byte[] output, int outOff)
         {
             if (off != 64)
@@ -212,6 +309,27 @@ namespace Org.BouncyCastle.Crypto.Digests
             return rv;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            if (off != 64)
+            {
+                throw new ArgumentException("input must be exactly 64 bytes");
+            }
+
+            if (output.Length < 32)
+            {
+                throw new ArgumentException("output too short to receive digest");
+            }
+
+            int rv = Haraka512256(buffer, output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
         public override void Reset()
         {
             off = 0;
diff --git a/crypto/src/crypto/digests/HarakaBase.cs b/crypto/src/crypto/digests/HarakaBase.cs
index 6157198f1..1270de35c 100644
--- a/crypto/src/crypto/digests/HarakaBase.cs
+++ b/crypto/src/crypto/digests/HarakaBase.cs
@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Text;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
@@ -26,10 +24,7 @@ namespace Org.BouncyCastle.Crypto.Digests
         {(byte)0xe1, (byte)0xf8, (byte)0x98, (byte)0x11, (byte)0x69, (byte)0xd9, (byte)0x8e, (byte)0x94, (byte)0x9b, (byte)0x1e, (byte)0x87, (byte)0xe9, (byte)0xce, (byte)0x55, (byte)0x28, (byte)0xdf},
         {(byte)0x8c, (byte)0xa1, (byte)0x89, (byte)0x0d, (byte)0xbf, (byte)0xe6, (byte)0x42, (byte)0x68, (byte)0x41, (byte)0x99, (byte)0x2d, (byte)0x0f, (byte)0xb0, (byte)0x54, (byte)0xbb, (byte)0x16}};
 
-        public string AlgorithmName
-        {
-            get { return "Haraka Base"; }
-        }
+        public abstract string AlgorithmName { get; }
 
         static byte sBox(byte x)
         {
@@ -144,5 +139,10 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         public abstract void Reset();
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract void BlockUpdate(ReadOnlySpan<byte> input);
+
+        public abstract int DoFinal(Span<byte> output);
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/KeccakDigest.cs b/crypto/src/crypto/digests/KeccakDigest.cs
index 2da2e099e..b8305cc13 100644
--- a/crypto/src/crypto/digests/KeccakDigest.cs
+++ b/crypto/src/crypto/digests/KeccakDigest.cs
@@ -76,6 +76,13 @@ namespace Org.BouncyCastle.Crypto.Digests
             Absorb(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            Absorb(input);
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
             Squeeze(output, outOff, fixedOutputLength);
@@ -85,6 +92,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             return GetDigestSize();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            int digestSize = GetDigestSize();
+            Squeeze(output[..digestSize]);
+
+            Reset();
+
+            return digestSize;
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
@@ -199,6 +218,46 @@ namespace Org.BouncyCastle.Crypto.Digests
             this.bitsInQueue = remaining << 3;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected void Absorb(ReadOnlySpan<byte> data)
+        {
+            if ((bitsInQueue & 7) != 0)
+                throw new InvalidOperationException("attempt to absorb with odd length queue");
+            if (squeezing)
+                throw new InvalidOperationException("attempt to absorb while squeezing");
+
+            int bytesInQueue = bitsInQueue >> 3;
+            int rateBytes = rate >> 3;
+
+            int len = data.Length;
+            int available = rateBytes - bytesInQueue;
+            if (len < available)
+            {
+                data.CopyTo(dataQueue.AsSpan(bytesInQueue));
+                this.bitsInQueue += len << 3;
+                return;
+            }
+
+            int count = 0;
+            if (bytesInQueue > 0)
+            {
+                data[..available].CopyTo(dataQueue.AsSpan(bytesInQueue));
+                count += available;
+                KeccakAbsorb(dataQueue, 0);
+            }
+
+            int remaining;
+            while ((remaining = len - count) >= rateBytes)
+            {
+                KeccakAbsorb(data[count..]);
+                count += rateBytes;
+            }
+
+            data[count..].CopyTo(dataQueue.AsSpan());
+            this.bitsInQueue = remaining << 3;
+        }
+#endif
+
         protected void AbsorbBits(int data, int bits)
         {
             if (bits < 1 || bits > 7)
@@ -270,6 +329,30 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        protected void Squeeze(Span<byte> output)
+        {
+            if (!squeezing)
+            {
+                PadAndSwitchToSqueezingPhase();
+            }
+            long outputLength = (long)output.Length << 3;
+
+            long i = 0;
+            while (i < outputLength)
+            {
+                if (bitsInQueue == 0)
+                {
+                    KeccakExtract();
+                }
+                int partialBlock = (int)System.Math.Min(bitsInQueue, outputLength - i);
+                dataQueue.AsSpan((rate - bitsInQueue) >> 3, partialBlock >> 3).CopyTo(output[(int)(i >> 3)..]);
+                bitsInQueue -= partialBlock;
+                i += partialBlock;
+            }
+        }
+#endif
+
         private void KeccakAbsorb(byte[] data, int off)
         {
             int count = rate >> 6;
@@ -282,6 +365,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             KeccakPermutation();
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void KeccakAbsorb(ReadOnlySpan<byte> data)
+        {
+            int count = rate >> 6, off = 0;
+            for (int i = 0; i < count; ++i)
+            {
+                state[i] ^= Pack.LE_To_UInt64(data[off..]);
+                off += 8;
+            }
+
+            KeccakPermutation();
+        }
+#endif
+
         private void KeccakExtract()
         {
             KeccakPermutation();
diff --git a/crypto/src/crypto/digests/LongDigest.cs b/crypto/src/crypto/digests/LongDigest.cs
index aaa0b43ce..6a2f94ece 100644
--- a/crypto/src/crypto/digests/LongDigest.cs
+++ b/crypto/src/crypto/digests/LongDigest.cs
@@ -10,46 +10,46 @@ namespace Org.BouncyCastle.Crypto.Digests
     * Base class for SHA-384 and SHA-512.
     */
     public abstract class LongDigest
-		: IDigest, IMemoable
+        : IDigest, IMemoable
     {
-        private int     MyByteLength = 128;
+        private int MyByteLength = 128;
 
-        private byte[]  xBuf;
-        private int     xBufOff;
+        private byte[] xBuf;
+        private int xBufOff;
 
-        private long	byteCount1;
-        private long	byteCount2;
+        private long byteCount1;
+        private long byteCount2;
 
         internal ulong H1, H2, H3, H4, H5, H6, H7, H8;
 
         private ulong[] W = new ulong[80];
         private int wOff;
 
-		/**
+        /**
         * Constructor for variable length word
         */
         internal LongDigest()
         {
             xBuf = new byte[8];
 
-			Reset();
+            Reset();
         }
 
-		/**
+        /**
         * Copy constructor.  We are using copy constructors in place
         * of the object.Clone() interface as this interface is not
         * supported by J2ME.
         */
         internal LongDigest(
-			LongDigest t)
-		{
-			xBuf = new byte[t.xBuf.Length];
+            LongDigest t)
+        {
+            xBuf = new byte[t.xBuf.Length];
 
-			CopyIn(t);
-		}
+            CopyIn(t);
+        }
 
-		protected void CopyIn(LongDigest t)
-		{
+        protected void CopyIn(LongDigest t)
+        {
             Array.Copy(t.xBuf, 0, xBuf, 0, t.xBuf.Length);
 
             xBufOff = t.xBufOff;
@@ -84,9 +84,9 @@ namespace Org.BouncyCastle.Crypto.Digests
         }
 
         public void BlockUpdate(
-            byte[]  input,
-            int     inOff,
-            int     length)
+            byte[] input,
+            int inOff,
+            int length)
         {
             //
             // fill the current word
@@ -123,12 +123,54 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int inOff = 0;
+            int length = input.Length;
+
+            //
+            // fill the current word
+            //
+            while ((xBufOff != 0) && (length > 0))
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+
+            //
+            // process whole words.
+            //
+            while (length >= xBuf.Length)
+            {
+                ProcessWord(input.Slice(inOff, xBuf.Length));
+
+                inOff += xBuf.Length;
+                length -= xBuf.Length;
+                byteCount1 += xBuf.Length;
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (length > 0)
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+        }
+#endif
+
         public void Finish()
         {
             AdjustByteCounts();
 
-            long    lowBitLength = byteCount1 << 3;
-            long    hiBitLength = byteCount2;
+            long lowBitLength = byteCount1 << 3;
+            long hiBitLength = byteCount2;
 
             //
             // add the pad bytes.
@@ -151,20 +193,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             byteCount2 = 0;
 
             xBufOff = 0;
-            for ( int i = 0; i < xBuf.Length; i++ )
+            for (int i = 0; i < xBuf.Length; i++)
             {
                 xBuf[i] = 0;
             }
 
             wOff = 0;
-			Array.Clear(W, 0, W.Length);
+            Array.Clear(W, 0, W.Length);
         }
 
         internal void ProcessWord(
-            byte[]  input,
-            int     inOff)
+            byte[] input,
+            int inOff)
         {
-			W[wOff] = Pack.BE_To_UInt64(input, inOff);
+            W[wOff] = Pack.BE_To_UInt64(input, inOff);
 
             if (++wOff == 16)
             {
@@ -172,6 +214,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            W[wOff] = Pack.BE_To_UInt64(word);
+
+            if (++wOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         /**
         * adjust the byte counts so that byteCount2 represents the
         * upper long (less 3 bits) word of the byte count.
@@ -180,14 +234,14 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             if (byteCount1 > 0x1fffffffffffffffL)
             {
-                byteCount2 += (long) ((ulong) byteCount1 >> 61);
+                byteCount2 += (long)((ulong)byteCount1 >> 61);
                 byteCount1 &= 0x1fffffffffffffffL;
             }
         }
 
         internal void ProcessLength(
-            long	lowW,
-            long	hiW)
+            long lowW,
+            long hiW)
         {
             if (wOff > 14)
             {
@@ -222,51 +276,51 @@ namespace Org.BouncyCastle.Crypto.Digests
             ulong g = H7;
             ulong h = H8;
 
-			int t = 0;
-			for(int i = 0; i < 10; i ++)
-			{
-				// t = 8 * i
-				h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
-				d += h;
-				h += Sum0(a) + Maj(a, b, c);
-
-				// t = 8 * i + 1
-				g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
-				c += g;
-				g += Sum0(h) + Maj(h, a, b);
-
-				// t = 8 * i + 2
-				f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
-				b += f;
-				f += Sum0(g) + Maj(g, h, a);
-
-				// t = 8 * i + 3
-				e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
-				a += e;
-				e += Sum0(f) + Maj(f, g, h);
-
-				// t = 8 * i + 4
-				d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
-				h += d;
-				d += Sum0(e) + Maj(e, f, g);
-
-				// t = 8 * i + 5
-				c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
-				g += c;
-				c += Sum0(d) + Maj(d, e, f);
-
-				// t = 8 * i + 6
-				b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
-				f += b;
-				b += Sum0(c) + Maj(c, d, e);
-
-				// t = 8 * i + 7
-				a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
-				e += a;
-				a += Sum0(b) + Maj(b, c, d);
-			}
-
-			H1 += a;
+            int t = 0;
+            for (int i = 0; i < 10; i++)
+            {
+                // t = 8 * i
+                h += Sum1(e) + Ch(e, f, g) + K[t] + W[t++];
+                d += h;
+                h += Sum0(a) + Maj(a, b, c);
+
+                // t = 8 * i + 1
+                g += Sum1(d) + Ch(d, e, f) + K[t] + W[t++];
+                c += g;
+                g += Sum0(h) + Maj(h, a, b);
+
+                // t = 8 * i + 2
+                f += Sum1(c) + Ch(c, d, e) + K[t] + W[t++];
+                b += f;
+                f += Sum0(g) + Maj(g, h, a);
+
+                // t = 8 * i + 3
+                e += Sum1(b) + Ch(b, c, d) + K[t] + W[t++];
+                a += e;
+                e += Sum0(f) + Maj(f, g, h);
+
+                // t = 8 * i + 4
+                d += Sum1(a) + Ch(a, b, c) + K[t] + W[t++];
+                h += d;
+                d += Sum0(e) + Maj(e, f, g);
+
+                // t = 8 * i + 5
+                c += Sum1(h) + Ch(h, a, b) + K[t] + W[t++];
+                g += c;
+                c += Sum0(d) + Maj(d, e, f);
+
+                // t = 8 * i + 6
+                b += Sum1(g) + Ch(g, h, a) + K[t] + W[t++];
+                f += b;
+                b += Sum0(c) + Maj(c, d, e);
+
+                // t = 8 * i + 7
+                a += Sum1(f) + Ch(f, g, h) + K[t] + W[t++];
+                e += a;
+                a += Sum0(b) + Maj(b, c, d);
+            }
+
+            H1 += a;
             H2 += b;
             H3 += c;
             H4 += d;
@@ -275,14 +329,14 @@ namespace Org.BouncyCastle.Crypto.Digests
             H7 += g;
             H8 += h;
 
-			//
+            //
             // reset the offset and clean out the word buffer.
             //
             wOff = 0;
-			Array.Clear(W, 0, 16);
-		}
+            Array.Clear(W, 0, 16);
+        }
 
-		/* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
+        /* SHA-384 and SHA-512 functions (as for SHA-256 but for longs) */
         private static ulong Ch(ulong x, ulong y, ulong z)
         {
             return (x & y) ^ (~x & z);
@@ -295,61 +349,64 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         private static ulong Sum0(ulong x)
         {
-	        return ((x << 36) | (x >> 28)) ^ ((x << 30) | (x >> 34)) ^ ((x << 25) | (x >> 39));
+            return ((x << 36) | (x >> 28)) ^ ((x << 30) | (x >> 34)) ^ ((x << 25) | (x >> 39));
         }
 
-		private static ulong Sum1(ulong x)
+        private static ulong Sum1(ulong x)
         {
-	        return ((x << 50) | (x >> 14)) ^ ((x << 46) | (x >> 18)) ^ ((x << 23) | (x >> 41));
+            return ((x << 50) | (x >> 14)) ^ ((x << 46) | (x >> 18)) ^ ((x << 23) | (x >> 41));
         }
 
         private static ulong Sigma0(ulong x)
         {
-	        return ((x << 63) | (x >> 1)) ^ ((x << 56) | (x >> 8)) ^ (x >> 7);
+            return ((x << 63) | (x >> 1)) ^ ((x << 56) | (x >> 8)) ^ (x >> 7);
         }
 
         private static ulong Sigma1(ulong x)
         {
-	        return ((x << 45) | (x >> 19)) ^ ((x << 3) | (x >> 61)) ^ (x >> 6);
+            return ((x << 45) | (x >> 19)) ^ ((x << 3) | (x >> 61)) ^ (x >> 6);
         }
 
         /* SHA-384 and SHA-512 Constants
          * (represent the first 64 bits of the fractional parts of the
          * cube roots of the first sixty-four prime numbers)
          */
-		internal static readonly ulong[] K =
-		{
-			0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
-			0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
-			0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
-			0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
-			0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
-			0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
-			0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
-			0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
-			0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
-			0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
-			0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
-			0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
-			0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
-			0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
-			0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
-			0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
-			0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
-			0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
-			0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
-			0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
-		};
-
-		public int GetByteLength()
-		{
-			return MyByteLength;
-		}
-
-		public abstract string AlgorithmName { get; }
-		public abstract int GetDigestSize();
+        internal static readonly ulong[] K =
+        {
+            0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
+            0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
+            0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
+            0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
+            0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
+            0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
+            0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
+            0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
+            0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
+            0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
+            0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
+            0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
+            0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
+            0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
+            0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
+            0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
+            0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
+            0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
+            0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
+            0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817
+        };
+
+        public int GetByteLength()
+        {
+            return MyByteLength;
+        }
+
+        public abstract string AlgorithmName { get; }
+        public abstract int GetDigestSize();
         public abstract int DoFinal(byte[] output, int outOff);
-		public abstract IMemoable Copy();
-		public abstract void Reset(IMemoable t);
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public abstract int DoFinal(Span<byte> output);
+#endif
+        public abstract IMemoable Copy();
+        public abstract void Reset(IMemoable t);
     }
 }
diff --git a/crypto/src/crypto/digests/MD2Digest.cs b/crypto/src/crypto/digests/MD2Digest.cs
index f72d08768..cea89a311 100644
--- a/crypto/src/crypto/digests/MD2Digest.cs
+++ b/crypto/src/crypto/digests/MD2Digest.cs
@@ -1,6 +1,5 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
@@ -99,6 +98,30 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            // add padding
+            byte paddingByte = (byte)(M.Length - mOff);
+            for (int i = mOff; i < M.Length; i++)
+            {
+                M[i] = paddingByte;
+            }
+            //do final check sum
+            ProcessChecksum(M);
+            // do final block process
+            ProcessBlock(M);
+
+            ProcessBlock(C);
+
+            X.AsSpan(xOff, 16).CopyTo(output);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the digest back to it's initial state.
         */
@@ -179,6 +202,40 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            //
+            // fill the current word
+            //
+            while ((mOff != 0) && (input.Length > 0))
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+
+            //
+            // process whole words.
+            //
+            while (input.Length >= 16)
+            {
+                input[..16].CopyTo(M);
+                ProcessChecksum(M);
+                ProcessBlock(M);
+                input = input[16..];
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (input.Length > 0)
+            {
+                Update(input[0]);
+                input = input[1..];
+            }
+        }
+#endif
+
         internal void ProcessChecksum(byte[] m)
         {
             int L = C[15];
diff --git a/crypto/src/crypto/digests/MD4Digest.cs b/crypto/src/crypto/digests/MD4Digest.cs
index 8743f7dad..2eb2c8400 100644
--- a/crypto/src/crypto/digests/MD4Digest.cs
+++ b/crypto/src/crypto/digests/MD4Digest.cs
@@ -1,5 +1,6 @@
 using System;
 
+using Org.BouncyCastle.Crypto.Utilities;
 using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
@@ -60,12 +61,9 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
         {
-            X[xOff++] = (input[inOff] & 0xff) | ((input[inOff + 1] & 0xff) << 8)
-                | ((input[inOff + 2] & 0xff) << 16) | ((input[inOff + 3] & 0xff) << 24);
+            X[xOff++] = (int)Pack.LE_To_UInt32(input, inOff);
 
             if (xOff == 16)
             {
@@ -73,6 +71,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long    bitLength)
         {
@@ -85,32 +95,35 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (int)((ulong) bitLength >> 32);
         }
 
-        private void UnpackWord(
-            int     word,
-            byte[]  outBytes,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
-            outBytes[outOff]     = (byte)word;
-            outBytes[outOff + 1] = (byte)((uint) word >> 8);
-            outBytes[outOff + 2] = (byte)((uint) word >> 16);
-            outBytes[outOff + 3] = (byte)((uint) word >> 24);
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H1, output, outOff);
+            Pack.UInt32_To_LE((uint)H2, output, outOff + 4);
+            Pack.UInt32_To_LE((uint)H3, output, outOff + 8);
+            Pack.UInt32_To_LE((uint)H4, output, outOff + 12);
+
+            Reset();
+
+            return DigestLength;
         }
 
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
         {
             Finish();
 
-            UnpackWord(H1, output, outOff);
-            UnpackWord(H2, output, outOff + 4);
-            UnpackWord(H3, output, outOff + 8);
-            UnpackWord(H4, output, outOff + 12);
+            Pack.UInt32_To_LE((uint)H1, output);
+            Pack.UInt32_To_LE((uint)H2, output[4..]);
+            Pack.UInt32_To_LE((uint)H3, output[8..]);
+            Pack.UInt32_To_LE((uint)H4, output[12..]);
 
             Reset();
 
             return DigestLength;
         }
+#endif
 
         /**
         * reset the chaining variables to the IV values.
diff --git a/crypto/src/crypto/digests/MD5Digest.cs b/crypto/src/crypto/digests/MD5Digest.cs
index c60ac92a3..062d7bb46 100644
--- a/crypto/src/crypto/digests/MD5Digest.cs
+++ b/crypto/src/crypto/digests/MD5Digest.cs
@@ -55,9 +55,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
-        internal override void ProcessWord(
-            byte[] input,
-            int inOff)
+        internal override void ProcessWord(byte[] input, int inOff)
         {
             X[xOff] = Pack.LE_To_UInt32(input, inOff);
 
@@ -67,6 +65,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.LE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -103,6 +113,22 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE(H1, output);
+            Pack.UInt32_To_LE(H2, output[4..]);
+            Pack.UInt32_To_LE(H3, output[8..]);
+            Pack.UInt32_To_LE(H4, output[12..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/NonMemoableDigest.cs b/crypto/src/crypto/digests/NonMemoableDigest.cs
index 02c49b887..bad38911b 100644
--- a/crypto/src/crypto/digests/NonMemoableDigest.cs
+++ b/crypto/src/crypto/digests/NonMemoableDigest.cs
@@ -44,11 +44,25 @@ namespace Org.BouncyCastle.Crypto.Digests
             mBaseDigest.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            mBaseDigest.BlockUpdate(input);
+        }
+#endif
+
         public virtual int DoFinal(byte[] output, int outOff)
         {
             return mBaseDigest.DoFinal(output, outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            return mBaseDigest.DoFinal(output);
+        }
+#endif
+
         public virtual void Reset()
         {
             mBaseDigest.Reset();
diff --git a/crypto/src/crypto/digests/NullDigest.cs b/crypto/src/crypto/digests/NullDigest.cs
index d14dd5c9f..28554cf3e 100644
--- a/crypto/src/crypto/digests/NullDigest.cs
+++ b/crypto/src/crypto/digests/NullDigest.cs
@@ -35,17 +35,48 @@ namespace Org.BouncyCastle.Crypto.Digests
 			bOut.Write(inBytes, inOff, len);
 		}
 
-        public int DoFinal(byte[] outBytes, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
 		{
+			bOut.Write(input);
+		}
+#endif
+
+		public int DoFinal(byte[] outBytes, int outOff)
+		{
+            try
+            {
+                byte[] data = bOut.GetBuffer();
+				int length = Convert.ToInt32(bOut.Length);
+
+				Array.Copy(data, 0, outBytes, outOff, length);
+
+				return length;
+			}
+			finally
+            {
+                Reset();
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
             try
             {
-                return Streams.WriteBufTo(bOut, outBytes, outOff);
+				byte[] data = bOut.GetBuffer();
+				int length = Convert.ToInt32(bOut.Length);
+
+				data.AsSpan(0, length).CopyTo(output);
+
+				return length;
             }
             finally
             {
                 Reset();
             }
         }
+#endif
 
         public void Reset()
 		{
diff --git a/crypto/src/crypto/digests/ParallelHash.cs b/crypto/src/crypto/digests/ParallelHash.cs
index f28795f5a..8054b2005 100644
--- a/crypto/src/crypto/digests/ParallelHash.cs
+++ b/crypto/src/crypto/digests/ParallelHash.cs
@@ -85,7 +85,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             buffer[bufOff++] = b;
             if (bufOff == buffer.Length)
             {
-                compress();
+                Compress();
             }
         }
 
@@ -106,7 +106,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 
                 if (bufOff == buffer.Length)
                 {
-                    compress();
+                    Compress();
                 }
             }
 
@@ -114,7 +114,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             {
                 while (len - i >= B)
                 {
-                    compress(inBuf, inOff + i, B);
+                    Compress(inBuf, inOff + i, B);
                     i += B;
                 }
             }
@@ -125,13 +125,49 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-        private void compress()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
         {
-            compress(buffer, 0, bufOff);
+            //
+            // fill the current word
+            //
+            int i = 0;
+            if (bufOff != 0)
+            {
+                while (i < input.Length && bufOff != buffer.Length)
+                {
+                    buffer[bufOff++] = input[i++];
+                }
+
+                if (bufOff == buffer.Length)
+                {
+                    Compress();
+                }
+            }
+
+            if (i < input.Length)
+            {
+                while (input.Length - i >= B)
+                {
+                    Compress(input, i, B);
+                    i += B;
+                }
+            }
+
+            while (i < input.Length)
+            {
+                Update(input[i++]);
+            }
+        }
+#endif
+
+        private void Compress()
+        {
+            Compress(buffer, 0, bufOff);
             bufOff = 0;
         }
 
-        private void compress(byte[] buf, int offSet, int len)
+        private void Compress(byte[] buf, int offSet, int len)
         {
             compressor.BlockUpdate(buf, offSet, len);
             compressor.DoFinal(compressorBuffer, 0, compressorBuffer.Length);
@@ -141,11 +177,23 @@ namespace Org.BouncyCastle.Crypto.Digests
             nCount++;
         }
 
-        private void wrapUp(int outputSize)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Compress(ReadOnlySpan<byte> input, int pos, int len)
+        {
+            compressor.BlockUpdate(input.Slice(pos, len));
+            compressor.DoFinal(compressorBuffer, 0, compressorBuffer.Length);
+
+            cshake.BlockUpdate(compressorBuffer, 0, compressorBuffer.Length);
+
+            nCount++;
+        }
+#endif
+
+        private void WrapUp(int outputSize)
         {
             if (bufOff != 0)
             {
-                compress();
+                Compress();
             }
             byte[] nOut = XofUtilities.RightEncode(nCount);
             byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
@@ -160,21 +208,37 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             if (firstOutput)
             {
-                wrapUp(outputLength);
+                WrapUp(outputLength);
+            }
+
+            int rv = cshake.DoFinal(outBuf, outOff);
+
+            Reset();
+
+            return rv;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int DoFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(outputLength);
             }
 
-            int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
+            int rv = cshake.DoFinal(output);
 
             Reset();
 
             return rv;
         }
+#endif
 
         public virtual int DoFinal(byte[] outBuf, int outOff, int outLen)
         {
             if (firstOutput)
             {
-                wrapUp(outputLength);
+                WrapUp(outputLength);
             }
 
             int rv = cshake.DoFinal(outBuf, outOff, outLen);
@@ -184,16 +248,44 @@ namespace Org.BouncyCastle.Crypto.Digests
             return rv;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int OutputFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(outputLength);
+            }
+
+            int rv = cshake.OutputFinal(output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
         public virtual int DoOutput(byte[] outBuf, int outOff, int outLen)
         {
             if (firstOutput)
             {
-                wrapUp(0);
+                WrapUp(0);
             }
 
             return cshake.DoOutput(outBuf, outOff, outLen);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual int Output(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(0);
+            }
+
+            return cshake.Output(output);
+        }
+#endif
+
         public virtual void Reset()
         {
             cshake.Reset();
diff --git a/crypto/src/crypto/digests/RipeMD128Digest.cs b/crypto/src/crypto/digests/RipeMD128Digest.cs
index cba2c65d3..b66452682 100644
--- a/crypto/src/crypto/digests/RipeMD128Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD128Digest.cs
@@ -68,6 +68,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -94,6 +106,22 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/RipeMD160Digest.cs b/crypto/src/crypto/digests/RipeMD160Digest.cs
index 0fc2a4a1c..a95bff48a 100644
--- a/crypto/src/crypto/digests/RipeMD160Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD160Digest.cs
@@ -70,6 +70,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -97,6 +109,23 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables to the IV values.
         */
diff --git a/crypto/src/crypto/digests/RipeMD256Digest.cs b/crypto/src/crypto/digests/RipeMD256Digest.cs
index 621162a6f..40508e9f7 100644
--- a/crypto/src/crypto/digests/RipeMD256Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD256Digest.cs
@@ -70,6 +70,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -100,6 +112,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+            Pack.UInt32_To_LE((uint)H5, output[20..]);
+            Pack.UInt32_To_LE((uint)H6, output[24..]);
+            Pack.UInt32_To_LE((uint)H7, output[28..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /// <summary> reset the chaining variables to the IV values.</summary>
         public override void  Reset()
         {
diff --git a/crypto/src/crypto/digests/RipeMD320Digest.cs b/crypto/src/crypto/digests/RipeMD320Digest.cs
index c46bc4fea..ddaf858ff 100644
--- a/crypto/src/crypto/digests/RipeMD320Digest.cs
+++ b/crypto/src/crypto/digests/RipeMD320Digest.cs
@@ -73,6 +73,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff++] = (int)Pack.LE_To_UInt32(word);
+
+            if (xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(
             long bitLength)
         {
@@ -105,6 +117,28 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_LE((uint)H0, output);
+            Pack.UInt32_To_LE((uint)H1, output[4..]);
+            Pack.UInt32_To_LE((uint)H2, output[8..]);
+            Pack.UInt32_To_LE((uint)H3, output[12..]);
+            Pack.UInt32_To_LE((uint)H4, output[16..]);
+            Pack.UInt32_To_LE((uint)H5, output[20..]);
+            Pack.UInt32_To_LE((uint)H6, output[24..]);
+            Pack.UInt32_To_LE((uint)H7, output[28..]);
+            Pack.UInt32_To_LE((uint)H8, output[32..]);
+            Pack.UInt32_To_LE((uint)H9, output[36..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /// <summary> reset the chaining variables to the IV values.</summary>
         public override void  Reset()
         {
diff --git a/crypto/src/crypto/digests/SHA3Digest.cs b/crypto/src/crypto/digests/SHA3Digest.cs
index 3dc63a6ed..778c453d8 100644
--- a/crypto/src/crypto/digests/SHA3Digest.cs
+++ b/crypto/src/crypto/digests/SHA3Digest.cs
@@ -55,6 +55,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             return base.DoFinal(output,  outOff);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            AbsorbBits(0x02, 2);
+
+            return base.DoFinal(output);
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
diff --git a/crypto/src/crypto/digests/SM3Digest.cs b/crypto/src/crypto/digests/SM3Digest.cs
index 449d7c161..81d4a68a9 100644
--- a/crypto/src/crypto/digests/SM3Digest.cs
+++ b/crypto/src/crypto/digests/SM3Digest.cs
@@ -118,7 +118,6 @@ namespace Org.BouncyCastle.Crypto.Digests
 			this.xOff = 0;
 		}
 
-
 		public override int DoFinal(byte[] output, int outOff)
 		{
 			Finish();
@@ -130,13 +129,22 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DIGEST_LENGTH;
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(V, output);
+
+            Reset();
 
-		internal override void ProcessWord(byte[] input,
-		                                   int inOff)
+            return DIGEST_LENGTH;
+        }
+#endif
+
+        internal override void ProcessWord(byte[] input, int inOff)
 		{
-			uint n = Pack.BE_To_UInt32(input, inOff);
-			this.inwords[this.xOff] = n;
-			++this.xOff;
+			inwords[xOff++] = Pack.BE_To_UInt32(input, inOff);
 
 			if (this.xOff >= 16)
 			{
@@ -144,7 +152,19 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
-		internal override void ProcessLength(long bitLength)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            inwords[xOff++] = Pack.BE_To_UInt32(word);
+
+            if (this.xOff >= 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(long bitLength)
 		{
 			if (this.xOff > (BLOCK_SIZE - 2))
 			{
diff --git a/crypto/src/crypto/digests/Sha1Digest.cs b/crypto/src/crypto/digests/Sha1Digest.cs
index 60ec651d5..9b384b8cb 100644
--- a/crypto/src/crypto/digests/Sha1Digest.cs
+++ b/crypto/src/crypto/digests/Sha1Digest.cs
@@ -61,9 +61,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
-        internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+        internal override void ProcessWord(byte[] input, int inOff)
         {
             X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -73,6 +71,18 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
         internal override void ProcessLength(long    bitLength)
         {
             if (xOff > 14)
@@ -101,6 +111,23 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
          * reset the chaining variables
          */
@@ -279,6 +306,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha224Digest.cs b/crypto/src/crypto/digests/Sha224Digest.cs
index b4e853745..28d09adec 100644
--- a/crypto/src/crypto/digests/Sha224Digest.cs
+++ b/crypto/src/crypto/digests/Sha224Digest.cs
@@ -72,9 +72,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
         {
 			X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -84,7 +82,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		internal override void ProcessLength(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(
             long bitLength)
         {
             if (xOff > 14)
@@ -96,9 +106,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (uint)((ulong)bitLength);
         }
 
-        public override int DoFinal(
-            byte[]	output,
-            int		outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             Finish();
 
@@ -115,7 +123,26 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
         }
 
-		/**
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+            Pack.UInt32_To_BE(H6, output[20..]);
+            Pack.UInt32_To_BE(H7, output[24..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
+        /**
          * reset the chaining variables
          */
         public override void Reset()
@@ -284,6 +311,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha256Digest.cs b/crypto/src/crypto/digests/Sha256Digest.cs
index 63d5b8bee..51859697e 100644
--- a/crypto/src/crypto/digests/Sha256Digest.cs
+++ b/crypto/src/crypto/digests/Sha256Digest.cs
@@ -67,9 +67,7 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return DigestLength;
 		}
 
-		internal override void ProcessWord(
-            byte[]  input,
-            int     inOff)
+		internal override void ProcessWord(byte[] input, int inOff)
 		{
 			X[xOff] = Pack.BE_To_UInt32(input, inOff);
 
@@ -79,7 +77,19 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		internal override void ProcessLength(
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal override void ProcessWord(ReadOnlySpan<byte> word)
+        {
+            X[xOff] = Pack.BE_To_UInt32(word);
+
+            if (++xOff == 16)
+            {
+                ProcessBlock();
+            }
+        }
+#endif
+
+        internal override void ProcessLength(
             long bitLength)
         {
             if (xOff > 14)
@@ -91,26 +101,44 @@ namespace Org.BouncyCastle.Crypto.Digests
             X[15] = (uint)((ulong)bitLength);
         }
 
-        public override int DoFinal(
-            byte[]  output,
-            int     outOff)
+        public override int DoFinal(byte[] output, int outOff)
         {
             Finish();
 
-            Pack.UInt32_To_BE((uint)H1, output, outOff);
-            Pack.UInt32_To_BE((uint)H2, output, outOff + 4);
-            Pack.UInt32_To_BE((uint)H3, output, outOff + 8);
-            Pack.UInt32_To_BE((uint)H4, output, outOff + 12);
-            Pack.UInt32_To_BE((uint)H5, output, outOff + 16);
-            Pack.UInt32_To_BE((uint)H6, output, outOff + 20);
-            Pack.UInt32_To_BE((uint)H7, output, outOff + 24);
-            Pack.UInt32_To_BE((uint)H8, output, outOff + 28);
+            Pack.UInt32_To_BE(H1, output, outOff);
+            Pack.UInt32_To_BE(H2, output, outOff + 4);
+            Pack.UInt32_To_BE(H3, output, outOff + 8);
+            Pack.UInt32_To_BE(H4, output, outOff + 12);
+            Pack.UInt32_To_BE(H5, output, outOff + 16);
+            Pack.UInt32_To_BE(H6, output, outOff + 20);
+            Pack.UInt32_To_BE(H7, output, outOff + 24);
+            Pack.UInt32_To_BE(H8, output, outOff + 28);
 
             Reset();
 
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt32_To_BE(H1, output);
+            Pack.UInt32_To_BE(H2, output[4..]);
+            Pack.UInt32_To_BE(H3, output[8..]);
+            Pack.UInt32_To_BE(H4, output[12..]);
+            Pack.UInt32_To_BE(H5, output[16..]);
+            Pack.UInt32_To_BE(H6, output[20..]);
+            Pack.UInt32_To_BE(H7, output[24..]);
+            Pack.UInt32_To_BE(H8, output[28..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -313,6 +341,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha384Digest.cs b/crypto/src/crypto/digests/Sha384Digest.cs
index e6c9a9aa9..e4e65ed85 100644
--- a/crypto/src/crypto/digests/Sha384Digest.cs
+++ b/crypto/src/crypto/digests/Sha384Digest.cs
@@ -64,6 +64,24 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_BE(H1, output);
+            Pack.UInt64_To_BE(H2, output[8..]);
+            Pack.UInt64_To_BE(H3, output[16..]);
+            Pack.UInt64_To_BE(H4, output[24..]);
+            Pack.UInt64_To_BE(H5, output[32..]);
+            Pack.UInt64_To_BE(H6, output[40..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -72,9 +90,9 @@ namespace Org.BouncyCastle.Crypto.Digests
             base.Reset();
 
             /* SHA-384 initial hash value
-                * The first 64 bits of the fractional parts of the square roots
-                * of the 9th through 16th prime numbers
-                */
+             * The first 64 bits of the fractional parts of the square roots
+             * of the 9th through 16th prime numbers
+              */
             H1 = 0xcbbb9d5dc1059ed8;
             H2 = 0x629a292a367cd507;
             H3 = 0x9159015a3070dd17;
@@ -96,6 +114,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha512Digest.cs b/crypto/src/crypto/digests/Sha512Digest.cs
index 2a0964fd3..9156c24bf 100644
--- a/crypto/src/crypto/digests/Sha512Digest.cs
+++ b/crypto/src/crypto/digests/Sha512Digest.cs
@@ -67,6 +67,26 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_BE(H1, output);
+            Pack.UInt64_To_BE(H2, output[8..]);
+            Pack.UInt64_To_BE(H3, output[16..]);
+            Pack.UInt64_To_BE(H4, output[24..]);
+            Pack.UInt64_To_BE(H5, output[32..]);
+            Pack.UInt64_To_BE(H6, output[40..]);
+            Pack.UInt64_To_BE(H7, output[48..]);
+            Pack.UInt64_To_BE(H8, output[56..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
@@ -99,6 +119,5 @@ namespace Org.BouncyCastle.Crypto.Digests
 
 			CopyIn(d);
 		}
-
     }
 }
diff --git a/crypto/src/crypto/digests/Sha512tDigest.cs b/crypto/src/crypto/digests/Sha512tDigest.cs
index 2caefa763..939dbda4f 100644
--- a/crypto/src/crypto/digests/Sha512tDigest.cs
+++ b/crypto/src/crypto/digests/Sha512tDigest.cs
@@ -76,6 +76,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return digestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            UInt64_To_BE(H1, output, 0, digestLength);
+            UInt64_To_BE(H2, output, 8, digestLength - 8);
+            UInt64_To_BE(H3, output, 16, digestLength - 16);
+            UInt64_To_BE(H4, output, 24, digestLength - 24);
+            UInt64_To_BE(H5, output, 32, digestLength - 32);
+            UInt64_To_BE(H6, output, 40, digestLength - 40);
+            UInt64_To_BE(H7, output, 48, digestLength - 48);
+            UInt64_To_BE(H8, output, 56, digestLength - 56);
+
+            Reset();
+
+            return digestLength;
+        }
+#endif
+
         /**
          * reset the chaining variables
          */
@@ -170,7 +190,32 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
-		public override IMemoable Copy()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private static void UInt64_To_BE(ulong n, Span<byte> bs, int off, int max)
+        {
+            if (max > 0)
+            {
+                UInt32_To_BE((uint)(n >> 32), bs, off, max);
+
+                if (max > 4)
+                {
+                    UInt32_To_BE((uint)n, bs, off + 4, max - 4);
+                }
+            }
+        }
+
+        private static void UInt32_To_BE(uint n, Span<byte> bs, int off, int max)
+        {
+            int num = System.Math.Min(4, max);
+            while (--num >= 0)
+            {
+                int shift = 8 * (3 - num);
+                bs[off + num] = (byte)(n >> shift);
+            }
+        }
+#endif
+
+        public override IMemoable Copy()
 		{
 			return new Sha512tDigest(this);
 		}
diff --git a/crypto/src/crypto/digests/ShakeDigest.cs b/crypto/src/crypto/digests/ShakeDigest.cs
index 8d7a7d6e3..17d262261 100644
--- a/crypto/src/crypto/digests/ShakeDigest.cs
+++ b/crypto/src/crypto/digests/ShakeDigest.cs
@@ -77,6 +77,34 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outLen;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..GetDigestSize()]);
+        }
+
+        public virtual int OutputFinal(Span<byte> output)
+        {
+            int length = Output(output);
+
+            Reset();
+
+            return length;
+        }
+
+        public virtual int Output(Span<byte> output)
+        {
+            if (!squeezing)
+            {
+                AbsorbBits(0x0F, 4);
+            }
+
+            Squeeze(output);
+
+            return output.Length;
+        }
+#endif
+
         /*
          * TODO Possible API change to support partial-byte suffixes.
          */
diff --git a/crypto/src/crypto/digests/ShortenedDigest.cs b/crypto/src/crypto/digests/ShortenedDigest.cs
index 9e4d99e7b..9e9998560 100644
--- a/crypto/src/crypto/digests/ShortenedDigest.cs
+++ b/crypto/src/crypto/digests/ShortenedDigest.cs
@@ -1,5 +1,4 @@
 using System;
-using Org.BouncyCastle.Crypto;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
@@ -58,7 +57,14 @@ namespace Org.BouncyCastle.Crypto.Digests
 			baseDigest.BlockUpdate(input, inOff, length);
 		}
 
-		public int DoFinal(byte[] output, int outOff)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            baseDigest.BlockUpdate(input);
+        }
+#endif
+
+        public int DoFinal(byte[] output, int outOff)
 		{
 			byte[] tmp = new byte[baseDigest.GetDigestSize()];
 
@@ -69,7 +75,20 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return length;
 		}
 
-		public void Reset()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            Span<byte> tmp = stackalloc byte[baseDigest.GetDigestSize()];
+
+            baseDigest.DoFinal(tmp);
+
+            tmp[..length].CopyTo(output);
+
+            return length;
+        }
+#endif
+
+        public void Reset()
 		{
 			baseDigest.Reset();
 		}
diff --git a/crypto/src/crypto/digests/SkeinDigest.cs b/crypto/src/crypto/digests/SkeinDigest.cs
index 394f0acd5..3dba9ec75 100644
--- a/crypto/src/crypto/digests/SkeinDigest.cs
+++ b/crypto/src/crypto/digests/SkeinDigest.cs
@@ -5,7 +5,6 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.Digests
 {
-
 	/// <summary>
 	/// Implementation of the Skein parameterised hash function in 256, 512 and 1024 bit block sizes,
 	/// based on the <see cref="Org.BouncyCastle.Crypto.Engines.ThreefishEngine">Threefish</see> tweakable block cipher.
@@ -111,5 +110,16 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return engine.DoFinal(outBytes, outOff);
 		}
 
-	}
-}
\ No newline at end of file
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            engine.Update(input);
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            return engine.DoFinal(output);
+        }
+#endif
+    }
+}
diff --git a/crypto/src/crypto/digests/SkeinEngine.cs b/crypto/src/crypto/digests/SkeinEngine.cs
index a36ac8fe7..86aa3c938 100644
--- a/crypto/src/crypto/digests/SkeinEngine.cs
+++ b/crypto/src/crypto/digests/SkeinEngine.cs
@@ -431,7 +431,7 @@ namespace Org.BouncyCastle.Crypto.Digests
                         currentOffset = 0;
                     }
 
-                    int toCopy = System.Math.Min((len - copied), currentBlock.Length - currentOffset);
+                    int toCopy = System.Math.Min(len - copied, currentBlock.Length - currentOffset);
                     Array.Copy(value, offset + copied, currentBlock, currentOffset, toCopy);
                     copied += toCopy;
                     currentOffset += toCopy;
@@ -439,6 +439,32 @@ namespace Org.BouncyCastle.Crypto.Digests
                 }
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public void Update(ReadOnlySpan<byte> input, ulong[] output)
+            {
+                /*
+                 * Buffer complete blocks for the underlying Threefish cipher, only flushing when there
+                 * are subsequent bytes (last block must be processed in doFinal() with final=true set).
+                 */
+                int copied = 0, len = input.Length;
+                while (len > copied)
+                {
+                    if (currentOffset == currentBlock.Length)
+                    {
+                        ProcessBlock(output);
+                        tweak.First = false;
+                        currentOffset = 0;
+                    }
+
+                    int toCopy = System.Math.Min(len - copied, currentBlock.Length - currentOffset);
+                    input.Slice(copied, toCopy).CopyTo(currentBlock.AsSpan(currentOffset));
+                    copied += toCopy;
+                    currentOffset += toCopy;
+                    tweak.AdvancePosition(toCopy);
+                }
+            }
+#endif
+
             private void ProcessBlock(ulong[] output)
             {
                 engine.threefish.Init(true, engine.chain, tweak.GetWords());
@@ -735,6 +761,14 @@ namespace Org.BouncyCastle.Crypto.Digests
             ubi.Update(inBytes, inOff, len, chain);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Update(ReadOnlySpan<byte> input)
+        {
+            CheckInitialised();
+            ubi.Update(input, chain);
+        }
+#endif
+
         public int DoFinal(byte[] outBytes, int outOff)
         {
             CheckInitialised();
@@ -770,6 +804,42 @@ namespace Org.BouncyCastle.Crypto.Digests
             return outputSizeBytes;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            CheckInitialised();
+            if (output.Length < outputSizeBytes)
+                throw new DataLengthException("Output span is too short to hold output");
+
+            // Finalise message block
+            UbiFinal();
+
+            // Process additional post-message parameters
+            if (postMessageParameters != null)
+            {
+                for (int i = 0; i < postMessageParameters.Length; i++)
+                {
+                    Parameter param = postMessageParameters[i];
+                    UbiComplete(param.Type, param.Value);
+                }
+            }
+
+            // Perform the output transform
+            int blockSize = BlockSize;
+            int blocksRequired = (outputSizeBytes + blockSize - 1) / blockSize;
+            for (int i = 0; i < blocksRequired; i++)
+            {
+                int toWrite = System.Math.Min(blockSize, outputSizeBytes - (i * blockSize));
+                //Output((ulong)i, outBytes, outOff + (i * blockSize), toWrite);
+                Output((ulong)i, output[(i * blockSize)..], toWrite);
+            }
+
+            Reset();
+
+            return outputSizeBytes;
+        }
+#endif
+
         private void Output(ulong outputSequence, byte[] outBytes, int outOff, int outputBytes)
         {
             byte[] currentBytes = new byte[8];
@@ -796,5 +866,34 @@ namespace Org.BouncyCastle.Crypto.Digests
                 }
             }
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void Output(ulong outputSequence, Span<byte> output, int outputBytes)
+        {
+            Span<byte> currentBytes = stackalloc byte[8];
+            Pack.UInt64_To_LE(outputSequence, currentBytes);
+
+            // Output is a sequence of UBI invocations all of which use and preserve the pre-output state
+            ulong[] outputWords = new ulong[chain.Length];
+            UbiInit(PARAM_TYPE_OUTPUT);
+            this.ubi.Update(currentBytes, outputWords);
+            ubi.DoFinal(outputWords);
+
+            int wordsRequired = (outputBytes + 8 - 1) / 8;
+            for (int i = 0; i < wordsRequired; i++)
+            {
+                int toWrite = System.Math.Min(8, outputBytes - (i * 8));
+                if (toWrite == 8)
+                {
+                    Pack.UInt64_To_LE(outputWords[i], output[(i * 8)..]);
+                }
+                else
+                {
+                    Pack.UInt64_To_LE(outputWords[i], currentBytes);
+                    currentBytes[..toWrite].CopyTo(output[(i * 8)..]);
+                }
+            }
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/TigerDigest.cs b/crypto/src/crypto/digests/TigerDigest.cs
index a452d3f0b..d83e905db 100644
--- a/crypto/src/crypto/digests/TigerDigest.cs
+++ b/crypto/src/crypto/digests/TigerDigest.cs
@@ -603,6 +603,20 @@ namespace Org.BouncyCastle.Crypto.Digests
             bOff = 0;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        private void ProcessWord(ReadOnlySpan<byte> b)
+        {
+            x[xOff++] = (long)Pack.LE_To_UInt64(b);
+
+            if (xOff == x.Length)
+            {
+                ProcessBlock();
+            }
+
+            bOff = 0;
+        }
+#endif
+
         public void Update(
             byte input)
         {
@@ -656,6 +670,47 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            int inOff = 0, length = input.Length;
+
+            //
+            // fill the current word
+            //
+            while ((bOff != 0) && (length > 0))
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+
+            //
+            // process whole words.
+            //
+            while (length >= 8)
+            {
+                ProcessWord(input[inOff..]);
+
+                inOff += 8;
+                length -= 8;
+                byteCount += 8;
+            }
+
+            //
+            // load in the remainder.
+            //
+            while (length > 0)
+            {
+                Update(input[inOff]);
+
+                inOff++;
+                length--;
+            }
+        }
+#endif
+
         private void RoundABC(
             long    x,
             long    mul)
@@ -809,6 +864,21 @@ namespace Org.BouncyCastle.Crypto.Digests
             return DigestLength;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            Finish();
+
+            Pack.UInt64_To_LE((ulong)a, output);
+            Pack.UInt64_To_LE((ulong)b, output[8..]);
+            Pack.UInt64_To_LE((ulong)c, output[16..]);
+
+            Reset();
+
+            return DigestLength;
+        }
+#endif
+
         /**
         * reset the chaining variables
         */
diff --git a/crypto/src/crypto/digests/TupleHash.cs b/crypto/src/crypto/digests/TupleHash.cs
index 98c2d2acf..43030d5d5 100644
--- a/crypto/src/crypto/digests/TupleHash.cs
+++ b/crypto/src/crypto/digests/TupleHash.cs
@@ -78,7 +78,7 @@ namespace Org.BouncyCastle.Crypto.Digests
             cshake.BlockUpdate(bytes, 0, bytes.Length);
         }
 
-        private void wrapUp(int outputSize)
+        private void WrapUp(int outputSize)
         {
             byte[] encOut = XofUtilities.RightEncode(outputSize * 8);
 
@@ -89,23 +89,14 @@ namespace Org.BouncyCastle.Crypto.Digests
 
         public virtual int DoFinal(byte[] outBuf, int outOff)
         {
-            if (firstOutput)
-            {
-                wrapUp(GetDigestSize());
-            }
-
-            int rv = cshake.DoFinal(outBuf, outOff, GetDigestSize());
-
-            Reset();
-
-            return rv;
+            return DoFinal(outBuf, outOff, GetDigestSize());
         }
 
         public virtual int DoFinal(byte[] outBuf, int outOff, int outLen)
         {
             if (firstOutput)
             {
-                wrapUp(GetDigestSize());
+                WrapUp(GetDigestSize());
             }
 
             int rv = cshake.DoFinal(outBuf, outOff, outLen);
@@ -119,7 +110,7 @@ namespace Org.BouncyCastle.Crypto.Digests
         {
             if (firstOutput)
             {
-                wrapUp(0);
+                WrapUp(0);
             }
 
             return cshake.DoOutput(outBuf, outOff, outLen);
@@ -130,5 +121,41 @@ namespace Org.BouncyCastle.Crypto.Digests
             cshake.Reset();
             firstOutput = true;
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public virtual void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            XofUtilities.EncodeTo(cshake, input);
+        }
+
+        public virtual int DoFinal(Span<byte> output)
+        {
+            return OutputFinal(output[..GetDigestSize()]);
+        }
+
+        public virtual int OutputFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(GetDigestSize());
+            }
+
+            int rv = cshake.OutputFinal(output);
+
+            Reset();
+
+            return rv;
+        }
+
+        public virtual int Output(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                WrapUp(0);
+            }
+
+            return cshake.Output(output);
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/digests/WhirlpoolDigest.cs b/crypto/src/crypto/digests/WhirlpoolDigest.cs
index b28e259f3..73d389a3c 100644
--- a/crypto/src/crypto/digests/WhirlpoolDigest.cs
+++ b/crypto/src/crypto/digests/WhirlpoolDigest.cs
@@ -162,6 +162,20 @@ namespace Org.BouncyCastle.Crypto.Digests
 			return GetDigestSize();
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public int DoFinal(Span<byte> output)
+		{
+			// sets output[0..DIGEST_LENGTH_BYTES]
+			Finish();
+
+			Pack.UInt64_To_BE(_hash, output);
+
+			Reset();
+
+			return GetDigestSize();
+		}
+#endif
+
 		/**
 		* Reset the chaining variables
 		*/
@@ -276,6 +290,16 @@ namespace Org.BouncyCastle.Crypto.Digests
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public void BlockUpdate(ReadOnlySpan<byte> input)
+		{
+			for (int i = 0; i < input.Length; ++i)
+			{
+				Update(input[i]);
+			}
+		}
+#endif
+
 		private void Finish()
 		{
 			/*
diff --git a/crypto/src/crypto/digests/XofUtils.cs b/crypto/src/crypto/digests/XofUtils.cs
index a4d6622b3..a1242f987 100644
--- a/crypto/src/crypto/digests/XofUtils.cs
+++ b/crypto/src/crypto/digests/XofUtils.cs
@@ -28,6 +28,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return b;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static int LeftEncode(long length, Span<byte> lengthEncoding)
+        {
+            byte n = 1;
+
+            long v = length;
+            while ((v >>= 8) != 0)
+            {
+                n++;
+            }
+
+            lengthEncoding[0] = n;
+            for (int i = 1; i <= n; i++)
+            {
+                lengthEncoding[i] = (byte)(length >> (8 * (n - i)));
+            }
+            return 1 + n;
+        }
+#endif
+
         internal static byte[] RightEncode(long strLen)
         {
             byte n = 1;
@@ -50,6 +70,26 @@ namespace Org.BouncyCastle.Crypto.Digests
             return b;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static int RightEncode(long length, Span<byte> lengthEncoding)
+        {
+            byte n = 1;
+
+            long v = length;
+            while ((v >>= 8) != 0)
+            {
+                n++;
+            }
+
+            lengthEncoding[n] = n;
+            for (int i = 0; i < n; i++)
+            {
+                lengthEncoding[i] = (byte)(length >> (8 * (n - i - 1)));
+            }
+            return n + 1;
+        }
+#endif
+
         internal static byte[] Encode(byte X)
         {
             return Arrays.Concatenate(LeftEncode(8), new byte[] { X });
@@ -63,5 +103,15 @@ namespace Org.BouncyCastle.Crypto.Digests
             }
             return Arrays.Concatenate(LeftEncode(len * 8), Arrays.CopyOfRange(inBuf, inOff, inOff + len));
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void EncodeTo(IDigest digest, ReadOnlySpan<byte> buf)
+        {
+            Span<byte> lengthEncoding = stackalloc byte[9];
+            int count = LeftEncode(buf.Length * 8, lengthEncoding);
+            digest.BlockUpdate(lengthEncoding[..count]);
+            digest.BlockUpdate(buf);
+        }
+#endif
     }
 }
diff --git a/crypto/src/crypto/macs/KMac.cs b/crypto/src/crypto/macs/KMac.cs
index 05031ac2f..ce6c9f701 100644
--- a/crypto/src/crypto/macs/KMac.cs
+++ b/crypto/src/crypto/macs/KMac.cs
@@ -39,6 +39,16 @@ namespace Org.BouncyCastle.Crypto.Macs
             cshake.BlockUpdate(input, inOff, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            if (!initialised)
+                throw new InvalidOperationException("KMAC not initialized");
+
+            cshake.BlockUpdate(input);
+        }
+#endif
+
         public int DoFinal(byte[] output, int outOff)
         {
             if (firstOutput)
@@ -58,6 +68,27 @@ namespace Org.BouncyCastle.Crypto.Macs
             return rv;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int DoFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(GetMacSize() * 8, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+            }
+
+            int rv = cshake.OutputFinal(output[..GetMacSize()]);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
         public int DoFinal(byte[] output, int outOff, int outLen)
         {
             if (firstOutput)
@@ -77,6 +108,27 @@ namespace Org.BouncyCastle.Crypto.Macs
             return rv;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int OutputFinal(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(output.Length * 8, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+            }
+
+            int rv = cshake.OutputFinal(output);
+
+            Reset();
+
+            return rv;
+        }
+#endif
+
         public int DoOutput(byte[] output, int outOff, int outLen)
         {
             if (firstOutput)
@@ -94,6 +146,25 @@ namespace Org.BouncyCastle.Crypto.Macs
             return cshake.DoOutput(output, outOff, outLen);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public int Output(Span<byte> output)
+        {
+            if (firstOutput)
+            {
+                if (!initialised)
+                    throw new InvalidOperationException("KMAC not initialized");
+
+                Span<byte> lengthEncoding = stackalloc byte[9];
+                int count = XofUtilities.RightEncode(0, lengthEncoding);
+                cshake.BlockUpdate(lengthEncoding[..count]);
+
+                firstOutput = false;
+            }
+
+            return cshake.Output(output);
+        }
+#endif
+
         public int GetByteLength()
         {
             return cshake.GetByteLength();
diff --git a/crypto/src/pqc/crypto/lms/LMSContext.cs b/crypto/src/pqc/crypto/lms/LMSContext.cs
index 35c33093b..2113184fe 100644
--- a/crypto/src/pqc/crypto/lms/LMSContext.cs
+++ b/crypto/src/pqc/crypto/lms/LMSContext.cs
@@ -124,5 +124,17 @@ namespace Org.BouncyCastle.Pqc.Crypto.Lms
         {
             digest.Reset();
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void BlockUpdate(ReadOnlySpan<byte> input)
+        {
+            digest.BlockUpdate(input);
+        }
+
+        public int DoFinal(Span<byte> output)
+        {
+            return digest.DoFinal(output);
+        }
+#endif
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/src/security/DigestUtilities.cs b/crypto/src/security/DigestUtilities.cs
index 2c9e89277..035280fd6 100644
--- a/crypto/src/security/DigestUtilities.cs
+++ b/crypto/src/security/DigestUtilities.cs
@@ -266,9 +266,22 @@ namespace Org.BouncyCastle.Security
         public static byte[] CalculateDigest(string algorithm, byte[] input)
         {
             IDigest digest = GetDigest(algorithm);
-            digest.BlockUpdate(input, 0, input.Length);
-            return DoFinal(digest);
+            return DoFinal(digest, input);
+        }
+
+        public static byte[] CalculateDigest(string algorithm, byte[] buf, int off, int len)
+        {
+            IDigest digest = GetDigest(algorithm);
+            return DoFinal(digest, buf, off, len);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] CalculateDigest(string algorithm, ReadOnlySpan<byte> buffer)
+        {
+            IDigest digest = GetDigest(algorithm);
+            return DoFinal(digest, buffer);
         }
+#endif
 
         public static byte[] DoFinal(IDigest digest)
         {
@@ -282,5 +295,19 @@ namespace Org.BouncyCastle.Security
             digest.BlockUpdate(input, 0, input.Length);
             return DoFinal(digest);
         }
+
+        public static byte[] DoFinal(IDigest digest, byte[] buf, int off, int len)
+        {
+            digest.BlockUpdate(buf, off, len);
+            return DoFinal(digest);
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static byte[] DoFinal(IDigest digest, ReadOnlySpan<byte> buffer)
+        {
+            digest.BlockUpdate(buffer);
+            return DoFinal(digest);
+        }
+#endif
     }
 }
diff --git a/crypto/test/src/crypto/test/Blake2bDigestTest.cs b/crypto/test/src/crypto/test/Blake2bDigestTest.cs
index e7835e2a7..518331b2e 100644
--- a/crypto/test/src/crypto/test/Blake2bDigestTest.cs
+++ b/crypto/test/src/crypto/test/Blake2bDigestTest.cs
@@ -146,6 +146,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             ResetTest();
             DoTestNullKeyVsUnkeyed();
             DoTestLengthConstruction();
+
+            DigestTest.SpanConsistencyTests(this, new Blake2bDigest(512));
         }
 
         private void CloneTest()
diff --git a/crypto/test/src/crypto/test/Blake2sDigestTest.cs b/crypto/test/src/crypto/test/Blake2sDigestTest.cs
index 2080e2871..10c35579f 100644
--- a/crypto/test/src/crypto/test/Blake2sDigestTest.cs
+++ b/crypto/test/src/crypto/test/Blake2sDigestTest.cs
@@ -293,6 +293,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             RunSelfTest();
             DoTestNullKeyVsUnkeyed();
             DoTestLengthConstruction();
+
+            DigestTest.SpanConsistencyTests(this, new Blake2sDigest(256));
         }
 
         [Test]
diff --git a/crypto/test/src/crypto/test/CSHAKETest.cs b/crypto/test/src/crypto/test/CSHAKETest.cs
index 6a3c99a0f..581832aaf 100644
--- a/crypto/test/src/crypto/test/CSHAKETest.cs
+++ b/crypto/test/src/crypto/test/CSHAKETest.cs
@@ -103,6 +103,9 @@ namespace Org.BouncyCastle.Crypto.Tests
             checkSHAKE(128, new CShakeDigest(128, null, new byte[0]), Hex.Decode("eeaabeef"));
             checkSHAKE(128, new CShakeDigest(128, null, null), Hex.Decode("eeaabeef"));
             checkSHAKE(256, new CShakeDigest(256, null, null), Hex.Decode("eeaabeef"));
+
+            DigestTest.SpanConsistencyTests(this, new CShakeDigest(128, null, null));
+            DigestTest.SpanConsistencyTests(this, new CShakeDigest(256, null, null));
         }
 
         private void checkZeroPadZ()
diff --git a/crypto/test/src/crypto/test/DigestTest.cs b/crypto/test/src/crypto/test/DigestTest.cs
index 930979643..e2d2a47ef 100644
--- a/crypto/test/src/crypto/test/DigestTest.cs
+++ b/crypto/test/src/crypto/test/DigestTest.cs
@@ -1,7 +1,6 @@
 using System;
 
-using Org.BouncyCastle.Crypto;
-
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
@@ -11,6 +10,8 @@ namespace Org.BouncyCastle.Crypto.Tests
 	public abstract class DigestTest
 		: SimpleTest
 	{
+		internal static readonly SecureRandom Random = new SecureRandom();
+
 		private IDigest digest;
 		private string[] input;
 		private string[] results;
@@ -108,6 +109,8 @@ namespace Org.BouncyCastle.Crypto.Tests
 			{
 				Fail("failing memo copy vector test", results[results.Length - 1], Hex.ToHexString(resBuf));
 			}
+
+			SpanConsistencyTests(this, digest);
 		}
 
 		private byte[] toByteArray(
@@ -179,5 +182,57 @@ namespace Org.BouncyCastle.Crypto.Tests
 				Fail("64k test failed");
 			}
 		}
+
+		internal static void SpanConsistencyTests(SimpleTest test, IDigest digest)
+        {
+			// 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
+
+			// Span-based API consistency checks
+			byte[] data = new byte[16 + 256];
+			Random.NextBytes(data);
+
+			for (int len = 0; len <= 256; ++len)
+			{
+				int off = Random.Next(0, 17);
+
+				SpanConsistencyTest(test, digest, data, off, len);
+			}
+#endif
+		}
+
+		internal static void SpanConsistencyTest(SimpleTest test, IDigest digest, byte[] buf, int off, int len)
+        {
+			// 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
+			digest.Reset();
+
+			byte[] arrayResult = DigestUtilities.DoFinal(digest, buf, off, len);
+			byte[] spanResult1 = DigestUtilities.DoFinal(digest, buf.AsSpan(off, len));
+
+			if (!test.AreEqual(arrayResult, spanResult1))
+			{
+				test.Fail("failing span consistency test 1", Hex.ToHexString(arrayResult), Hex.ToHexString(spanResult1));
+			}
+
+			int pos = 0;
+			while (pos < len)
+			{
+				int next = 1 + Random.Next(len - pos);
+				digest.BlockUpdate(buf.AsSpan(off + pos, next));
+				pos += next;
+			}
+
+			byte[] spanResult2 = new byte[digest.GetDigestSize()];
+			digest.DoFinal(spanResult2.AsSpan());
+
+			if (!test.AreEqual(arrayResult, spanResult2))
+			{
+				test.Fail("failing span consistency test 2", Hex.ToHexString(arrayResult), Hex.ToHexString(spanResult2));
+			}
+#endif
+		}
 	}
 }
diff --git a/crypto/test/src/crypto/test/Haraka256DigestTest.cs b/crypto/test/src/crypto/test/Haraka256DigestTest.cs
index 3f7706211..e8a181169 100644
--- a/crypto/test/src/crypto/test/Haraka256DigestTest.cs
+++ b/crypto/test/src/crypto/test/Haraka256DigestTest.cs
@@ -2,6 +2,7 @@
 using NUnit.Framework;
 
 using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
@@ -185,6 +186,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             TestInputTooShort();
             TestOutput();
             TestMonty();
+
+            SpanConsistencyTests();
         }
 
         [Test]
@@ -194,5 +197,22 @@ namespace Org.BouncyCastle.Crypto.Tests
 
             Assert.AreEqual(Name + ": Okay", resultText);
         }
+
+        private void SpanConsistencyTests()
+        {
+            // 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
+            // Span-based API consistency checks
+            byte[] data = new byte[16 + 32];
+            DigestTest.Random.NextBytes(data);
+
+            var digest = new Haraka256Digest();
+            for (int off = 0; off <= 16; ++off)
+            {
+                DigestTest.SpanConsistencyTest(this, digest, data, off, 32);
+            }
+#endif
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/test/src/crypto/test/Haraka512DigestTest.cs b/crypto/test/src/crypto/test/Haraka512DigestTest.cs
index 11aa746af..88208100e 100644
--- a/crypto/test/src/crypto/test/Haraka512DigestTest.cs
+++ b/crypto/test/src/crypto/test/Haraka512DigestTest.cs
@@ -188,6 +188,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             TestInputTooShort();
             TestOutput();
             TestMonty();
+
+            SpanConsistencyTests();
         }
 
         [Test]
@@ -197,5 +199,22 @@ namespace Org.BouncyCastle.Crypto.Tests
 
             Assert.AreEqual(Name + ": Okay", resultText);
         }
+
+        private void SpanConsistencyTests()
+        {
+            // 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
+            // Span-based API consistency checks
+            byte[] data = new byte[16 + 64];
+            DigestTest.Random.NextBytes(data);
+
+            var digest = new Haraka512Digest();
+            for (int off = 0; off <= 16; ++off)
+            {
+                DigestTest.SpanConsistencyTest(this, digest, data, off, 64);
+            }
+#endif
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/crypto/test/src/crypto/test/KeccakDigestTest.cs b/crypto/test/src/crypto/test/KeccakDigestTest.cs
index ddabddab4..12b310fc0 100644
--- a/crypto/test/src/crypto/test/KeccakDigestTest.cs
+++ b/crypto/test/src/crypto/test/KeccakDigestTest.cs
@@ -273,6 +273,8 @@ namespace Org.BouncyCastle.Crypto.Tests
             //    Fail("Keccak mismatch on " + digest.AlgorithmName + " extreme data test");
             //}
             //Console.WriteLine("Done");
+
+            DigestTest.SpanConsistencyTests(this, digest);
         }
 
         private void TestDigestDoFinal(IDigest digest)
diff --git a/crypto/test/src/crypto/test/ParallelHashTest.cs b/crypto/test/src/crypto/test/ParallelHashTest.cs
index 536567313..e37516366 100644
--- a/crypto/test/src/crypto/test/ParallelHashTest.cs
+++ b/crypto/test/src/crypto/test/ParallelHashTest.cs
@@ -119,6 +119,9 @@ namespace Org.BouncyCastle.Crypto.Tests
             IsTrue("oops!", Arrays.AreEqual(Hex.Decode("6b3e790b330c889a204c2fbc728d809f19367328d852f4002dc829f73afd6bcefb7fe5b607b13a801c0be5c1170bdb794e339458fdb0e62a6af3d42558970249"), res));
 
             testEmpty();
+
+            DigestTest.SpanConsistencyTests(this, new ParallelHash(128, new byte[0], 8));
+            DigestTest.SpanConsistencyTests(this, new ParallelHash(256, new byte[0], 8));
         }
 
         private void testEmpty()
diff --git a/crypto/test/src/crypto/test/SHA3DigestTest.cs b/crypto/test/src/crypto/test/SHA3DigestTest.cs
index 7b9ab26cf..2984f1c83 100644
--- a/crypto/test/src/crypto/test/SHA3DigestTest.cs
+++ b/crypto/test/src/crypto/test/SHA3DigestTest.cs
@@ -40,6 +40,8 @@ namespace Org.BouncyCastle.Crypto.Tests
         public override void PerformTest()
         {
             TestVectors();
+
+            DigestTest.SpanConsistencyTests(this, new Sha3Digest());
         }
 
         public void TestVectors()
diff --git a/crypto/test/src/crypto/test/ShakeDigestTest.cs b/crypto/test/src/crypto/test/ShakeDigestTest.cs
index 4b4d0fbd6..0aeedb256 100644
--- a/crypto/test/src/crypto/test/ShakeDigestTest.cs
+++ b/crypto/test/src/crypto/test/ShakeDigestTest.cs
@@ -40,6 +40,8 @@ namespace Org.BouncyCastle.Crypto.Tests
         public override void PerformTest()
         {
             TestVectors();
+
+            DigestTest.SpanConsistencyTests(this, new ShakeDigest());
         }
 
         public void TestVectors()
diff --git a/crypto/test/src/crypto/test/ShortenedDigestTest.cs b/crypto/test/src/crypto/test/ShortenedDigestTest.cs
index 927ffee3a..01c408219 100644
--- a/crypto/test/src/crypto/test/ShortenedDigestTest.cs
+++ b/crypto/test/src/crypto/test/ShortenedDigestTest.cs
@@ -74,6 +74,8 @@ namespace Org.BouncyCastle.Crypto.Tests
 			{
 				// expected
 			}
+
+			DigestTest.SpanConsistencyTests(this, new ShortenedDigest(new Sha1Digest(), 10));
 		}
 
 		public override string Name
diff --git a/crypto/test/src/crypto/test/SkeinDigestTest.cs b/crypto/test/src/crypto/test/SkeinDigestTest.cs
index 50a2d9565..7935eaafb 100644
--- a/crypto/test/src/crypto/test/SkeinDigestTest.cs
+++ b/crypto/test/src/crypto/test/SkeinDigestTest.cs
@@ -205,6 +205,8 @@ namespace Org.BouncyCastle.Crypto.Tests
 				Case test = TEST_CASES[i];
 				runTest(test);
 			}
+
+			DigestTest.SpanConsistencyTests(this, new SkeinDigest(256, 256));
 		}
 
 		private void runTest(Case dc)
diff --git a/crypto/test/src/crypto/test/TupleHashTest.cs b/crypto/test/src/crypto/test/TupleHashTest.cs
index fd1ee001f..ba9010e99 100644
--- a/crypto/test/src/crypto/test/TupleHashTest.cs
+++ b/crypto/test/src/crypto/test/TupleHashTest.cs
@@ -3,6 +3,7 @@ using System;
 using NUnit.Framework;
 
 using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
 using Org.BouncyCastle.Utilities;
 using Org.BouncyCastle.Utilities.Encoders;
 using Org.BouncyCastle.Utilities.Test;
@@ -103,6 +104,8 @@ namespace Org.BouncyCastle.Crypto.Tests
 
             IsTrue("oops!", !Arrays.AreEqual(Hex.Decode("45 00 0B E6 3F 9B 6B FD 89 F5 47 17 67 0F 69 A9 BC 76 35 91 A4 F0 5C 50 D6 88 91 A7 44 BC C6 E7 D6 D5 B5 E8 2C 01 8D A9 99 ED 35 B0 BB 49 C9 67 8E 52 6A BD 8E 85 C1 3E D2 54 02 1D B9 E7 90 CE"), res));
             IsTrue("oops!", Arrays.AreEqual(Hex.Decode("0c59b11464f2336c34663ed51b2b950bec743610856f36c28d1d088d8a2446284dd09830a6a178dc752376199fae935d86cfdee5913d4922dfd369b66a53c897"), res));
+
+            SpanConsistencyTests();
         }
 
         [Test]
@@ -112,5 +115,64 @@ namespace Org.BouncyCastle.Crypto.Tests
 
             Assert.AreEqual(Name + ": Okay", resultText);
         }
+
+        internal void SpanConsistencyTests()
+        {
+            // 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
+            IDigest digest1 = new TupleHash(128, new byte[0]);
+            IDigest digest2 = new TupleHash(128, new byte[0]);
+
+            // Span-based API consistency checks
+            byte[] data = new byte[16 + 256];
+            DigestTest.Random.NextBytes(data);
+
+            for (int len = 0; len <= 256; ++len)
+            {
+                int off = DigestTest.Random.Next(0, 17);
+
+                SpanConsistencyTest(digest1, digest2, data, off, len);
+            }
+#endif
+        }
+
+        internal void SpanConsistencyTest(IDigest digest1, IDigest digest2, byte[] buf, int off, int len)
+        {
+            // 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
+            digest1.Reset();
+            digest2.Reset();
+
+            byte[] arrayResult1 = DigestUtilities.DoFinal(digest1, buf, off, len);
+            byte[] spanResult1 = DigestUtilities.DoFinal(digest2, buf.AsSpan(off, len));
+
+            if (!AreEqual(arrayResult1, spanResult1))
+            {
+                Fail("failing span consistency test 1", Hex.ToHexString(arrayResult1), Hex.ToHexString(spanResult1));
+            }
+
+            int pos = 0;
+            while (pos < len)
+            {
+                int next = 1 + DigestTest.Random.Next(len - pos);
+                digest1.BlockUpdate(buf, off + pos, next);
+                digest2.BlockUpdate(buf.AsSpan(off + pos, next));
+                pos += next;
+            }
+
+            byte[] arrayResult2 = new byte[digest1.GetDigestSize()];
+            digest1.DoFinal(arrayResult2, 0);
+
+            byte[] spanResult2 = new byte[digest2.GetDigestSize()];
+            digest2.DoFinal(spanResult2.AsSpan());
+
+            if (!AreEqual(arrayResult2, spanResult2))
+            {
+                Fail("failing span consistency test 2", Hex.ToHexString(arrayResult2), Hex.ToHexString(spanResult2));
+            }
+#endif
+        }
     }
 }