summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Dettman <peter.dettman@bouncycastle.org>2022-09-03 00:36:40 +0700
committerPeter Dettman <peter.dettman@bouncycastle.org>2022-09-03 00:36:40 +0700
commitaa5f3f0929c50fc942325f18ed7ae48129d4c992 (patch)
tree0382f7336cb55c4a01bd38782d7694d1cb094921
parentClean up tests (diff)
downloadBouncyCastle.NET-ed25519-aa5f3f0929c50fc942325f18ed7ae48129d4c992.tar.xz
Stream modernization
-rw-r--r--crypto/src/asn1/Asn1OutputStream.cs16
-rw-r--r--crypto/src/asn1/BEROctetStringGenerator.cs68
-rw-r--r--crypto/src/asn1/DerOctetString.cs17
-rw-r--r--crypto/src/bcpg/ArmoredInputStream.cs8
-rw-r--r--crypto/src/bcpg/ArmoredOutputStream.cs27
-rw-r--r--crypto/src/bcpg/BcpgInputStream.cs12
-rw-r--r--crypto/src/bcpg/BcpgOutputStream.cs10
-rw-r--r--crypto/src/bzip2/CBZip2OutputStream.cs43
-rw-r--r--crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs20
-rw-r--r--crypto/src/cms/CMSCompressedDataStreamGenerator.cs14
-rw-r--r--crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs43
-rw-r--r--crypto/src/cms/CMSSignedDataStreamGenerator.cs8
-rw-r--r--crypto/src/cms/CMSTypedStream.cs26
-rw-r--r--crypto/src/crypto/io/CipherStream.cs234
-rw-r--r--crypto/src/crypto/io/DigestSink.cs25
-rw-r--r--crypto/src/crypto/io/DigestStream.cs109
-rw-r--r--crypto/src/crypto/io/MacSink.cs25
-rw-r--r--crypto/src/crypto/io/MacStream.cs109
-rw-r--r--crypto/src/crypto/io/SignerSink.cs25
-rw-r--r--crypto/src/crypto/io/SignerStream.cs73
-rw-r--r--crypto/src/openpgp/PgpCompressedDataGenerator.cs36
-rw-r--r--crypto/src/openpgp/PgpEncryptedData.cs2
-rw-r--r--crypto/src/openpgp/PgpEncryptedDataGenerator.cs2
-rw-r--r--crypto/src/openpgp/WrappedGeneratorStream.cs6
-rw-r--r--crypto/src/tls/ByteQueue.cs59
-rw-r--r--crypto/src/tls/ByteQueueInputStream.cs20
-rw-r--r--crypto/src/tls/ByteQueueOutputStream.cs7
-rw-r--r--crypto/src/tls/TlsStream.cs12
-rw-r--r--crypto/src/util/io/BaseOutputStream.cs8
-rw-r--r--crypto/src/util/io/FilterStream.cs42
-rw-r--r--crypto/src/util/io/LimitedInputStream.cs56
-rw-r--r--crypto/src/util/io/PushbackStream.cs36
-rw-r--r--crypto/src/util/io/Streams.cs53
-rw-r--r--crypto/src/util/io/TeeInputStream.cs25
-rw-r--r--crypto/src/util/io/TeeOutputStream.cs19
-rw-r--r--crypto/src/util/zlib/ZInputStream.cs26
-rw-r--r--crypto/src/util/zlib/ZOutputStream.cs74
-rw-r--r--crypto/test/src/crypto/io/test/CipherStreamTest.cs37
38 files changed, 844 insertions, 588 deletions
diff --git a/crypto/src/asn1/Asn1OutputStream.cs b/crypto/src/asn1/Asn1OutputStream.cs
index 1a69d7c2c..59178ea31 100644
--- a/crypto/src/asn1/Asn1OutputStream.cs
+++ b/crypto/src/asn1/Asn1OutputStream.cs
@@ -77,7 +77,11 @@ namespace Org.BouncyCastle.Asn1
                 return;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> stack = stackalloc byte[5];
+#else
             byte[] stack = new byte[5];
+#endif
             int pos = stack.Length;
 
             do
@@ -90,7 +94,11 @@ namespace Org.BouncyCastle.Asn1
             int count = stack.Length - pos;
             stack[--pos] = (byte)(0x80 | count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Write(stack[pos..]);
+#else
             Write(stack, pos, count + 1);
+#endif
         }
 
         internal void WriteIdentifier(int tagClass, int tagNo)
@@ -101,7 +109,11 @@ namespace Org.BouncyCastle.Asn1
                 return;
             }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Span<byte> stack = stackalloc byte[6];
+#else
             byte[] stack = new byte[6];
+#endif
             int pos = stack.Length;
 
             stack[--pos] = (byte)(tagNo & 0x7F);
@@ -113,7 +125,11 @@ namespace Org.BouncyCastle.Asn1
 
             stack[--pos] = (byte)(tagClass | 0x1F);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            Write(stack[pos..]);
+#else
             Write(stack, pos, stack.Length - pos);
+#endif
         }
 
         internal static IAsn1Encoding[] GetContentsEncodings(int encoding, Asn1Encodable[] elements)
diff --git a/crypto/src/asn1/BEROctetStringGenerator.cs b/crypto/src/asn1/BEROctetStringGenerator.cs
index 7893139d6..b07576e7d 100644
--- a/crypto/src/asn1/BEROctetStringGenerator.cs
+++ b/crypto/src/asn1/BEROctetStringGenerator.cs
@@ -64,6 +64,9 @@ namespace Org.BouncyCastle.Asn1
 			{
 				Streams.ValidateBufferArguments(buffer, offset, count);
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                Write(buffer.AsSpan(offset, count));
+#else
                 int bufLen = _buf.Length;
                 int available = bufLen - _off;
                 if (count < available)
@@ -77,8 +80,9 @@ namespace Org.BouncyCastle.Asn1
                 if (_off > 0)
                 {
                     Array.Copy(buffer, offset, _buf, _off, available);
-                    pos += available;
+                    pos = available;
                     DerOctetString.Encode(_derOut, _buf, 0, bufLen);
+                    //_off = 0;
                 }
 
                 int remaining;
@@ -90,9 +94,40 @@ namespace Org.BouncyCastle.Asn1
 
                 Array.Copy(buffer, offset + pos, _buf, 0, remaining);
                 this._off = remaining;
+#endif
+            }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void Write(ReadOnlySpan<byte> buffer)
+			{
+                int bufLen = _buf.Length;
+                int available = bufLen - _off;
+                if (buffer.Length < available)
+                {
+                    buffer.CopyTo(_buf.AsSpan(_off));
+                    _off += buffer.Length;
+                    return;
+                }
+
+                if (_off > 0)
+                {
+                    DerOctetString.Encode(_derOut, _buf.AsSpan(0, _off), buffer[..available]);
+                    buffer = buffer[available..];
+                    //_off = 0;
+                }
+
+                while (buffer.Length >= bufLen)
+                {
+                    DerOctetString.Encode(_derOut, buffer[..bufLen]);
+                    buffer = buffer[bufLen..];
+                }
+
+                buffer.CopyTo(_buf.AsSpan());
+                _off = buffer.Length;
             }
+#endif
 
-			public override void WriteByte(byte value)
+            public override void WriteByte(byte value)
 			{
 				_buf[_off++] = value;
 
@@ -103,33 +138,20 @@ namespace Org.BouncyCastle.Asn1
 				}
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
                 {
-                    ImplClose();
-                }
-                base.Dispose(disposing);
-            }
-#else
-            public override void Close()
-            {
-                ImplClose();
-                base.Close();
-            }
-#endif
-
-            private void ImplClose()
-            {
-                if (_off != 0)
-                {
-                    DerOctetString.Encode(_derOut, _buf, 0, _off);
-                }
+                    if (_off != 0)
+                    {
+                        DerOctetString.Encode(_derOut, _buf, 0, _off);
+                    }
 
-                _derOut.FlushInternal();
+                    _derOut.FlushInternal();
 
-                _gen.WriteBerEnd();
+                    _gen.WriteBerEnd();
+                }
+                base.Dispose(disposing);
             }
         }
     }
diff --git a/crypto/src/asn1/DerOctetString.cs b/crypto/src/asn1/DerOctetString.cs
index d9913f065..ea13765ec 100644
--- a/crypto/src/asn1/DerOctetString.cs
+++ b/crypto/src/asn1/DerOctetString.cs
@@ -37,5 +37,22 @@ namespace Org.BouncyCastle.Asn1
             asn1Out.WriteDL(len);
             asn1Out.Write(buf, off, len);
         }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        internal static void Encode(Asn1OutputStream asn1Out, ReadOnlySpan<byte> buf)
