From 51747c526fc7b1bf0aa508da25e624e7add97104 Mon Sep 17 00:00:00 2001 From: Rob Hague Date: Sat, 18 Apr 2026 11:16:31 +0100 Subject: [PATCH] Encrypt packets in-place Support in-place encryption in the cipher types, then use it on the plaintext packets instead of allocating a new array each time. Removes 2 of 4 bytes allocated for each byte uploaded over SFTP. For AES-CTR, supporting in-place encryption in this case means adding a persistent buffer for the keystream and encrypting in chunks. The performance difference is ~1-2% i.e. marginal versus one-shotting it. The variance is similar also for different choices of buffer size (here 4096 is used). --- src/Renci.SshNet/Common/Extensions.cs | 26 + src/Renci.SshNet/Messages/Message.cs | 34 +- .../Security/Cryptography/Cipher.cs | 20 + .../Cryptography/Ciphers/AesCipher.BclImpl.cs | 35 +- .../Cryptography/Ciphers/AesCipher.CtrImpl.cs | 81 +- .../Cryptography/Ciphers/AesCipher.cs | 7 + .../Cryptography/Ciphers/AesGcmCipher.cs | 31 +- .../Ciphers/ChaCha20Poly1305Cipher.cs | 40 +- src/Renci.SshNet/Session.cs | 88 ++- .../Ciphers/AesCipherBenchmarks.cs | 4 +- .../Ciphers/AesCipherTest.Gen.cs.txt | 14 + .../Cryptography/Ciphers/AesCipherTest.cs | 700 +++++++++++++++++- 12 files changed, 973 insertions(+), 107 deletions(-) diff --git a/src/Renci.SshNet/Common/Extensions.cs b/src/Renci.SshNet/Common/Extensions.cs index 1bba08de2..34b9d3e7a 100644 --- a/src/Renci.SshNet/Common/Extensions.cs +++ b/src/Renci.SshNet/Common/Extensions.cs @@ -11,6 +11,7 @@ using System.Net.Sockets; using System.Numerics; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using System.Threading; using Renci.SshNet.Messages; @@ -434,5 +435,30 @@ internal bool IsCompletedSuccessfully } } #endif + public static bool TryComputeHash( + this HashAlgorithm hashAlgorithm, + byte[] buffer, + int offset, + int count, + Span destination, + out int bytesWritten) + { +#if NET + return hashAlgorithm.TryComputeHash(buffer.AsSpan(offset, count), destination, out bytesWritten); +#else + if (destination.Length < hashAlgorithm.HashSize / 8) + { + bytesWritten = 0; + return false; + } + + var hash = hashAlgorithm.ComputeHash(buffer, offset, count); + + hash.CopyTo(destination); + + bytesWritten = hash.Length; + return true; +#endif + } } } diff --git a/src/Renci.SshNet/Messages/Message.cs b/src/Renci.SshNet/Messages/Message.cs index 96815378c..daf77a65a 100644 --- a/src/Renci.SshNet/Messages/Message.cs +++ b/src/Renci.SshNet/Messages/Message.cs @@ -1,6 +1,7 @@ -using System.IO; -using System.Security.Cryptography; +#nullable enable +using System.IO; +using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Compression; @@ -37,7 +38,8 @@ protected override void WriteBytes(SshDataStream stream) base.WriteBytes(stream); } - internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool excludePacketLengthFieldWhenPadding = false) + /// [4 bytes] || packet_len || padding_len || payload || padding || [macLength bytes]. + internal byte[] GetPacket(byte paddingMultiplier, Compressor? compressor, bool excludePacketLengthFieldWhenPadding = false, int macLength = 0) { const int outboundPacketSequenceSize = 4; @@ -82,10 +84,6 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool ex // padding length calculation var paddingLength = GetPaddingLength(paddingMultiplier, excludePacketLengthFieldWhenPadding ? packetLength - 4 : packetLength); - // add padding bytes - var paddingBytes = RandomNumberGenerator.GetBytes(paddingLength); - sshDataStream.Write(paddingBytes, 0, paddingLength); - var packetDataLength = GetPacketDataLength(messageLength, paddingLength); // skip bytes for outbound packet sequence @@ -97,7 +95,16 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool ex // add packet padding length sshDataStream.WriteByte(paddingLength); - return sshDataStream.ToArray(); + _ = sshDataStream.Seek(0, SeekOrigin.End); + + sshDataStream.SetLength(sshDataStream.Length + paddingLength + macLength); + + var buffer = sshDataStream.ToArray(); + + // add padding bytes + CryptoAbstraction.Randomizer.GetBytes(buffer, (int)sshDataStream.Position, paddingLength); + + return buffer; } } else @@ -112,7 +119,7 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool ex var packetDataLength = GetPacketDataLength(messageLength, paddingLength); // lets construct an SSH data stream of the exact size required - using (var sshDataStream = new SshDataStream(packetLength + paddingLength + outboundPacketSequenceSize)) + using (var sshDataStream = new SshDataStream(packetLength + paddingLength + outboundPacketSequenceSize + macLength)) { // skip bytes for outbound packet sequenceSize _ = sshDataStream.Seek(outboundPacketSequenceSize, SeekOrigin.Begin); @@ -126,11 +133,14 @@ internal byte[] GetPacket(byte paddingMultiplier, Compressor compressor, bool ex // add message payload WriteBytes(sshDataStream); + sshDataStream.SetLength(sshDataStream.Length + paddingLength + macLength); + + var buffer = sshDataStream.ToArray(); + // add padding bytes - var paddingBytes = RandomNumberGenerator.GetBytes(paddingLength); - sshDataStream.Write(paddingBytes, 0, paddingLength); + CryptoAbstraction.Randomizer.GetBytes(buffer, (int)sshDataStream.Position, paddingLength); - return sshDataStream.ToArray(); + return buffer; } } } diff --git a/src/Renci.SshNet/Security/Cryptography/Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Cipher.cs index 872c2131d..dd3a0a099 100644 --- a/src/Renci.SshNet/Security/Cryptography/Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Cipher.cs @@ -54,6 +54,26 @@ public byte[] Encrypt(byte[] input) /// public abstract byte[] Encrypt(byte[] input, int offset, int length); + /// + /// Encrypts the specified input into a given buffer. + /// + /// The input. + /// The zero-based offset in at which to begin encrypting. + /// The number of bytes to encrypt from . + /// The output buffer to write to. + /// The zero-based offset in at which to write encrypted output. + /// + /// The number of bytes written to . + /// + public virtual int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + var ciphertext = Encrypt(input, offset, length); + + ciphertext.AsSpan().CopyTo(output.AsSpan(outputOffset)); + + return ciphertext.Length; + } + /// /// Decrypts the specified input. /// diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs index 6b639dcbc..e46fc7aa4 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.BclImpl.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Diagnostics; using System.Security.Cryptography; using Renci.SshNet.Common; @@ -44,6 +45,13 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return Transform(_encryptor, input, offset, length, output: null, 0, out _); } + public override int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + _ = Transform(_encryptor, input, offset, length, output, outputOffset, out var bytesWritten); + + return bytesWritten; + } + public override byte[] Decrypt(byte[] input, int offset, int length) { return Transform(_decryptor, input, offset, length, output: null, 0, out _); @@ -80,6 +88,8 @@ private byte[] Transform(ICryptoTransform transform, byte[] input, int offset, i // encrypted data in all packets are considered a single data // stream i.e. we do not want to reset the state between calls to Decrypt. + byte[]? tmp = null; + var paddingLength = 0; if (length % BlockSize > 0) { @@ -89,34 +99,29 @@ private byte[] Transform(ICryptoTransform transform, byte[] input, int offset, i // See https://github.com/dotnet/runtime/blob/e7d837da5b1aacd9325a8b8f2214cfaf4d3f0ff6/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/SymmetricPadding.cs#L20-L21 paddingLength = BlockSize - (length % BlockSize); - var tmp = new byte[length + paddingLength]; + tmp = new byte[length + paddingLength]; input.AsSpan(offset, length).CopyTo(tmp); - - input = tmp; - offset = 0; - length = tmp.Length; } } - if (output is null) - { - output = new byte[length]; - - bytesWritten = transform.TransformBlock(input, offset, length, output, outputOffset); + output ??= new byte[length]; - bytesWritten -= paddingLength; + if (tmp is not null) + { + bytesWritten = transform.TransformBlock(tmp, 0, tmp.Length, tmp, 0); - // Manually unpad the output. - Array.Resize(ref output, bytesWritten); + tmp.AsSpan(0, length).CopyTo(output.AsSpan(outputOffset)); } else { bytesWritten = transform.TransformBlock(input, offset, length, output, outputOffset); - - bytesWritten -= paddingLength; } + bytesWritten -= paddingLength; + + Debug.Assert(bytesWritten == length); + return output; } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs index 89f865079..d3dfbf044 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.CtrImpl.cs @@ -11,6 +11,8 @@ public partial class AesCipher { private sealed class CtrImpl : BlockCipher, IDisposable { + private const int KeystreamBufferLength = 4096; + private readonly Aes _aes; private readonly ICryptoTransform _encryptor; @@ -18,6 +20,8 @@ private sealed class CtrImpl : BlockCipher, IDisposable private ulong _ivUpper; // The upper 64 bits of the IV private ulong _ivLower; // The lower 64 bits of the IV + private byte[]? _keystreamBuffer; + public CtrImpl( byte[] key, byte[] iv) @@ -39,6 +43,11 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return Decrypt(input, offset, length); } + public override int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + return Decrypt(input, offset, length, output, outputOffset); + } + public override byte[] Decrypt(byte[] input, int offset, int length) { ArgumentNullException.ThrowIfNull(input); @@ -84,23 +93,61 @@ private byte[] CTREncryptDecrypt(byte[] data, int offset, int length, byte[]? ou Debug.Assert(blockSizedLength % BlockSize == 0); - if (output is null) + byte[] keystream; + int keystreamOffset; + int chunkSize; + + if (data == output && offset == outputOffset) { - output = new byte[blockSizedLength]; - outputOffset = 0; + keystream = _keystreamBuffer ??= new byte[KeystreamBufferLength]; + keystreamOffset = 0; + chunkSize = KeystreamBufferLength; } - else if (data.AsSpan(offset, length).Overlaps(output.AsSpan(outputOffset, blockSizedLength))) + else { - throw new ArgumentException("Input and output buffers must not overlap"); + if (output is null) + { + output = new byte[blockSizedLength]; + outputOffset = 0; + } + else if (data.AsSpan(offset, length).Overlaps(output.AsSpan(outputOffset, blockSizedLength))) + { + throw new ArgumentException("Input and output buffers must not overlap (except when identical)."); + } + + keystream = output; + keystreamOffset = outputOffset; + chunkSize = length; } - CTRCreateCounterArray(output.AsSpan(outputOffset, blockSizedLength)); - - var bytesWritten = _encryptor.TransformBlock(output, outputOffset, blockSizedLength, output, outputOffset); - - Debug.Assert(bytesWritten == blockSizedLength); - - ArrayXOR(output, outputOffset, data, offset, length); + var bytesProcessed = 0; + while (bytesProcessed < length) + { + var bytesThisChunk = Math.Min(chunkSize, length - bytesProcessed); + var blockSizedChunk = (bytesThisChunk + BlockSize - 1) & ~(BlockSize - 1); + + CTRCreateCounterArray(keystream.AsSpan(keystreamOffset, blockSizedChunk)); + + var bytesWritten = _encryptor.TransformBlock( + inputBuffer: keystream, + inputOffset: keystreamOffset, + inputCount: blockSizedChunk, + outputBuffer: keystream, + outputOffset: keystreamOffset); + + Debug.Assert(bytesWritten == blockSizedChunk); + + ArrayXOR( + dst: output, + dstOffset: outputOffset + bytesProcessed, + a: data, + aOffset: offset + bytesProcessed, + b: keystream, + bOffset: keystreamOffset, + length: bytesThisChunk); + + bytesProcessed += bytesThisChunk; + } return output; } @@ -120,21 +167,21 @@ private void CTRCreateCounterArray(Span buffer) } } - // XOR 2 arrays using Vector - private static void ArrayXOR(byte[] buffer, int bufferOffset, byte[] data, int offset, int length) + // dst[i] = a[i] ^ b[i] + private static void ArrayXOR(byte[] dst, int dstOffset, byte[] a, int aOffset, byte[] b, int bOffset, int length) { var i = 0; var oneVectorFromEnd = length - Vector.Count; for (; i <= oneVectorFromEnd; i += Vector.Count) { - var v = new Vector(buffer, bufferOffset + i) ^ new Vector(data, offset + i); - v.CopyTo(buffer, bufferOffset + i); + var v = new Vector(a, aOffset + i) ^ new Vector(b, bOffset + i); + v.CopyTo(dst, dstOffset + i); } for (; i < length; i++) { - buffer[bufferOffset + i] ^= data[offset + i]; + dst[dstOffset + i] = (byte)(a[aOffset + i] ^ b[bOffset + i]); } } diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs index eb335549a..4f2d1002f 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesCipher.cs @@ -1,3 +1,4 @@ +#nullable enable using System; using System.Security.Cryptography; @@ -71,6 +72,12 @@ public override byte[] Encrypt(byte[] input, int offset, int length) return _impl.Encrypt(input, offset, length); } + /// + public override int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + return _impl.Encrypt(input, offset, length, output, outputOffset); + } + /// public override byte[] Decrypt(byte[] input, int offset, int length) { diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs index 03cb78ea5..2edaa2b1e 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/AesGcmCipher.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Buffers.Binary; using System.Diagnostics; @@ -74,6 +75,17 @@ public AesGcmCipher(byte[] key, byte[] iv, int aadLength) } } + public override byte[] Encrypt(byte[] input, int offset, int length) + { + var output = new byte[length + TagSizeInBytes]; + + var bytesWritten = Encrypt(input, offset, length, output, 0); + + Debug.Assert(bytesWritten == output.Length); + + return output; + } + /// /// Encrypts the specified input. /// @@ -85,16 +97,17 @@ public AesGcmCipher(byte[] key, byte[] iv, int aadLength) /// /// The zero-based offset in at which to begin encrypting. /// The number of bytes to encrypt from . - /// - /// The encrypted data with below format: + /// The output buffer to write to. + /// The zero-based offset in at which to write encrypted output. + /// + /// The output data is written with the below format: /// /// [----AAD----][----Cipher Text----][----TAG----] /// - /// - public override byte[] Encrypt(byte[] input, int offset, int length) + /// + public override int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) { - var output = new byte[length + TagSize]; - Buffer.BlockCopy(input, offset, output, 0, _aadLength); + input.AsSpan(offset, _aadLength).CopyTo(output.AsSpan(outputOffset)); _impl.Encrypt( input, @@ -103,11 +116,11 @@ public override byte[] Encrypt(byte[] input, int offset, int length) associatedDataOffset: offset, associatedDataLength: _aadLength, output, - cipherTextOffset: _aadLength); + outputOffset + _aadLength); IncrementCounter(); - return output; + return length + TagSizeInBytes; } public override byte[] Decrypt(byte[] input, int offset, int length) diff --git a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs index 4107db278..9ce4f53d0 100644 --- a/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs +++ b/src/Renci.SshNet/Security/Cryptography/Ciphers/ChaCha20Poly1305Cipher.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Buffers.Binary; using System.Diagnostics; @@ -20,9 +21,9 @@ internal sealed class ChaCha20Poly1305Cipher : SymmetricCipher { private readonly byte[] _iv; private readonly int _aadLength; - private readonly KeyParameter _aadKeyParameter; + private readonly KeyParameter? _aadKeyParameter; private readonly KeyParameter _keyParameter; - private readonly ChaCha7539Engine _aadCipher; + private readonly ChaCha7539Engine? _aadCipher; private readonly ChaCha7539Engine _cipher; private readonly Poly1305 _mac; @@ -74,6 +75,17 @@ public ChaCha20Poly1305Cipher(byte[] key, int aadLength) _mac = new Poly1305(); } + public override byte[] Encrypt(byte[] input, int offset, int length) + { + var output = new byte[length + TagSize]; + + var bytesWritten = Encrypt(input, offset, length, output, 0); + + Debug.Assert(bytesWritten == length); + + return output; + } + /// /// Encrypts the specified input. /// @@ -85,13 +97,15 @@ public ChaCha20Poly1305Cipher(byte[] key, int aadLength) /// /// The zero-based offset in at which to begin encrypting. /// The number of bytes to encrypt from . - /// - /// The encrypted data with below format: + /// The output buffer to write to. + /// The zero-based offset in at which to write encrypted output. + /// + /// The output data is written with the below format: /// /// [----Cipher AAD----][----Cipher Text----][----TAG----] /// - /// - public override byte[] Encrypt(byte[] input, int offset, int length) + /// + public override int Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) { _aadCipher?.Init(forEncryption: true, new ParametersWithIV(_aadKeyParameter, _iv)); _cipher.Init(forEncryption: true, new ParametersWithIV(_keyParameter, _iv)); @@ -100,15 +114,15 @@ public override byte[] Encrypt(byte[] input, int offset, int length) _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); _mac.Init(new KeyParameter(keyStream, 0, 32)); - var output = new byte[length + TagSize]; + _aadCipher?.ProcessBytes(input, offset, _aadLength, output, outputOffset); + _cipher.ProcessBytes(input, offset + _aadLength, length - _aadLength, output, outputOffset + _aadLength); - _aadCipher?.ProcessBytes(input, offset, _aadLength, output, 0); - _cipher.ProcessBytes(input, offset + _aadLength, length - _aadLength, output, _aadLength); + _mac.BlockUpdate(output, outputOffset, length); + var macBytesWritten = _mac.DoFinal(output, outputOffset + length); - _mac.BlockUpdate(output, 0, length); - _ = _mac.DoFinal(output, length); + Debug.Assert(macBytesWritten == TagSize); - return output; + return length + macBytesWritten; } /// diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 104852a40..a6576d9af 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -1050,68 +1050,80 @@ internal void SendMessage(Message message) } var paddingMultiplier = _clientCipher is null ? (byte)8 : Math.Max((byte)8, _clientCipher.MinimumSize); - var packetData = message.GetPacket(paddingMultiplier, _clientCompression, _clientEtm || _clientAead); + + var macLength = 0; + + if (_clientAead) + { + macLength = _clientCipher.TagSize; + } + else if (_clientMac != null) + { + macLength = _clientMac.HashSize / 8; + } + + var packetData = message.GetPacket(paddingMultiplier, _clientCompression, _clientEtm || _clientAead, macLength); + + if (packetData.Length > MaximumSshPacketSize) + { + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize)); + } // take a write lock to ensure the outbound packet sequence number is incremented // atomically, and only after the packet has actually been sent lock (_socketWriteLock) { - byte[] hash = null; - var packetDataOffset = 4; // first four bytes are reserved for outbound packet sequence - // write outbound packet sequence to start of packet data BinaryPrimitives.WriteUInt32BigEndian(packetData, _outboundPacketSequence); if (_clientMac != null && !_clientEtm) { - // calculate packet hash - hash = _clientMac.ComputeHash(packetData); + // non-ETM mac = MAC(key, sequence_number || unencrypted_packet) + + var hashSuccess = _clientMac.TryComputeHash( + buffer: packetData, + offset: 0, + count: packetData.Length - macLength, + destination: packetData.AsSpan(packetData.Length - macLength), + bytesWritten: out var bytesWritten); + + Debug.Assert(hashSuccess && bytesWritten == macLength); } - // Encrypt packet data if (_clientCipher != null) { _clientCipher.SetSequenceNumber(_outboundPacketSequence); - if (_clientEtm) - { - // The length of the "packet length" field in bytes - const int packetLengthFieldLength = 4; - - var encryptedData = _clientCipher.Encrypt(packetData, packetDataOffset + packetLengthFieldLength, packetData.Length - packetDataOffset - packetLengthFieldLength); - Array.Resize(ref packetData, packetDataOffset + packetLengthFieldLength + encryptedData.Length); + // Not encrypting the sequence number (it is not part of the packet), + // nor the packet length for ETM. + var offset = _clientEtm ? 8 : 4; - // write encrypted data - Buffer.BlockCopy(encryptedData, 0, packetData, packetDataOffset + packetLengthFieldLength, encryptedData.Length); + var numberOfBytesEncrypted = _clientCipher.Encrypt( + input: packetData, + offset, + length: packetData.Length - offset - macLength, + output: packetData, + outputOffset: offset); - // calculate packet hash - hash = _clientMac.ComputeHash(packetData); - } - else - { - packetData = _clientCipher.Encrypt(packetData, packetDataOffset, packetData.Length - packetDataOffset); - packetDataOffset = 0; - } + Debug.Assert(numberOfBytesEncrypted == packetData.Length - offset - macLength + (_clientAead ? macLength : 0)); } - if (packetData.Length > MaximumSshPacketSize) + if (_clientMac != null && _clientEtm) { - throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, "Packet is too big. Maximum packet size is {0} bytes.", MaximumSshPacketSize)); - } + // ETM mac = MAC(key, sequence_number || packet_length || encrypted_packet) - var packetLength = packetData.Length - packetDataOffset; - if (hash is null) - { - SendPacket(packetData, packetDataOffset, packetLength); - } - else - { - var data = new byte[packetLength + hash.Length]; - Buffer.BlockCopy(packetData, packetDataOffset, data, 0, packetLength); - Buffer.BlockCopy(hash, 0, data, packetLength, hash.Length); - SendPacket(data, 0, data.Length); + var hashSuccess = _clientMac.TryComputeHash( + buffer: packetData, + offset: 0, + count: packetData.Length - macLength, + destination: packetData.AsSpan(packetData.Length - macLength), + bytesWritten: out var bytesWritten); + + Debug.Assert(hashSuccess && bytesWritten == macLength); } + SendPacket(packetData, 4, packetData.Length - 4); + if (_isStrictKex && message is NewKeysMessage) { _outboundPacketSequence = 0; diff --git a/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs b/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs index 9cecc0855..43032763e 100644 --- a/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs +++ b/test/Renci.SshNet.Benchmarks/Security/Cryptography/Ciphers/AesCipherBenchmarks.cs @@ -62,13 +62,13 @@ public byte[] Decrypt_CTR() [Benchmark] public byte[] Encrypt_ECB() { - return new AesCipher(_key, null, AesCipherMode.ECB, false).Encrypt(_data); + return new AesCipher(_key, [], AesCipherMode.ECB, false).Encrypt(_data); } [Benchmark] public byte[] Decrypt_ECB() { - return new AesCipher(_key, null, AesCipherMode.ECB, false).Decrypt(_data); + return new AesCipher(_key, [], AesCipherMode.ECB, false).Decrypt(_data); } } } diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt index 066c80ed5..1f9de58a1 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.Gen.cs.txt @@ -141,6 +141,20 @@ foreach ((string mode, (string modeCode, CipherMode? bclMode)) in modes) tw.WriteLine(); tw.WriteLine($"CollectionAssert.AreEqual(input, decrypted);"); + tw.WriteLine(); + tw.WriteLine($"// Decrypt in-place"); + tw.WriteLine($"var decryptCount = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Decrypt(actual, 0, actual.Length, actual, 0);"); + tw.WriteLine(); + tw.WriteLine($"Assert.AreEqual(input.Length, decryptCount);"); + tw.WriteLine($"CollectionAssert.AreEqual(input, {(pad ? "actual.Take(decryptCount)" : "actual")});"); + + tw.WriteLine(); + tw.WriteLine($"// Re-encrypt in-place"); + tw.WriteLine($"var encryptCount = new AesCipher(key, {modeCode}, pkcs7Padding: {(pad ? "true" : "false")}).Encrypt(actual, 0, input.Length, actual, 0);"); + tw.WriteLine(); + tw.WriteLine($"Assert.AreEqual(expected.Length, encryptCount);"); + tw.WriteLine($"CollectionAssert.AreEqual(expected, actual);"); + tw.Indent--; tw.WriteLine("}"); tw.WriteLine(); diff --git a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs index ee738b8a9..42bf9311b 100644 --- a/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs +++ b/test/Renci.SshNet.Tests/Classes/Security/Cryptography/Ciphers/AesCipherTest.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -182,6 +183,18 @@ public void Decrypt_InputAndOffsetAndLength_128_CTR() Assert.IsTrue(expected.IsEqualTo(actual)); } + [TestMethod] + public void AES_CTR_OverlappingBuffers_ThrowsArgumentException() + { + var key = new byte[16]; + var iv = new byte[16]; + var cipher = new AesCipher(key, iv, AesCipherMode.CTR, pkcs7Padding: false); + var buffer = new byte[64]; + + Assert.ThrowsExactly(() => cipher.Encrypt(buffer, 0, 32, buffer, 16)); + Assert.ThrowsExactly(() => cipher.Decrypt(buffer, 0, 32, buffer, 16)); + } + // All tests below this line were generated by the script in AesCipherTest.Gen.cs.txt [TestMethod] public void AES_ECB_128_Length16_NoPad() @@ -208,6 +221,18 @@ public void AES_ECB_128_Length16_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -236,6 +261,18 @@ public void AES_ECB_128_Length16_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -267,6 +304,18 @@ public void AES_ECB_128_Length35_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -300,6 +349,18 @@ public void AES_ECB_128_Length64_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -334,6 +395,18 @@ public void AES_ECB_128_Length64_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -362,6 +435,18 @@ public void AES_ECB_192_Length16_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -391,6 +476,18 @@ public void AES_ECB_192_Length16_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -423,6 +520,18 @@ public void AES_ECB_192_Length35_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -457,6 +566,18 @@ public void AES_ECB_192_Length64_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -492,6 +613,18 @@ public void AES_ECB_192_Length64_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -520,6 +653,18 @@ public void AES_ECB_256_Length16_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -549,6 +694,18 @@ public void AES_ECB_256_Length16_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -581,6 +738,18 @@ public void AES_ECB_256_Length35_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -615,6 +784,18 @@ public void AES_ECB_256_Length64_NoPad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -650,6 +831,18 @@ public void AES_ECB_256_Length64_Pad() var decrypted = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, iv: null, AesCipherMode.ECB, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -681,6 +874,18 @@ public void AES_CBC_128_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -713,6 +918,18 @@ public void AES_CBC_128_Length16_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -748,6 +965,18 @@ public void AES_CBC_128_Length35_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -785,6 +1014,18 @@ public void AES_CBC_128_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -823,6 +1064,18 @@ public void AES_CBC_128_Length64_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -855,6 +1108,18 @@ public void AES_CBC_192_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -888,6 +1153,18 @@ public void AES_CBC_192_Length16_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -924,6 +1201,18 @@ public void AES_CBC_192_Length35_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -962,6 +1251,18 @@ public void AES_CBC_192_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1001,6 +1302,18 @@ public void AES_CBC_192_Length64_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1033,6 +1346,18 @@ public void AES_CBC_256_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1066,6 +1391,18 @@ public void AES_CBC_256_Length16_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1102,6 +1439,18 @@ public void AES_CBC_256_Length35_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1140,6 +1489,18 @@ public void AES_CBC_256_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1179,6 +1540,18 @@ public void AES_CBC_256_Length64_Pad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual.Take(decryptCount)); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CBC, pkcs7Padding: true).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1210,6 +1583,18 @@ public void AES_CFB_128_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1245,6 +1630,18 @@ public void AES_CFB_128_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1282,6 +1679,18 @@ public void AES_CFB_128_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1314,6 +1723,18 @@ public void AES_CFB_192_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1350,6 +1771,18 @@ public void AES_CFB_192_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1388,6 +1821,18 @@ public void AES_CFB_192_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1420,6 +1865,18 @@ public void AES_CFB_256_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1456,6 +1913,18 @@ public void AES_CFB_256_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1494,6 +1963,18 @@ public void AES_CFB_256_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1525,6 +2006,18 @@ public void AES_CTR_128_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1560,6 +2053,18 @@ public void AES_CTR_128_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1597,6 +2102,18 @@ public void AES_CTR_128_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1629,6 +2146,18 @@ public void AES_CTR_192_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1665,6 +2194,18 @@ public void AES_CTR_192_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1703,6 +2244,18 @@ public void AES_CTR_192_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1735,6 +2288,18 @@ public void AES_CTR_256_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1771,6 +2336,18 @@ public void AES_CTR_256_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1809,6 +2386,18 @@ public void AES_CTR_256_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.CTR, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1840,6 +2429,18 @@ public void AES_OFB_128_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1875,6 +2476,18 @@ public void AES_OFB_128_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1912,6 +2525,18 @@ public void AES_OFB_128_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1944,6 +2569,18 @@ public void AES_OFB_192_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -1980,6 +2617,18 @@ public void AES_OFB_192_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -2018,6 +2667,18 @@ public void AES_OFB_192_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -2050,6 +2711,18 @@ public void AES_OFB_256_Length16_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -2086,6 +2759,18 @@ public void AES_OFB_256_Length35_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } [TestMethod] @@ -2124,6 +2809,19 @@ public void AES_OFB_256_Length64_NoPad() var decrypted = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual); CollectionAssert.AreEqual(input, decrypted); + + // Decrypt in-place + var decryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Decrypt(actual, 0, actual.Length, actual, 0); + + Assert.AreEqual(input.Length, decryptCount); + CollectionAssert.AreEqual(input, actual); + + // Re-encrypt in-place + var encryptCount = new AesCipher(key, (byte[])iv.Clone(), AesCipherMode.OFB, pkcs7Padding: false).Encrypt(actual, 0, input.Length, actual, 0); + + Assert.AreEqual(expected.Length, encryptCount); + CollectionAssert.AreEqual(expected, actual); } + } }