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..a87bf047b 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; } /// @@ -124,23 +138,28 @@ public override byte[] Encrypt(byte[] input, int offset, int length) /// The decrypted plaintext. public override byte[] Decrypt(byte[] input, int offset, int length) { - byte[] output; + var output = new byte[length]; + + _cipher.Init(forEncryption: false, new ParametersWithIV(_keyParameter, _iv)); + + var keyStream = new byte[64]; + _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); + _mac.Init(new KeyParameter(keyStream, 0, 32)); if (_aadLength > 0) { // If we are in 'AAD mode', then put these bytes through the AAD cipher. + _mac.BlockUpdate(input, offset, length); + Debug.Assert(_aadCipher != null); _aadCipher.Init(forEncryption: false, new ParametersWithIV(_aadKeyParameter, _iv)); - output = new byte[length]; _aadCipher.ProcessBytes(input, offset, length, output, 0); } else { - output = new byte[length]; - var bytesWritten = Decrypt(input, offset, length, output, 0); Debug.Assert(bytesWritten == length); @@ -155,7 +174,7 @@ public override byte[] Decrypt(byte[] input, int offset, int length) /// /// The input data with below format: /// - /// [----][----Cipher AAD----(offset)][----Cipher Text----(length)][----TAG----] + /// [----(offset)][----Cipher Text----(length)][----TAG----] /// /// /// The zero-based offset in at which to begin decrypting and authenticating. @@ -165,16 +184,8 @@ public override byte[] Decrypt(byte[] input, int offset, int length) /// The number of plaintext bytes written to . public override int Decrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) { - Debug.Assert(offset >= _aadLength, "The offset must be greater than or equals to aad length"); - - _cipher.Init(forEncryption: false, new ParametersWithIV(_keyParameter, _iv)); - - var keyStream = new byte[64]; - _cipher.ProcessBytes(keyStream, 0, keyStream.Length, keyStream, 0); - _mac.Init(new KeyParameter(keyStream, 0, 32)); - var tag = new byte[TagSize]; - _mac.BlockUpdate(input, offset - _aadLength, length + _aadLength); + _mac.BlockUpdate(input, offset, length); _ = _mac.DoFinal(tag, 0); if (!Arrays.FixedTimeEquals(TagSize, tag, 0, input, offset + length)) { diff --git a/src/Renci.SshNet/Session.cs b/src/Renci.SshNet/Session.cs index 104852a40..c73c3db26 100644 --- a/src/Renci.SshNet/Session.cs +++ b/src/Renci.SshNet/Session.cs @@ -105,6 +105,23 @@ public sealed class Session : ISession /// private readonly SemaphoreSlim _connectLock = new SemaphoreSlim(1, 1); + private readonly byte[] _inboundPacketSequenceBytes = new byte[4]; + + /// + /// Gets or sets the incoming packet number. + /// + private uint InboundPacketSequence + { + get + { + return BinaryPrimitives.ReadUInt32BigEndian(_inboundPacketSequenceBytes); + } + set + { + BinaryPrimitives.WriteUInt32BigEndian(_inboundPacketSequenceBytes, value); + } + } + /// /// Holds metadata about session messages. /// @@ -120,11 +137,6 @@ public sealed class Session : ISession /// private volatile uint _outboundPacketSequence; - /// - /// Specifies incoming packet number. - /// - private uint _inboundPacketSequence; - /// /// WaitHandle to signal that last service request was accepted. /// @@ -200,7 +212,6 @@ public sealed class Session : ISession private Socket _socket; private ArrayBuffer _receiveBuffer = new(4 * 1024); - private byte[] _plaintextReceiveBuffer = new byte[4 * 1024]; /// /// Gets the session semaphore that controls session channels. @@ -1050,68 +1061,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); + // Not encrypting the sequence number (it is not part of the packet), + // nor the packet length for ETM. + var offset = _clientEtm ? 8 : 4; - Array.Resize(ref packetData, packetDataOffset + packetLengthFieldLength + encryptedData.Length); + var numberOfBytesEncrypted = _clientCipher.Encrypt( + input: packetData, + offset, + length: packetData.Length - offset - macLength, + output: packetData, + outputOffset: offset); - // write encrypted data - Buffer.BlockCopy(encryptedData, 0, packetData, packetDataOffset + packetLengthFieldLength, encryptedData.Length); - - // 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; @@ -1201,9 +1224,6 @@ private bool TrySendMessage(Message message) /// private Message ReceiveMessage(Socket socket) { - // the length of the packet sequence field in bytes - const int inboundPacketSequenceLength = 4; - // The length of the "packet length" field in bytes const int packetLengthFieldLength = 4; @@ -1260,31 +1280,28 @@ private Message ReceiveMessage(Socket socket) } } - var firstBlock = new ArraySegment( - _receiveBuffer.DangerousGetUnderlyingBuffer(), - _receiveBuffer.ActiveStartOffset, - blockSize); - - var plainFirstBlock = firstBlock; - - // For ETM or AES-GCM, firstBlock holds the packet length which is - // not encrypted. Otherwise, we decrypt the first "blockSize" bytes. - // (For chacha20-poly1305, this means passing the encrypted packet - // length as AAD). + // For ETM or AES-GCM, the first "blockSize" bytes hold the packet length + // which is not encrypted. Otherwise, we decrypt them. + // (For chacha20-poly1305, this means passing the encrypted packet length + // to its AAD cipher instance - it is the awkward difference between the + // 3-arg and 5-arg Decrypt, and explains why we don't just decrypt these + // bytes in-place). if (_serverCipher is not null and not Security.Cryptography.Ciphers.AesGcmCipher) { - _serverCipher.SetSequenceNumber(_inboundPacketSequence); + _serverCipher.SetSequenceNumber(InboundPacketSequence); if (_serverMac == null || !_serverEtm) { - plainFirstBlock = new ArraySegment(_serverCipher.Decrypt( - firstBlock.Array, - firstBlock.Offset, - firstBlock.Count)); + var plainFirstBlock = _serverCipher.Decrypt( + _receiveBuffer.DangerousGetUnderlyingBuffer(), + _receiveBuffer.ActiveStartOffset, + blockSize); + + plainFirstBlock.CopyTo(_receiveBuffer.ActiveSpan); } } - var packetLength = BinaryPrimitives.ReadInt32BigEndian(plainFirstBlock); + var packetLength = BinaryPrimitives.ReadInt32BigEndian(_receiveBuffer.ActiveReadOnlySpan); // Test packet minimum and maximum boundaries if (packetLength < Math.Max((byte)8, blockSize) - 4 || packetLength > MaximumSshPacketSize - 4) @@ -1318,26 +1335,13 @@ private Message ReceiveMessage(Socket socket) } } - // Construct buffer for holding the payload and the inbound packet sequence as we need both in order - // to generate the hash. - var plaintextLength = 4 + totalPacketLength - serverMacLength; - - if (_plaintextReceiveBuffer.Length < plaintextLength) - { - Array.Resize(ref _plaintextReceiveBuffer, Math.Max(plaintextLength, 2 * _plaintextReceiveBuffer.Length)); - } - - BinaryPrimitives.WriteUInt32BigEndian(_plaintextReceiveBuffer, _inboundPacketSequence); - - plainFirstBlock.AsSpan().CopyTo(_plaintextReceiveBuffer.AsSpan(4)); - if (_serverMac != null && _serverEtm) { // ETM mac = MAC(key, sequence_number || packet_length || encrypted_packet) // sequence_number _ = _serverMac.TransformBlock( - inputBuffer: _plaintextReceiveBuffer, + inputBuffer: _inboundPacketSequenceBytes, inputOffset: 0, inputCount: 4, outputBuffer: null, @@ -1365,41 +1369,52 @@ private Message ReceiveMessage(Socket socket) { Debug.Assert(numberOfBytesToDecrypt % blockSize == 0); + var decryptBuffer = _receiveBuffer.DangerousGetUnderlyingBuffer(); + var decryptOffset = _receiveBuffer.ActiveStartOffset + blockSize; + var numberOfBytesDecrypted = _serverCipher.Decrypt( - input: _receiveBuffer.DangerousGetUnderlyingBuffer(), - offset: _receiveBuffer.ActiveStartOffset + blockSize, + input: decryptBuffer, + offset: decryptOffset, length: numberOfBytesToDecrypt, - output: _plaintextReceiveBuffer, - outputOffset: 4 + blockSize); + output: decryptBuffer, + outputOffset: decryptOffset); Debug.Assert(numberOfBytesDecrypted == numberOfBytesToDecrypt); } - else - { - _receiveBuffer.ActiveReadOnlySpan - .Slice(blockSize, numberOfBytesToDecrypt) - .CopyTo(_plaintextReceiveBuffer.AsSpan(4 + blockSize)); - } if (_serverMac != null && !_serverEtm) { // non-ETM mac = MAC(key, sequence_number || unencrypted_packet) - var clientHash = _serverMac.ComputeHash(_plaintextReceiveBuffer, 0, plaintextLength); + // sequence_number + _ = _serverMac.TransformBlock( + inputBuffer: _inboundPacketSequenceBytes, + inputOffset: 0, + inputCount: 4, + outputBuffer: null, + outputOffset: 0); + + // unencrypted_packet + _ = _serverMac.TransformBlock( + inputBuffer: _receiveBuffer.DangerousGetUnderlyingBuffer(), + inputOffset: _receiveBuffer.ActiveStartOffset, + inputCount: totalPacketLength - serverMacLength, + outputBuffer: null, + outputOffset: 0); - if (!CryptoAbstraction.FixedTimeEquals(clientHash, _receiveBuffer.ActiveSpan.Slice(totalPacketLength - serverMacLength, serverMacLength))) + _ = _serverMac.TransformFinalBlock(Array.Empty(), 0, 0); + + if (!CryptoAbstraction.FixedTimeEquals(_serverMac.Hash, _receiveBuffer.ActiveSpan.Slice(totalPacketLength - serverMacLength, serverMacLength))) { throw new SshConnectionException("MAC error", DisconnectReason.MacError); } } - _receiveBuffer.Discard(totalPacketLength); - - var paddingLength = _plaintextReceiveBuffer[inboundPacketSequenceLength + packetLengthFieldLength]; + var paddingLength = _receiveBuffer.ActiveReadOnlySpan[packetLengthFieldLength]; ArraySegment payload = new( - _plaintextReceiveBuffer, - offset: inboundPacketSequenceLength + packetLengthFieldLength + paddingLengthFieldLength, + _receiveBuffer.DangerousGetUnderlyingBuffer(), + offset: _receiveBuffer.ActiveStartOffset + packetLengthFieldLength + paddingLengthFieldLength, count: packetLength - paddingLength - paddingLengthFieldLength); if (_serverDecompression != null) @@ -1407,16 +1422,24 @@ private Message ReceiveMessage(Socket socket) payload = new(_serverDecompression.Decompress(payload.Array, payload.Offset, payload.Count)); } - _inboundPacketSequence++; + var newInboundPacketSequence = ++InboundPacketSequence; // The below code mirrors from https://github.com/openssh/openssh-portable/commit/1edb00c58f8a6875fad6a497aa2bacf37f9e6cd5 // It ensures the integrity of key exchange process. - if (_inboundPacketSequence == uint.MaxValue && _isInitialKex) + if (newInboundPacketSequence == uint.MaxValue && _isInitialKex) { throw new SshConnectionException("Inbound packet sequence number is about to wrap during initial key exchange.", DisconnectReason.KeyExchangeFailed); } - return LoadMessage(payload.Array, payload.Offset, payload.Count); + var message = LoadMessage(payload.Array, payload.Offset, payload.Count); + + // The deserialised message may still reference data in the buffer, so calling Discard + // here might seem misguided. It is OK because Discard does not mutate the buffer + // and it will not be touched again until the next call to ReceiveMessage, which will + // only occur after the message has been fully processed. + _receiveBuffer.Discard(totalPacketLength); + + return message; } private void TrySendDisconnect(DisconnectReason reasonCode, string message) @@ -1533,7 +1556,7 @@ internal void OnKeyExchangeInitReceived(KeyExchangeInitMessage message) _logger.LogDebug("[{SessionId}] Enabling strict key exchange extension.", SessionIdHex); - if (_inboundPacketSequence != 1) + if (InboundPacketSequence != 1) { throw new SshConnectionException("KEXINIT was not the first packet during strict key exchange.", DisconnectReason.KeyExchangeFailed); } @@ -1634,7 +1657,7 @@ internal void OnNewKeysReceived(NewKeysMessage message) if (_isStrictKex) { - _inboundPacketSequence = 0; + InboundPacketSequence = 0; } NewKeysReceived?.Invoke(this, new MessageEventArgs(message)); 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); } + } }