+        {
+            asn1Out.WriteIdentifier(Asn1Tags.Universal, Asn1Tags.OctetString);
+            asn1Out.WriteDL(buf.Length);
+            asn1Out.Write(buf);
+        }
+
+        internal static void Encode(Asn1OutputStream asn1Out, ReadOnlySpan<byte> buf1, ReadOnlySpan<byte> buf2)
+        {
+            asn1Out.WriteIdentifier(Asn1Tags.Universal, Asn1Tags.OctetString);
+            asn1Out.WriteDL(buf1.Length + buf2.Length);
+            asn1Out.Write(buf1);
+            asn1Out.Write(buf2);
+        }
+#endif
     }
 }
diff --git a/crypto/src/bcpg/ArmoredInputStream.cs b/crypto/src/bcpg/ArmoredInputStream.cs
index 224ef7d28..1c5ebd7c5 100644
--- a/crypto/src/bcpg/ArmoredInputStream.cs
+++ b/crypto/src/bcpg/ArmoredInputStream.cs
@@ -482,7 +482,6 @@ namespace Org.BouncyCastle.Bcpg
             return c;
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -491,13 +490,6 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(input);
-			base.Close();
-		}
-#endif
 
         /**
          * Change how the stream should react if it encounters missing CRC checksum.
diff --git a/crypto/src/bcpg/ArmoredOutputStream.cs b/crypto/src/bcpg/ArmoredOutputStream.cs
index 4a726e2dd..7f3bcc2ef 100644
--- a/crypto/src/bcpg/ArmoredOutputStream.cs
+++ b/crypto/src/bcpg/ArmoredOutputStream.cs
@@ -331,35 +331,20 @@ namespace Org.BouncyCastle.Bcpg
          * <b>Note</b>: Close() does not close the underlying stream. So it is possible to write
          * multiple objects using armoring to a single stream.
          */
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
             {
-                if (type == null)
-                    return;
-
-                DoClose();
+                if (type != null)
+                {
+                    DoClose();
 
-                type = null;
-                start = true;
+                    type = null;
+                    start = true;
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (type == null)
-                return;
-
-            DoClose();
-
-            type = null;
-            start = true;
-
-            base.Close();
-        }
-#endif
 
         private void DoClose()
         {
diff --git a/crypto/src/bcpg/BcpgInputStream.cs b/crypto/src/bcpg/BcpgInputStream.cs
index 895b03260..5aa7ae306 100644
--- a/crypto/src/bcpg/BcpgInputStream.cs
+++ b/crypto/src/bcpg/BcpgInputStream.cs
@@ -184,11 +184,7 @@ namespace Org.BouncyCastle.Bcpg
             else
             {
                 PartialInputStream pis = new PartialInputStream(this, partial, bodyLen);
-#if PORTABLE
-                Stream buf = pis;
-#else
 				Stream buf = new BufferedStream(pis);
-#endif
                 objStream = new BcpgInputStream(buf);
             }
 
@@ -251,7 +247,6 @@ namespace Org.BouncyCastle.Bcpg
             return tag;
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -260,13 +255,6 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(m_in);
-			base.Close();
-		}
-#endif
 
 		/// <summary>
 		/// A stream that overlays our input stream, allowing the user to only read a segment of it.
diff --git a/crypto/src/bcpg/BcpgOutputStream.cs b/crypto/src/bcpg/BcpgOutputStream.cs
index fbce0820e..fbdd75bff 100644
--- a/crypto/src/bcpg/BcpgOutputStream.cs
+++ b/crypto/src/bcpg/BcpgOutputStream.cs
@@ -376,7 +376,6 @@ namespace Org.BouncyCastle.Bcpg
             }
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -387,14 +386,5 @@ namespace Org.BouncyCastle.Bcpg
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-			this.Finish();
-			outStr.Flush();
-            Platform.Dispose(outStr);
-			base.Close();
-        }
-#endif
     }
 }
diff --git a/crypto/src/bzip2/CBZip2OutputStream.cs b/crypto/src/bzip2/CBZip2OutputStream.cs
index 38966efd4..5521dce56 100644
--- a/crypto/src/bzip2/CBZip2OutputStream.cs
+++ b/crypto/src/bzip2/CBZip2OutputStream.cs
@@ -421,39 +421,32 @@ namespace Org.BouncyCastle.Bzip2
 
         bool closed = false;
 
-//        protected void Finalize()
-//        {
-//            Close();
-//        }
-
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+        protected void Detach(bool disposing)
         {
             if (disposing)
             {
-                if (closed)
-                    return;
-
-                Finish();
-                closed = true;
-                Platform.Dispose(this.bsStream);
+                if (!closed)
+                {
+                    Finish();
+                    closed = true;
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
-
-            Finish();
-
-            closed = true;
-            Platform.Dispose(this.bsStream);
 
-            base.Close();
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                if (!closed)
+                {
+                    Finish();
+                    closed = true;
+                    Platform.Dispose(this.bsStream);
+                }
+            }
+            base.Dispose(disposing);
         }
-#endif
 
         public void Finish()
         {
diff --git a/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs b/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
index 4ac2b34c9..d66b0aea9 100644
--- a/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSAuthenticatedDataStreamGenerator.cs
@@ -248,7 +248,6 @@ namespace Org.BouncyCastle.Cms
 				macStream.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -269,25 +268,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-			{
-                Platform.Dispose(macStream);
-
-                // TODO Parent context(s) should really be be closed explicitly
-
-				eiGen.Close();
-
-				// [TODO] auth attributes go here 
-				byte[] macOctets = MacUtilities.DoFinal(mac);
-				authGen.AddObject(new DerOctetString(macOctets));
-				// [TODO] unauth attributes go here
-
-				authGen.Close();
-				cGen.Close();
-                base.Close();
-			}
-#endif
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSCompressedDataStreamGenerator.cs b/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
index 1a9513ce6..9a9c29b01 100644
--- a/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSCompressedDataStreamGenerator.cs
@@ -121,7 +121,6 @@ namespace Org.BouncyCastle.Cms
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -136,19 +135,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-			{
-                Platform.Dispose(_out);
-
-                // TODO Parent context(s) should really be be closed explicitly
-
-                _eiGen.Close();
-				_cGen.Close();
-				_sGen.Close();
-				base.Close();
-			}
-#endif
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs b/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
index 90ecf0748..4a8b57aad 100644
--- a/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSEnvelopedDataStreamGenerator.cs
@@ -246,44 +246,31 @@ namespace Org.BouncyCastle.Cms
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
  				{
-					ImplClose();
-                }
-                base.Dispose(disposing);
-            }
-#else
-			public override void Close()
-			{
-				ImplClose();
-				base.Close();
-			}
-#endif
+                    Platform.Dispose(_out);
 
-			private void ImplClose()
-            {
-				Platform.Dispose(_out);
+                    // TODO Parent context(s) should really be closed explicitly
 
-				// TODO Parent context(s) should really be closed explicitly
+                    _eiGen.Close();
 
-				_eiGen.Close();
+                    if (_outer.unprotectedAttributeGenerator != null)
+                    {
+                        Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(
+                            new Dictionary<CmsAttributeTableParameter, object>());
 
-				if (_outer.unprotectedAttributeGenerator != null)
-				{
-					Asn1.Cms.AttributeTable attrTable = _outer.unprotectedAttributeGenerator.GetAttributes(
-						new Dictionary<CmsAttributeTableParameter, object>());
+                        Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector());
 
-					Asn1Set unprotectedAttrs = new BerSet(attrTable.ToAsn1EncodableVector());
+                        _envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs));
+                    }
 
-					_envGen.AddObject(new DerTaggedObject(false, 1, unprotectedAttrs));
-				}
-
-				_envGen.Close();
-				_cGen.Close();
-			}
+                    _envGen.Close();
+                    _cGen.Close();
+                }
+                base.Dispose(disposing);
+            }
 		}
 	}
 }
diff --git a/crypto/src/cms/CMSSignedDataStreamGenerator.cs b/crypto/src/cms/CMSSignedDataStreamGenerator.cs
index 5587a9d07..69427c246 100644
--- a/crypto/src/cms/CMSSignedDataStreamGenerator.cs
+++ b/crypto/src/cms/CMSSignedDataStreamGenerator.cs
@@ -791,7 +791,6 @@ namespace Org.BouncyCastle.Cms
 				_out.WriteByte(value);
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
                 if (disposing)
@@ -800,13 +799,6 @@ namespace Org.BouncyCastle.Cms
                 }
                 base.Dispose(disposing);
             }
-#else
-			public override void Close()
-            {
-                DoClose();
-				base.Close();
-			}
-#endif
 
             private void DoClose()
             {
diff --git a/crypto/src/cms/CMSTypedStream.cs b/crypto/src/cms/CMSTypedStream.cs
index e99d904b5..d4ca62256 100644
--- a/crypto/src/cms/CMSTypedStream.cs
+++ b/crypto/src/cms/CMSTypedStream.cs
@@ -33,11 +33,7 @@ namespace Org.BouncyCastle.Cms
 			int		bufSize)
 		{
 			_oid = oid;
-#if PORTABLE
-			_in = new FullReaderStream(inStream);
-#else
 			_in = new FullReaderStream(new BufferedStream(inStream, bufSize));
-#endif
 		}
 
 		public string ContentType
