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