@@ -63,10 +59,24 @@ namespace Org.BouncyCastle.Cms
 			{
 			}
 
-			public override int Read(byte[]	buf, int off, int len)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override void CopyTo(Stream destination, int bufferSize)
+            {
+				s.CopyTo(destination, bufferSize);
+            }
+#endif
+
+            public override int Read(byte[]	buf, int off, int len)
 			{
-				return Streams.ReadFully(base.s, buf, off, len);
+				return Streams.ReadFully(s, buf, off, len);
 			}
-		}
-	}
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            public override int Read(Span<byte> buffer)
+            {
+                return Streams.ReadFully(s, buffer);
+            }
+#endif
+        }
+    }
 }
diff --git a/crypto/src/crypto/io/CipherStream.cs b/crypto/src/crypto/io/CipherStream.cs
index 06a3d392c..e59f33679 100644
--- a/crypto/src/crypto/io/CipherStream.cs
+++ b/crypto/src/crypto/io/CipherStream.cs
@@ -1,4 +1,7 @@
 using System;
+#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+using System.Buffers;
+#endif
 using System.Diagnostics;
 using System.IO;
 
@@ -7,44 +10,39 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class CipherStream
+    public sealed class CipherStream
         : Stream
     {
-        internal Stream stream;
-        internal IBufferedCipher inCipher, outCipher;
-        private byte[] mInBuf;
-        private int mInPos;
-        private bool inStreamEnded;
+        private readonly Stream m_stream;
+        private readonly IBufferedCipher m_readCipher, m_writeCipher;
+
+        private byte[] m_readBuf;
+        private int m_readBufPos;
+        private bool m_readEnded;
 
         public CipherStream(Stream stream, IBufferedCipher readCipher, IBufferedCipher writeCipher)
         {
-            this.stream = stream;
+            m_stream = stream;
 
             if (readCipher != null)
             {
-                this.inCipher = readCipher;
-                mInBuf = null;
+                m_readCipher = readCipher;
+                m_readBuf = null;
             }
 
             if (writeCipher != null)
             {
-                this.outCipher = writeCipher;
+                m_writeCipher = writeCipher;
             }
         }
 
-        public IBufferedCipher ReadCipher
-        {
-            get { return inCipher; }
-        }
+        public IBufferedCipher ReadCipher => m_readCipher;
 
-        public IBufferedCipher WriteCipher
-        {
-            get { return outCipher; }
-        }
+        public IBufferedCipher WriteCipher => m_writeCipher;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -54,42 +52,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readCipher == null)
             {
-			    if (outCipher != null)
-			    {
-				    byte[] data = outCipher.DoFinal();
-				    stream.Write(data, 0, data.Length);
-				    stream.Flush();
-			    }
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            if (outCipher != null)
+            else
             {
-                byte[] data = outCipher.DoFinal();
-                stream.Write(data, 0, data.Length);
-                stream.Flush();
+                base.CopyTo(destination, bufferSize);
             }
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            // Note: outCipher.DoFinal is only called during Close()
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -105,41 +87,70 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            if (inCipher == null)
-                return stream.Read(buffer, offset, count);
+            if (m_readCipher == null)
+                return m_stream.Read(buffer, offset, count);
 
             Streams.ValidateBufferArguments(buffer, offset, count);
 
             int num = 0;
             while (num < count)
             {
-                if (mInBuf == null || mInPos >= mInBuf.Length)
+                if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
+                {
+                    if (!FillInBuf())
+                        break;
+                }
+
+                int numToCopy = System.Math.Min(count - num, m_readBuf.Length - m_readBufPos);
+                Array.Copy(m_readBuf, m_readBufPos, buffer, offset + num, numToCopy);
+                m_readBufPos += numToCopy;
+                num += numToCopy;
+            }
+
+            return num;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            if (buffer.IsEmpty)
+                return 0;
+
+            if (m_readCipher == null)
+                return m_stream.Read(buffer);
+
+            int num = 0;
+            while (num < buffer.Length)
+            {
+                if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
                 {
                     if (!FillInBuf())
                         break;
                 }
 
-                int numToCopy = System.Math.Min(count - num, mInBuf.Length - mInPos);
-                Array.Copy(mInBuf, mInPos, buffer, offset + num, numToCopy);
-                mInPos += numToCopy;
+                int numToCopy = System.Math.Min(buffer.Length - num, m_readBuf.Length - m_readBufPos);
+                m_readBuf.AsSpan(m_readBufPos, numToCopy).CopyTo(buffer[num..]);
+
+                m_readBufPos += numToCopy;
                 num += numToCopy;
             }
 
             return num;
         }
+#endif
 
         public override int ReadByte()
         {
-            if (inCipher == null)
-                return stream.ReadByte();
+            if (m_readCipher == null)
+                return m_stream.ReadByte();
 
-            if (mInBuf == null || mInPos >= mInBuf.Length)
+            if (m_readBuf == null || m_readBufPos >= m_readBuf.Length)
             {
                 if (!FillInBuf())
                     return -1;
             }
 
-            return mInBuf[mInPos++];
+            return m_readBuf[m_readBufPos++];
         }
 
         public sealed override long Seek(long offset, SeekOrigin origin)
@@ -154,9 +165,9 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            if (outCipher == null)
+            if (m_writeCipher == null)
             {
-                stream.Write(buffer, offset, count);
+                m_stream.Write(buffer, offset, count);
                 return;
             }
 
@@ -164,69 +175,142 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                byte[] data = outCipher.ProcessBytes(buffer, offset, count);
-                if (data != null)
+#if NETCOREAPP1_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                int outputSize = m_writeCipher.GetUpdateOutputSize(count);
+                byte[] output = outputSize > 0 ? ArrayPool<byte>.Shared.Rent(outputSize) : null;
+                try
+                {
+                    int length = m_writeCipher.ProcessBytes(buffer, offset, count, output, 0);
+                    if (length > 0)
+                    {
+                        m_stream.Write(output, 0, length);
+                    }
+                }
+                finally
+                {
+                    if (output != null)
+                    {
+                        ArrayPool<byte>.Shared.Return(output);
+                    }
+                }
+#else
+                byte[] output = m_writeCipher.ProcessBytes(buffer, offset, count);
+                if (output != null)
+                {
+                    m_stream.Write(output, 0, output.Length);
+                }
+#endif
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (buffer.IsEmpty)
+                return;
+
+            if (m_writeCipher == null)
+            {
+                m_stream.Write(buffer);
+                return;
+            }
+
+            int outputSize = m_writeCipher.GetUpdateOutputSize(buffer.Length);
+            byte[] output = outputSize > 0 ? ArrayPool<byte>.Shared.Rent(outputSize) : null;
+            try
+            {
+                int length = m_writeCipher.ProcessBytes(buffer, Spans.FromNullable(output, 0));
+                if (length > 0)
                 {
-                    stream.Write(data, 0, data.Length);
+                    m_stream.Write(output[..length]);
+                }
+            }
+            finally
+            {
+                if (output != null)
+                {
+                    ArrayPool<byte>.Shared.Return(output);
                 }
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            if (outCipher == null)
+            if (m_writeCipher == null)
             {
-                stream.WriteByte(value);
+                m_stream.WriteByte(value);
                 return;
             }
 
-            byte[] data = outCipher.ProcessByte(value);
+            byte[] data = m_writeCipher.ProcessByte(value);
             if (data != null)
             {
-                stream.Write(data, 0, data.Length);
+                m_stream.Write(data, 0, data.Length);
             }
         }
 
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+			    if (m_writeCipher != null)
+			    {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+                    int outputSize = m_writeCipher.GetOutputSize(0);
+                    Span<byte> output = stackalloc byte[outputSize];
+                    int len = m_writeCipher.DoFinal(output);
+                    m_stream.Write(output[..len]);
+#else
+                    byte[] data = m_writeCipher.DoFinal();
+                    m_stream.Write(data, 0, data.Length);
+#endif
+			    }
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
+
         private bool FillInBuf()
         {
-            if (inStreamEnded)
+            if (m_readEnded)
                 return false;
 
-            mInPos = 0;
+            m_readBufPos = 0;
 
             do
             {
-                mInBuf = ReadAndProcessBlock();
+                m_readBuf = ReadAndProcessBlock();
             }
-            while (!inStreamEnded && mInBuf == null);
+            while (!m_readEnded && m_readBuf == null);
 
-            return mInBuf != null;
+            return m_readBuf != null;
         }
 
         private byte[] ReadAndProcessBlock()
         {
-            int blockSize = inCipher.GetBlockSize();
-            int readSize = (blockSize == 0) ? 256 : blockSize;
+            int blockSize = m_readCipher.GetBlockSize();
+            int readSize = blockSize == 0 ? 256 : blockSize;
 
             byte[] block = new byte[readSize];
             int numRead = 0;
             do
             {
-                int count = stream.Read(block, numRead, block.Length - numRead);
+                int count = m_stream.Read(block, numRead, block.Length - numRead);
                 if (count < 1)
                 {
-                    inStreamEnded = true;
+                    m_readEnded = true;
                     break;
                 }
                 numRead += count;
             }
             while (numRead < block.Length);
 
-            Debug.Assert(inStreamEnded || numRead == block.Length);
+            Debug.Assert(m_readEnded || numRead == block.Length);
 
-            byte[] bytes = inStreamEnded
-                ? inCipher.DoFinal(block, 0, numRead)
-                : inCipher.ProcessBytes(block);
+            byte[] bytes = m_readEnded
+                ? m_readCipher.DoFinal(block, 0, numRead)
+                : m_readCipher.ProcessBytes(block);
 
             if (bytes != null && bytes.Length == 0)
             {
diff --git a/crypto/src/crypto/io/DigestSink.cs b/crypto/src/crypto/io/DigestSink.cs
index c2a168bfe..283bda28b 100644
--- a/crypto/src/crypto/io/DigestSink.cs
+++ b/crypto/src/crypto/io/DigestSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class DigestSink
+    public sealed class DigestSink
         : BaseOutputStream
     {
-        private readonly IDigest mDigest;
+        private readonly IDigest m_digest;
 
         public DigestSink(IDigest digest)
         {
-            this.mDigest = digest;
+            m_digest = digest;
         }
 
-        public virtual IDigest Digest
-        {
-            get { return mDigest; }
-        }
+        public IDigest Digest => m_digest;
 
         public override void Write(byte[] buffer, int offset, int count)
         {
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                mDigest.BlockUpdate(buffer, offset, count);
+                m_digest.BlockUpdate(buffer, offset, count);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_digest.BlockUpdate(buffer);
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            mDigest.Update(value);
+            m_digest.Update(value);
         }
     }
 }
diff --git a/crypto/src/crypto/io/DigestStream.cs b/crypto/src/crypto/io/DigestStream.cs
index 387f42766..ae7fc94de 100644
--- a/crypto/src/crypto/io/DigestStream.cs
+++ b/crypto/src/crypto/io/DigestStream.cs
@@ -5,33 +5,27 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class DigestStream
+    public sealed class DigestStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly IDigest inDigest;
-        protected readonly IDigest outDigest;
+        private readonly Stream m_stream;
+        private readonly IDigest m_readDigest;
+        private readonly IDigest m_writeDigest;
 
         public DigestStream(Stream stream, IDigest readDigest, IDigest writeDigest)
         {
-            this.stream = stream;
-            this.inDigest = readDigest;
-            this.outDigest = writeDigest;
+            m_stream = stream;
+            m_readDigest = readDigest;
+            m_writeDigest = writeDigest;
         }
 
-        public virtual IDigest ReadDigest()
-        {
-            return inDigest;
-        }
+        public IDigest ReadDigest => m_readDigest;
 
-        public virtual IDigest WriteDigest()
-        {
-            return outDigest;
-        }
+        public IDigest WriteDigest => m_writeDigest;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -41,29 +35,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readDigest == null)
             {
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -79,23 +70,37 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            int n = stream.Read(buffer, offset, count);
+            int n = m_stream.Read(buffer, offset, count);
+
+            if (m_readDigest != null && n > 0)
+            {
+                m_readDigest.BlockUpdate(buffer, offset, n);
+            }
+
+            return n;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = m_stream.Read(buffer);
 
-            if (inDigest != null && n > 0)
+            if (m_readDigest != null && n > 0)
             {
-                inDigest.BlockUpdate(buffer, offset, n);
+                m_readDigest.BlockUpdate(buffer[..n]);
             }
 
             return n;
         }
+#endif
 
         public override int ReadByte()
         {
-            int b = stream.ReadByte();
+            int b = m_stream.ReadByte();
 
-            if (inDigest != null && b >= 0)
+            if (m_readDigest != null && b >= 0)
             {
-                inDigest.Update((byte)b);
+                m_readDigest.Update((byte)b);
             }
 
             return b;
@@ -113,23 +118,43 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            stream.Write(buffer, offset, count);
+            m_stream.Write(buffer, offset, count);
 
-            if (outDigest != null && count > 0)
+            if (m_writeDigest != null && count > 0)
             {
-                outDigest.BlockUpdate(buffer, offset, count);
+                m_writeDigest.BlockUpdate(buffer, offset, count);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_stream.Write(buffer);
+
+            if (m_writeDigest != null && !buffer.IsEmpty)
+            {
+                m_writeDigest.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
-            stream.WriteByte(value);
+            m_stream.WriteByte(value);
 
-            if (outDigest != null)
+            if (m_writeDigest != null)
             {
-                outDigest.Update(value);
+                m_writeDigest.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/crypto/io/MacSink.cs b/crypto/src/crypto/io/MacSink.cs
index aa72e9047..cc5d93b37 100644
--- a/crypto/src/crypto/io/MacSink.cs
+++ b/crypto/src/crypto/io/MacSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class MacSink
+    public sealed class MacSink
         : BaseOutputStream
     {
-        private readonly IMac mMac;
+        private readonly IMac m_mac;
 
         public MacSink(IMac mac)
         {
-            this.mMac = mac;
+            m_mac = mac;
         }
 
-        public virtual IMac Mac
-        {
-            get { return mMac; }
-        }
+        public IMac Mac => m_mac;
 
         public override void Write(byte[] buffer, int offset, int count)
         {
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
             if (count > 0)
             {
-                mMac.BlockUpdate(buffer, offset, count);
+                m_mac.BlockUpdate(buffer, offset, count);
+            }
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            if (!buffer.IsEmpty)
+            {
+                m_mac.BlockUpdate(buffer);
             }
         }
+#endif
 
         public override void WriteByte(byte value)
         {
-            mMac.Update(value);
+            m_mac.Update(value);
         }
     }
 }
diff --git a/crypto/src/crypto/io/MacStream.cs b/crypto/src/crypto/io/MacStream.cs
index c56e00d2c..c97614289 100644
--- a/crypto/src/crypto/io/MacStream.cs
+++ b/crypto/src/crypto/io/MacStream.cs
@@ -5,33 +5,27 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class MacStream
+    public sealed class MacStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly IMac inMac;
-        protected readonly IMac outMac;
+        private readonly Stream m_stream;
+        private readonly IMac m_readMac;
+        private readonly IMac m_writeMac;
 
         public MacStream(Stream stream, IMac readMac, IMac writeMac)
         {
-            this.stream = stream;
-            this.inMac = readMac;
-            this.outMac = writeMac;
+            m_stream = stream;
+            m_readMac = readMac;
+            m_writeMac = writeMac;
         }
 
-        public virtual IMac ReadMac()
-        {
-            return inMac;
-        }
+        public IMac ReadMac => m_readMac;
 
-        public virtual IMac WriteMac()
-        {
-            return outMac;
-        }
+        public IMac WriteMac => m_writeMac;
 
         public override bool CanRead
         {
-            get { return stream.CanRead; }
+            get { return m_stream.CanRead; }
         }
 
         public sealed override bool CanSeek
@@ -41,29 +35,26 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override bool CanWrite
         {
-            get { return stream.CanWrite; }
+            get { return m_stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (m_readMac == null)
             {
-                Platform.Dispose(stream);
+                m_stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
         public override void Flush()
         {
-            stream.Flush();
+            m_stream.Flush();
         }
 
         public sealed override long Length
@@ -79,23 +70,37 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override int Read(byte[] buffer, int offset, int count)
         {
-            int n = stream.Read(buffer, offset, count);
+            int n = m_stream.Read(buffer, offset, count);
+
+            if (m_readMac != null && n > 0)
+            {
+                m_readMac.BlockUpdate(buffer, offset, n);
+            }
+
+            return n;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = m_stream.Read(buffer);
 
-            if (inMac != null && n > 0)
+            if (m_readMac != null && n > 0)
             {
-                inMac.BlockUpdate(buffer, offset, n);
+                m_readMac.BlockUpdate(buffer[..n]);
             }
 
             return n;
         }
+#endif
 
         public override int ReadByte()
         {
-            int b = stream.ReadByte();
+            int b = m_stream.ReadByte();
 
-            if (inMac != null && b >= 0)
+            if (m_readMac != null && b >= 0)
             {
-                inMac.Update((byte)b);
+                m_readMac.Update((byte)b);
             }
 
             return b;
@@ -113,23 +118,43 @@ namespace Org.BouncyCastle.Crypto.IO
 
         public override void Write(byte[] buffer, int offset, int count)
         {
-            stream.Write(buffer, offset, count);
+            m_stream.Write(buffer, offset, count);
 
-            if (outMac != null && count > 0)
+            if (m_writeMac != null && count > 0)
             {
-                outMac.BlockUpdate(buffer, offset, count);
+                m_writeMac.BlockUpdate(buffer, offset, count);
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_stream.Write(buffer);
+
+            if (m_writeMac != null && !buffer.IsEmpty)
+            {
+                m_writeMac.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
-            stream.WriteByte(value);
+            m_stream.WriteByte(value);
 
-            if (outMac != null)
+            if (m_writeMac != null)
             {
-                outMac.Update(value);
+                m_writeMac.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(m_stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/crypto/io/SignerSink.cs b/crypto/src/crypto/io/SignerSink.cs
index 3485d3cdc..aaae59966 100644
--- a/crypto/src/crypto/io/SignerSink.cs
+++ b/crypto/src/crypto/io/SignerSink.cs
@@ -4,20 +4,17 @@ using Org.BouncyCastle.Utilities.IO;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class SignerSink
+    public sealed class SignerSink
 		: BaseOutputStream
 	{
-		private readonly ISigner mSigner;
+		private readonly ISigner m_signer;
 
         public SignerSink(ISigner signer)
 		{
-            this.mSigner = signer;
+            m_signer = signer;
 		}
 
-        public virtual ISigner Signer
-        {
-            get { return mSigner; }
-        }
+		public ISigner Signer => m_signer;
 
 		public override void Write(byte[] buffer, int offset, int count)
 		{
@@ -25,13 +22,23 @@ namespace Org.BouncyCastle.Crypto.IO
 
 			if (count > 0)
 			{
-				mSigner.BlockUpdate(buffer, offset, count);
+				m_signer.BlockUpdate(buffer, offset, count);
 			}
 		}
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+		public override void Write(ReadOnlySpan<byte> buffer)
+		{
+			if (!buffer.IsEmpty)
+			{
+				m_signer.BlockUpdate(buffer);
+			}
+		}
+#endif
+
 		public override void WriteByte(byte value)
 		{
-			mSigner.Update(value);
+			m_signer.Update(value);
 		}
 	}
 }
diff --git a/crypto/src/crypto/io/SignerStream.cs b/crypto/src/crypto/io/SignerStream.cs
index d25d7e285..fecc0d309 100644
--- a/crypto/src/crypto/io/SignerStream.cs
+++ b/crypto/src/crypto/io/SignerStream.cs
@@ -5,12 +5,12 @@ using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO
 {
-    public class SignerStream
+    public sealed class SignerStream
         : Stream
     {
-        protected readonly Stream stream;
-        protected readonly ISigner inSigner;
-        protected readonly ISigner outSigner;
+        private readonly Stream stream;
+        private readonly ISigner inSigner;
+        private readonly ISigner outSigner;
 
         public SignerStream(Stream stream, ISigner readSigner, ISigner writeSigner)
         {
@@ -19,15 +19,9 @@ namespace Org.BouncyCastle.Crypto.IO
             this.outSigner = writeSigner;
         }
 
-        public virtual ISigner ReadSigner()
-        {
-            return inSigner;
-        }
+        public ISigner ReadSigner => inSigner;
 
-        public virtual ISigner WriteSigner()
-        {
-            return outSigner;
-        }
+        public ISigner WriteSigner => outSigner;
 
         public override bool CanRead
         {
@@ -44,20 +38,17 @@ namespace Org.BouncyCastle.Crypto.IO
             get { return stream.CanWrite; }
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
         {
-            if (disposing)
+            if (inSigner == null)
             {
-                Platform.Dispose(stream);
+                stream.CopyTo(destination, bufferSize);
+            }
+            else
+            {
+                base.CopyTo(destination, bufferSize);
             }
-            base.Dispose(disposing);
-        }
-#else
-        public override void Close()
-        {
-            Platform.Dispose(stream);
-            base.Close();
         }
 #endif
 
@@ -89,6 +80,20 @@ namespace Org.BouncyCastle.Crypto.IO
             return n;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int n = stream.Read(buffer);
+
+            if (inSigner != null && n > 0)
+            {
+                inSigner.BlockUpdate(buffer[..n]);
+            }
+
+            return n;
+        }
+#endif
+
         public override int ReadByte()
         {
             int b = stream.ReadByte();
@@ -121,6 +126,18 @@ namespace Org.BouncyCastle.Crypto.IO
             }
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            stream.Write(buffer);
+
+            if (outSigner != null && !buffer.IsEmpty)
+            {
+                outSigner.BlockUpdate(buffer);
+            }
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             stream.WriteByte(value);
@@ -130,6 +147,14 @@ namespace Org.BouncyCastle.Crypto.IO
                 outSigner.Update(value);
             }
         }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                Platform.Dispose(stream);
+            }
+            base.Dispose(disposing);
+        }
     }
 }
-
diff --git a/crypto/src/openpgp/PgpCompressedDataGenerator.cs b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
index 88a1070d1..791aac0bf 100644
--- a/crypto/src/openpgp/PgpCompressedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpCompressedDataGenerator.cs
@@ -76,7 +76,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData);
 
-			doOpen();
+			DoOpen();
 
 			return new WrappedGeneratorStream(this, dOut);
 		}
@@ -120,12 +120,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 
 			this.pkOut = new BcpgOutputStream(outStr, PacketTag.CompressedData, buffer);
 
-			doOpen();
+			DoOpen();
 
 			return new WrappedGeneratorStream(this, dOut);
 		}
 
-		private void doOpen()
+		private void DoOpen()
 		{
 			pkOut.WriteByte((byte) algorithm);
 
@@ -173,22 +173,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			{
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
-                if (disposing)
-                {
-				    Finish();
-                    return;
-                }
-                base.Dispose(disposing);
+                Detach(disposing);
             }
-#else
-            public override void Close()
-			{
-				Finish();
-			}
-#endif
 		}
 
 		private class SafeZOutputStream : ZOutputStream
@@ -198,24 +186,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 			{
 			}
 
-#if PORTABLE
             protected override void Dispose(bool disposing)
             {
-                if (disposing)
-                {
-				    Finish();
-				    End();
-                    return;
-                }
-                base.Dispose(disposing);
+				Detach(disposing);
             }
-#else
-            public override void Close()
-			{
-				Finish();
-				End();
-			}
-#endif
 		}
 	}
 }
diff --git a/crypto/src/openpgp/PgpEncryptedData.cs b/crypto/src/openpgp/PgpEncryptedData.cs
index d3220fe86..5cdc0d533 100644
--- a/crypto/src/openpgp/PgpEncryptedData.cs
+++ b/crypto/src/openpgp/PgpEncryptedData.cs
@@ -140,7 +140,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
             //
 			byte[] lookAhead = truncStream.GetLookAhead();
 
-			IDigest hash = dIn.ReadDigest();
+			IDigest hash = dIn.ReadDigest;
 			hash.BlockUpdate(lookAhead, 0, 2);
 			byte[] digest = DigestUtilities.DoFinal(hash);
 
diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
index d16f328d9..f086aaf75 100644
--- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
+++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs
@@ -561,7 +561,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
                     digestOut.Flush();
 
 					// TODO
-					byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest());
+					byte[] dig = DigestUtilities.DoFinal(digestOut.WriteDigest);
 					cOut.Write(dig, 0, dig.Length);
                 }
 
diff --git a/crypto/src/openpgp/WrappedGeneratorStream.cs b/crypto/src/openpgp/WrappedGeneratorStream.cs
index df31d71f0..c54ee0b3b 100644
--- a/crypto/src/openpgp/WrappedGeneratorStream.cs
+++ b/crypto/src/openpgp/WrappedGeneratorStream.cs
@@ -31,11 +31,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp
 				m_generator = null;
 			}
 
-			// Don't dispose the wrapped Stream
-			if (!disposing)
-            {
-				base.Dispose(disposing);
-			}
+			Detach(disposing);
 		}
 	}
 }
diff --git a/crypto/src/tls/ByteQueue.cs b/crypto/src/tls/ByteQueue.cs
index 45bec8be4..b85106528 100644
--- a/crypto/src/tls/ByteQueue.cs
+++ b/crypto/src/tls/ByteQueue.cs
@@ -56,6 +56,9 @@ namespace Org.BouncyCastle.Tls
         /// <param name="len">How many bytes to read from the array.</param>
         public void AddData(byte[] buf, int off, int len)
         {
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            AddData(buf.AsSpan(off, len));
+#else
             if (m_readOnlyBuf)
                 throw new InvalidOperationException("Cannot add data to read-only buffer");
 
@@ -86,8 +89,46 @@ namespace Org.BouncyCastle.Tls
 
             Array.Copy(buf, off, m_databuf, m_skipped + m_available, len);
             m_available += len;
+#endif
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void AddData(ReadOnlySpan<byte> buffer)
+        {
+            if (m_readOnlyBuf)
+                throw new InvalidOperationException("Cannot add data to read-only buffer");
+
+            int len = buffer.Length;
+            if (m_available == 0)
+            {
+                if (len > m_databuf.Length)
+                {
+                    int desiredSize = NextTwoPow(len | 256);
+                    m_databuf = new byte[desiredSize];
+                }
+                m_skipped = 0;
+            }
+            else if ((m_skipped + m_available + len) > m_databuf.Length)
+            {
+                int desiredSize = NextTwoPow(m_available + len);
+                if (desiredSize > m_databuf.Length)
+                {
+                    byte[] tmp = new byte[desiredSize];
+                    Array.Copy(m_databuf, m_skipped, tmp, 0, m_available);
+                    m_databuf = tmp;
+                }
+                else
+                {
+                    Array.Copy(m_databuf, m_skipped, m_databuf, 0, m_available);
+                }
+                m_skipped = 0;
+            }
+
+            buffer.CopyTo(m_databuf.AsSpan(m_skipped + m_available));
+            m_available += len;
+        }
+#endif
+
         /// <returns>The number of bytes which are available in this buffer.</returns>
         public int Available
         {
@@ -124,6 +165,16 @@ namespace Org.BouncyCastle.Tls
             Array.Copy(m_databuf, m_skipped + skip, buf, offset, len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void Read(Span<byte> buffer, int skip)
+        {
+            if ((m_available - skip) < buffer.Length)
+                throw new InvalidOperationException("Not enough data to read");
+
+            m_databuf.AsSpan(m_skipped + skip, buffer.Length).CopyTo(buffer);
+        }
+#endif
+
         /// <summary>Return a <see cref="HandshakeMessageInput"/> over some bytes at the beginning of the data.
         /// </summary>
         /// <param name="length">How many bytes will be readable.</param>
@@ -182,6 +233,14 @@ namespace Org.BouncyCastle.Tls
             RemoveData(skip + len);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public void RemoveData(Span<byte> buffer, int skip)
+        {
+            Read(buffer, skip);
+            RemoveData(skip + buffer.Length);
+        }
+#endif
+
         public byte[] RemoveData(int len, int skip)
         {
             byte[] buf = new byte[len];
diff --git a/crypto/src/tls/ByteQueueInputStream.cs b/crypto/src/tls/ByteQueueInputStream.cs
index 0b15071ad..ab26faa98 100644
--- a/crypto/src/tls/ByteQueueInputStream.cs
+++ b/crypto/src/tls/ByteQueueInputStream.cs
@@ -40,6 +40,15 @@ namespace Org.BouncyCastle.Tls
             return bytesToRead;
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int bytesToRead = System.Math.Min(m_buffer.Available, buffer.Length);
+            m_buffer.RemoveData(buffer[..bytesToRead], 0);
+            return bytesToRead;
+        }
+#endif
+
         public override int ReadByte()
         {
             if (m_buffer.Available == 0)
@@ -59,16 +68,5 @@ namespace Org.BouncyCastle.Tls
         {
             get { return m_buffer.Available; }
         }
-
-#if PORTABLE
-        //protected override void Dispose(bool disposing)
-        //{
-        //    base.Dispose(disposing);
-        //}
-#else
-        public override void Close()
-        {
-        }
-#endif
     }
 }
diff --git a/crypto/src/tls/ByteQueueOutputStream.cs b/crypto/src/tls/ByteQueueOutputStream.cs
index 441a3773a..ecb1f865d 100644
--- a/crypto/src/tls/ByteQueueOutputStream.cs
+++ b/crypto/src/tls/ByteQueueOutputStream.cs
@@ -27,6 +27,13 @@ namespace Org.BouncyCastle.Tls
             m_buffer.AddData(buffer, offset, count);
         }
 
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            m_buffer.AddData(buffer);
+        }
+#endif
+
         public override void WriteByte(byte value)
         {
             m_buffer.AddData(new byte[]{ value }, 0, 1);
diff --git a/crypto/src/tls/TlsStream.cs b/crypto/src/tls/TlsStream.cs
index f3dea1574..01b990799 100644
--- a/crypto/src/tls/TlsStream.cs
+++ b/crypto/src/tls/TlsStream.cs
@@ -28,7 +28,6 @@ namespace Org.BouncyCastle.Tls
             get { return true; }
         }
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -37,13 +36,6 @@ namespace Org.BouncyCastle.Tls
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            m_handler.Close();
-            base.Close();
-        }
-#endif
 
         public override void Flush()
         {
@@ -69,7 +61,7 @@ namespace Org.BouncyCastle.Tls
         public override int ReadByte()
         {
             byte[] buf = new byte[1];
-            int ret = Read(buf, 0, 1);
+            int ret = m_handler.ReadApplicationData(buf, 0, 1);
             return ret <= 0 ? -1 : buf[0];
         }
 
@@ -90,7 +82,7 @@ namespace Org.BouncyCastle.Tls
 
         public override void WriteByte(byte value)
         {
-            Write(new byte[]{ value }, 0, 1);
+            m_handler.WriteApplicationData(new byte[]{ value }, 0, 1);
         }
     }
 }
diff --git a/crypto/src/util/io/BaseOutputStream.cs b/crypto/src/util/io/BaseOutputStream.cs
index d9a5b92d6..dad9b19a4 100644
--- a/crypto/src/util/io/BaseOutputStream.cs
+++ b/crypto/src/util/io/BaseOutputStream.cs
@@ -10,6 +10,9 @@ namespace Org.BouncyCastle.Utilities.IO
         public sealed override bool CanSeek { get { return false; } }
         public sealed override bool CanWrite { get { return true; } }
 
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize) { throw new NotSupportedException(); }
+#endif
         public override void Flush() {}
         public sealed override long Length { get { throw new NotSupportedException(); } }
         public sealed override long Position
@@ -35,10 +38,5 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             Write(buffer, 0, buffer.Length);
         }
-
-        public override void WriteByte(byte value)
-        {
-            Write(new byte[]{ value }, 0, 1);
-        }
     }
 }
diff --git a/crypto/src/util/io/FilterStream.cs b/crypto/src/util/io/FilterStream.cs
index 0db82ec88..d9bcbb8ef 100644
--- a/crypto/src/util/io/FilterStream.cs
+++ b/crypto/src/util/io/FilterStream.cs
@@ -15,15 +15,6 @@ namespace Org.BouncyCastle.Utilities.IO
 
             this.s = s;
         }
-        protected override void Dispose(bool disposing)
-        {
-            if (disposing)
-            {
-                s.Dispose();
-            }
-
-            base.Dispose(disposing);
-        }
         public override bool CanRead
         {
             get { return s.CanRead; }
@@ -36,6 +27,10 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             get { return s.CanWrite; }
         }
+        public override void Flush()
+        {
+            s.Flush();
+        }
         public override long Length
         {
             get { return s.Length; }
@@ -45,9 +40,13 @@ namespace Org.BouncyCastle.Utilities.IO
             get { return s.Position; }
             set { s.Position = value; }
         }
-        public override void Flush()
+        public override int Read(byte[] buffer, int offset, int count)
         {
-            s.Flush();
+            return s.Read(buffer, offset, count);
+        }
+        public override int ReadByte()
+        {
+            return s.ReadByte();
         }
         public override long Seek(long offset, SeekOrigin origin)
         {
@@ -57,14 +56,6 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             s.SetLength(value);
         }
-        public override int Read(byte[] buffer, int offset, int count)
-        {
-            return s.Read(buffer, offset, count);
-        }
-        public override int ReadByte()
-        {
-            return s.ReadByte();
-        }
         public override void Write(byte[] buffer, int offset, int count)
         {
             s.Write(buffer, offset, count);
@@ -73,5 +64,18 @@ namespace Org.BouncyCastle.Utilities.IO
         {
             s.WriteByte(value);
         }
+        protected void Detach(bool disposing)
+        {
+            base.Dispose(disposing);
+        }
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                s.Dispose();
+            }
+
+            base.Dispose(disposing);
+        }
     }
 }
diff --git a/crypto/src/util/io/LimitedInputStream.cs b/crypto/src/util/io/LimitedInputStream.cs
new file mode 100644
index 000000000..d6616eff5
--- /dev/null
+++ b/crypto/src/util/io/LimitedInputStream.cs
@@ -0,0 +1,56 @@
+using Org.BouncyCastle.Utilities.Zlib;
+using System;
+using System.IO;
+
+namespace Org.BouncyCastle.Utilities.IO
+{
+    internal class LimitedInputStream
+        : BaseInputStream
+    {
+        private readonly Stream m_stream;
+        private long m_limit;
+
+        internal LimitedInputStream(Stream stream, long limit)
+        {
+            this.m_stream = stream;
+            this.m_limit = limit;
+        }
+
+        internal long CurrentLimit => m_limit;
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            int numRead = m_stream.Read(buffer, offset, count);
+            if (numRead > 0)
+            {
+                if ((m_limit -= numRead) < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return numRead;
+        }
+
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int numRead = m_stream.Read(buffer);
+            if (numRead > 0)
+            {
+                if ((m_limit -= numRead) < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return numRead;
+        }
+#endif
+
+        public override int ReadByte()
+        {
+            int b = m_stream.ReadByte();
+            if (b >= 0)
+            {
+                if (--m_limit < 0)
+                    throw new StreamOverflowException("Data Overflow");
+            }
+            return b;
+        }
+    }
+}
diff --git a/crypto/src/util/io/PushbackStream.cs b/crypto/src/util/io/PushbackStream.cs
index 2ceb64361..15ba70501 100644
--- a/crypto/src/util/io/PushbackStream.cs
+++ b/crypto/src/util/io/PushbackStream.cs
@@ -13,7 +13,20 @@ namespace Org.BouncyCastle.Utilities.IO
 		{
 		}
 
-		public override int Read(byte[] buffer, int offset, int count)
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void CopyTo(Stream destination, int bufferSize)
+        {
+			if (m_buf != -1)
+			{
+				destination.WriteByte((byte)m_buf);
+                m_buf = -1;
+            }
+
+            s.CopyTo(destination, bufferSize);
+        }
+#endif
+
+        public override int Read(byte[] buffer, int offset, int count)
 		{
 			Streams.ValidateBufferArguments(buffer, offset, count);
 
@@ -27,10 +40,27 @@ namespace Org.BouncyCastle.Utilities.IO
 				return 1;
 			}
 
-			return base.Read(buffer, offset, count);
+			return s.Read(buffer, offset, count);
 		}
 
-		public override int ReadByte()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+			if (m_buf != -1)
+			{
+                if (buffer.IsEmpty)
+                    return 0;
+
+                buffer[0] = (byte)m_buf;
+                m_buf = -1;
+                return 1;
+            }
+
+            return s.Read(buffer);
+        }
+#endif
+
+        public override int ReadByte()
 		{
 			if (m_buf != -1)
 			{
diff --git a/crypto/src/util/io/Streams.cs b/crypto/src/util/io/Streams.cs
index e1da47fcd..c23332909 100644
--- a/crypto/src/util/io/Streams.cs
+++ b/crypto/src/util/io/Streams.cs
@@ -13,10 +13,7 @@ namespace Org.BouncyCastle.Utilities.IO
 
 		public static void Drain(Stream inStr)
 		{
-			byte[] bs = new byte[BufferSize];
-			while (inStr.Read(bs, 0, bs.Length) > 0)
-			{
-			}
+			inStr.CopyTo(Stream.Null, BufferSize);
 		}
 
         /// <summary>Write the full contents of inStr to the destination stream outStr.</summary>
@@ -25,7 +22,7 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException">In case of IO failure.</exception>
         public static void PipeAll(Stream inStr, Stream outStr)
 		{
-            PipeAll(inStr, outStr, BufferSize);
+			inStr.CopyTo(outStr, BufferSize);
         }
 
         /// <summary>Write the full contents of inStr to the destination stream outStr.</summary>
@@ -35,12 +32,7 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException">In case of IO failure.</exception>
         public static void PipeAll(Stream inStr, Stream outStr, int bufferSize)
         {
-            byte[] bs = new byte[bufferSize];
-            int numRead;
-			while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0)
-			{
-				outStr.Write(bs, 0, numRead);
-			}
+            inStr.CopyTo(outStr, bufferSize);
 		}
 
 		/// <summary>
@@ -60,17 +52,9 @@ namespace Org.BouncyCastle.Utilities.IO
 		/// <exception cref="IOException"></exception>
 		public static long PipeAllLimited(Stream inStr, long limit, Stream outStr)
 		{
-			byte[] bs = new byte[BufferSize];
-			long total = 0;
-			int numRead;
-			while ((numRead = inStr.Read(bs, 0, bs.Length)) > 0)
-			{
-                if ((limit - total) < numRead)
-					throw new StreamOverflowException("Data Overflow");
-                total += numRead;
-                outStr.Write(bs, 0, numRead);
-			}
-			return total;
+			var limited = new LimitedInputStream(inStr, limit);
+            limited.CopyTo(outStr, BufferSize);
+			return limit - limited.CurrentLimit;
 		}
 
 		public static byte[] ReadAll(Stream inStr)
@@ -105,7 +89,22 @@ namespace Org.BouncyCastle.Utilities.IO
 			return totalRead;
 		}
 
-		public static void ValidateBufferArguments(byte[] buffer, int offset, int count)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public static int ReadFully(Stream inStr, Span<byte> buffer)
+        {
+            int totalRead = 0;
+            while (totalRead < buffer.Length)
+            {
+                int numRead = inStr.Read(buffer[totalRead..]);
+                if (numRead < 1)
+                    break;
+                totalRead += numRead;
+            }
+            return totalRead;
+        }
+#endif
+
+        public static void ValidateBufferArguments(byte[] buffer, int offset, int count)
         {
 			if (buffer == null)
 				throw new ArgumentNullException("buffer");
@@ -120,6 +119,14 @@ namespace Org.BouncyCastle.Utilities.IO
         /// <exception cref="IOException"></exception>
         public static int WriteBufTo(MemoryStream buf, byte[] output, int offset)
         {
+#if NETCOREAPP2_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+            if (buf.TryGetBuffer(out var buffer))
+            {
+				buffer.CopyTo(output, offset);
+				return buffer.Count;
+            }
+#endif
+
 			int size = Convert.ToInt32(buf.Length);
             buf.WriteTo(new MemoryStream(output, offset, size));
             return size;
diff --git a/crypto/src/util/io/TeeInputStream.cs b/crypto/src/util/io/TeeInputStream.cs
index 73ea8fed0..3d45bb4f1 100644
--- a/crypto/src/util/io/TeeInputStream.cs
+++ b/crypto/src/util/io/TeeInputStream.cs
@@ -18,7 +18,6 @@ namespace Org.BouncyCastle.Utilities.IO
 			this.tee = tee;
 		}
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -28,14 +27,6 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(input);
-            Platform.Dispose(tee);
-            base.Close();
-		}
-#endif
 
         public override int Read(byte[] buffer, int offset, int count)
 		{
@@ -49,7 +40,21 @@ namespace Org.BouncyCastle.Utilities.IO
 			return i;
 		}
 
-		public override int ReadByte()
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override int Read(Span<byte> buffer)
+        {
+            int i = input.Read(buffer);
+
+            if (i > 0)
+            {
+				tee.Write(buffer[..i]);
+            }
+
+            return i;
+        }
+#endif
+
+        public override int ReadByte()
 		{
 			int i = input.ReadByte();
 
diff --git a/crypto/src/util/io/TeeOutputStream.cs b/crypto/src/util/io/TeeOutputStream.cs
index 5f447b18b..fc213ae55 100644
--- a/crypto/src/util/io/TeeOutputStream.cs
+++ b/crypto/src/util/io/TeeOutputStream.cs
@@ -18,7 +18,6 @@ namespace Org.BouncyCastle.Utilities.IO
 			this.tee = tee;
 		}
 
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
@@ -28,14 +27,6 @@ namespace Org.BouncyCastle.Utilities.IO
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-		{
-            Platform.Dispose(output);
-            Platform.Dispose(tee);
-            base.Close();
-		}
-#endif
 
         public override void Write(byte[] buffer, int offset, int count)
 		{
@@ -43,7 +34,15 @@ namespace Org.BouncyCastle.Utilities.IO
 			tee.Write(buffer, offset, count);
 		}
 
-		public override void WriteByte(byte value)
+#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER
+        public override void Write(ReadOnlySpan<byte> buffer)
+        {
+            output.Write(buffer);
+            tee.Write(buffer);
+        }
+#endif
+
+        public override void WriteByte(byte value)
 		{
 			output.WriteByte(value);
 			tee.WriteByte(value);
diff --git a/crypto/src/util/zlib/ZInputStream.cs b/crypto/src/util/zlib/ZInputStream.cs
index 3e6fcc1be..0433a0182 100644
--- a/crypto/src/util/zlib/ZInputStream.cs
+++ b/crypto/src/util/zlib/ZInputStream.cs
@@ -116,34 +116,18 @@ namespace Org.BouncyCastle.Utilities.Zlib
             this.z.avail_in = 0;
         }
 
-        /*public int available() throws IOException {
-		return inf.finished() ? 0 : 1;
-		}*/
-
-#if PORTABLE
         protected override void Dispose(bool disposing)
         {
             if (disposing)
             {
-			    if (closed)
-                    return;
-
-                closed = true;
-                Platform.Dispose(input);
+			    if (!closed)
+                {
+                    closed = true;
+                    Platform.Dispose(input);
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
-
-            closed = true;
-            Platform.Dispose(input);
-            base.Close();
-        }
-#endif
 
         public virtual int FlushMode
         {
diff --git a/crypto/src/util/zlib/ZOutputStream.cs b/crypto/src/util/zlib/ZOutputStream.cs
index dcb93f97b..301516e57 100644
--- a/crypto/src/util/zlib/ZOutputStream.cs
+++ b/crypto/src/util/zlib/ZOutputStream.cs
@@ -109,49 +109,61 @@ namespace Org.BouncyCastle.Utilities.Zlib
             this.z.deflateInit(level, nowrap);
         }
 
-#if PORTABLE
-        protected override void Dispose(bool disposing)
+        protected void Detach(bool disposing)
         {
             if (disposing)
             {
-			    if (closed)
-				    return;
-
-                DoClose();
+                if (!closed)
+                {
+                    try
+                    {
+                        try
+                        {
+                            Finish();
+                        }
+                        catch (IOException)
+                        {
+                            // Ignore
+                        }
+                    }
+                    finally
+                    {
+                        this.closed = true;
+                        End();
+                        output = null;
+                    }
+                }
             }
             base.Dispose(disposing);
         }
-#else
-        public override void Close()
-        {
-            if (closed)
-                return;
-
-            DoClose();
-            base.Close();
-        }
-#endif
 
-        private void DoClose()
+        protected override void Dispose(bool disposing)
         {
-            try
+            if (disposing)
             {
-                try
-                {
-                    Finish();
-                }
-                catch (IOException)
+			    if (!closed)
                 {
-                    // Ignore
+                    try
+                    {
+                        try
+                        {
+                            Finish();
+                        }
+                        catch (IOException)
+                        {
+                            // Ignore
+                        }
+                    }
+                    finally
+                    {
+                        this.closed = true;
+                        End();
+                        Platform.Dispose(output);
+                        output = null;
+                    }
                 }
             }
-            finally
-            {
-                this.closed = true;
-                End();
-                Platform.Dispose(output);
-                output = null;
-            }
+            base.Dispose(disposing);
         }
 
         public virtual void End()
diff --git a/crypto/test/src/crypto/io/test/CipherStreamTest.cs b/crypto/test/src/crypto/io/test/CipherStreamTest.cs
index 44462418c..799b44521 100644
--- a/crypto/test/src/crypto/io/test/CipherStreamTest.cs
+++ b/crypto/test/src/crypto/io/test/CipherStreamTest.cs
@@ -1,13 +1,10 @@
-using System;
 using System.IO;
 using System.Text;
 
 using NUnit.Framework;
 
-using Org.BouncyCastle.Crypto.Engines;
 using Org.BouncyCastle.Crypto.Modes;
 using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Utilities;
 
 namespace Org.BouncyCastle.Crypto.IO.Tests
 {
@@ -20,9 +17,9 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 		public void TestEncryptDecryptA()
 		{
 			byte[] dataBytes = Encoding.ASCII.GetBytes(DATA);
-			byte[] encryptedDataBytes = encryptOnWrite(dataBytes);
+			byte[] encryptedDataBytes = EncryptOnWrite(dataBytes);
 
-			byte[] decryptedDataBytes = decryptOnRead(encryptedDataBytes);
+			byte[] decryptedDataBytes = DecryptOnRead(encryptedDataBytes);
 			string decryptedData = Encoding.ASCII.GetString(decryptedDataBytes, 0, decryptedDataBytes.Length);
 			Assert.AreEqual(DATA, decryptedData);
 		}
@@ -31,9 +28,9 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 		public void TestEncryptDecryptB()
 		{
 			byte[] dataBytes = Encoding.ASCII.GetBytes(DATA);
-			byte[] encryptedDataBytes = encryptOnRead(dataBytes);
+			byte[] encryptedDataBytes = EncryptOnRead(dataBytes);
 
-			byte[] decryptedDataBytes = decryptOnWrite(encryptedDataBytes);
+			byte[] decryptedDataBytes = DecryptOnWrite(encryptedDataBytes);
 			string decryptedData = Encoding.ASCII.GetString(decryptedDataBytes, 0, decryptedDataBytes.Length);
 			Assert.AreEqual(DATA, decryptedData);
 		}
@@ -42,9 +39,9 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 		public void TestEncryptDecryptC()
 		{
 			byte[] dataBytes = Encoding.ASCII.GetBytes(DATA);
-			byte[] encryptedDataBytes = encryptOnWrite(dataBytes);
+			byte[] encryptedDataBytes = EncryptOnWrite(dataBytes);
 
-			byte[] decryptedDataBytes = decryptOnWrite(encryptedDataBytes);
+			byte[] decryptedDataBytes = DecryptOnWrite(encryptedDataBytes);
 			string decryptedData = Encoding.ASCII.GetString(decryptedDataBytes, 0, decryptedDataBytes.Length);
 			Assert.AreEqual(DATA, decryptedData);
 		}
@@ -53,17 +50,17 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 		public void TestEncryptDecryptD()
 		{
 			byte[] dataBytes = Encoding.ASCII.GetBytes(DATA);
-			byte[] encryptedDataBytes = encryptOnRead(dataBytes);
+			byte[] encryptedDataBytes = EncryptOnRead(dataBytes);
 
-			byte[] decryptedDataBytes = decryptOnRead(encryptedDataBytes);
+			byte[] decryptedDataBytes = DecryptOnRead(encryptedDataBytes);
 			string decryptedData = Encoding.ASCII.GetString(decryptedDataBytes, 0, decryptedDataBytes.Length);
 			Assert.AreEqual(DATA, decryptedData);
 		}
 
-		private byte[] encryptOnWrite(byte[] dataBytes)
+		private byte[] EncryptOnWrite(byte[] dataBytes)
 		{
 			MemoryStream encryptedDataStream = new MemoryStream();
-			IBufferedCipher outCipher = createCipher(true);
+			IBufferedCipher outCipher = CreateCipher(true);
 			CipherStream outCipherStream = new CipherStream(encryptedDataStream, null, outCipher);
 			outCipherStream.Write(dataBytes, 0, dataBytes.Length);
 			Assert.AreEqual(0L, encryptedDataStream.Position % outCipher.GetBlockSize());
@@ -75,11 +72,11 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 			return encryptedDataBytes;
 		}
 
-		private byte[] encryptOnRead(byte[] dataBytes)
+		private byte[] EncryptOnRead(byte[] dataBytes)
 		{
 			MemoryStream dataStream = new MemoryStream(dataBytes, false);
 			MemoryStream encryptedDataStream = new MemoryStream();
-			IBufferedCipher inCipher = createCipher(true);
+			IBufferedCipher inCipher = CreateCipher(true);
 			CipherStream inCipherStream = new CipherStream(dataStream, inCipher, null);
 
 			int ch;
@@ -97,11 +94,11 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 			return encryptedDataBytes;
 		}
 
-		private byte[] decryptOnRead(byte[] encryptedDataBytes)
+		private byte[] DecryptOnRead(byte[] encryptedDataBytes)
 		{
 			MemoryStream encryptedDataStream = new MemoryStream(encryptedDataBytes, false);
 			MemoryStream dataStream = new MemoryStream();
-			IBufferedCipher inCipher = createCipher(false);
+			IBufferedCipher inCipher = CreateCipher(false);
 			CipherStream inCipherStream = new CipherStream(encryptedDataStream, inCipher, null);
 
 			int ch;
@@ -119,11 +116,11 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 			return dataBytes;
 		}
 
-		private byte[] decryptOnWrite(byte[] encryptedDataBytes)
+		private byte[] DecryptOnWrite(byte[] encryptedDataBytes)
 		{
 			MemoryStream encryptedDataStream = new MemoryStream(encryptedDataBytes, false);
 			MemoryStream dataStream = new MemoryStream();
-			IBufferedCipher outCipher = createCipher(false);
+			IBufferedCipher outCipher = CreateCipher(false);
 			CipherStream outCipherStream = new CipherStream(dataStream, null, outCipher);
 
 			int ch;
@@ -141,7 +138,7 @@ namespace Org.BouncyCastle.Crypto.IO.Tests
 			return dataBytes;
 		}
 
-		private IBufferedCipher createCipher(bool forEncryption)
+		private IBufferedCipher CreateCipher(bool forEncryption)
 		{
 //			IBufferedCipher cipher = CipherUtilities.GetCipher("AES/CFB/NoPadding");