From 005ffc0db2ff26769d3ec7e5292953fa66ddd856 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Sun, 29 Jan 2023 21:55:39 +0100 Subject: [PATCH 1/2] Replace Bouncy Castle AES engine with native one --- src/Nethermind/Nethermind.Crypto/AesCtr.cs | 139 +++++++++++++++ .../Nethermind.Crypto/AesCtrTransform.cs | 99 +++++++++++ .../Rlpx/FrameCipherTests.cs | 167 +++++++++--------- .../Nethermind.Network/Rlpx/FrameCipher.cs | 51 +++--- 4 files changed, 341 insertions(+), 115 deletions(-) create mode 100644 src/Nethermind/Nethermind.Crypto/AesCtr.cs create mode 100644 src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs diff --git a/src/Nethermind/Nethermind.Crypto/AesCtr.cs b/src/Nethermind/Nethermind.Crypto/AesCtr.cs new file mode 100644 index 000000000000..ea29be12f627 --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/AesCtr.cs @@ -0,0 +1,139 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Security.Cryptography; + +namespace Nethermind.Crypto; + +/// +/// Represents an Advanced Encryption Standard (AES) key +/// to be used with the Counter (CTR) mode of operation. +/// +public class AesCtr : SymmetricAlgorithm +{ + private readonly Aes _aes; + private byte[] _nonce; + private readonly byte[] _zeroIV; + + /// + /// Initializes a new instance of the AesCtr class + /// with a provided key and initialization vector (IV/nonce) if any. + /// + /// The secret key to use for this instance. + /// The IV/nonce to use for this instance. + public AesCtr(byte[]? key = null, byte[]? iv = null) + { + _aes = Aes.Create(); + _aes.Mode = CipherMode.ECB; + _aes.Padding = PaddingMode.None; + + if (key is not null) + _aes.Key = key; + + _zeroIV = new byte[_aes.BlockSize / 8]; + _aes.IV = _zeroIV; + IV = iv ?? _zeroIV; + } + + ///// + ///// Initializes a new instance of the AesCtr class with a provided instance. + ///// + ///// + ///// + ///// The aes parameter is null. + ///// + ///// The cipher mode or padding is invalid. + //public AesCtr(Aes aes) + //{ + // _aes = aes ?? throw new ArgumentNullException(nameof(aes)); + + // if (_aes.Mode != CipherMode.ECB) + // throw new CryptographicException("Invalid cipher mode."); + + // if (_aes.Padding != PaddingMode.None) + // throw new CryptographicException("Invalid padding mode."); + + // _zeroIV = new byte[_aes.BlockSize / 8]; + //} + + public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV = null) + => CreateEncryptor(rgbKey, rgbIV); + + public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV = null) + => new AesCtrTransform(_aes.CreateEncryptor(rgbKey, _zeroIV), rgbIV ?? _nonce); + + protected override void Dispose(bool disposing) + { + if (disposing) + _aes.Dispose(); + } + + public override void GenerateIV() => RandomNumberGenerator.Fill(_nonce); + + public override void GenerateKey() => _aes.GenerateKey(); + + public override int BlockSize { get => _aes.BlockSize; set => _aes.BlockSize = value; } + + /// + /// The feedback size is not supported for the CTR cipher mode. + /// + /// + public override int FeedbackSize + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override byte[] IV + { + get => _nonce; + set + { + ArgumentNullException.ThrowIfNull(value); + + if (value.Length % (BlockSize / 8) != 0) + throw new CryptographicException("Specified initialization vector (IV) does not match the block size for this algorithm."); + + _nonce = value; + } + } + + public override byte[] Key { get => _aes.Key; set => _aes.Key = value; } + + public override int KeySize { get => _aes.KeySize; set => _aes.KeySize = value; } + + public override KeySizes[] LegalBlockSizes => _aes.LegalBlockSizes; + + public override KeySizes[] LegalKeySizes => _aes.LegalKeySizes; + + /// + /// Supports only. + /// + public override CipherMode Mode + { + get => _aes.Mode; + set + { + if (value != CipherMode.ECB) + throw new CryptographicException("Invalid cipher mode."); + + _aes.Mode = value; + } + } + + /// + /// Supports only. + /// + public override PaddingMode Padding + { + get => _aes.Padding; + set + { + if (value != PaddingMode.None) + throw new CryptographicException("Invalid padding mode."); + + _aes.Padding = value; + } + } +} diff --git a/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs b/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs new file mode 100644 index 000000000000..f91acc09d6f0 --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Security.Cryptography; + +namespace Nethermind.Crypto; + +internal sealed class AesCtrTransform : ICryptoTransform +{ + private readonly ICryptoTransform _aes; + private readonly byte[] _counter; + private readonly byte[] _nonce; + + public AesCtrTransform(ICryptoTransform aes, ReadOnlySpan nonce) + { + _aes = aes ?? throw new ArgumentNullException(nameof(aes)); + _counter = nonce.ToArray(); + _nonce = nonce.ToArray(); + } + + public bool CanReuseTransform => _aes.CanReuseTransform; + + public bool CanTransformMultipleBlocks => _aes.CanTransformMultipleBlocks; + + public int InputBlockSize => _aes.InputBlockSize; + + public int OutputBlockSize => _aes.OutputBlockSize; + + public void Dispose() => _aes.Dispose(); + + public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) + { + ArgumentNullException.ThrowIfNull(inputBuffer); + ArgumentNullException.ThrowIfNull(outputBuffer); + + if (inputOffset < 0) + throw new ArgumentOutOfRangeException(nameof(inputOffset)); + + if (inputCount <= 0) + throw new ArgumentOutOfRangeException(nameof(inputCount)); + + if (inputCount % InputBlockSize != 0) + throw new ArgumentOutOfRangeException(nameof(inputCount), "TransformBlock may only process bytes in block sized increments."); + + if (inputOffset + inputCount > inputBuffer.Length) + throw new ArgumentOutOfRangeException(nameof(inputOffset), "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + + if (outputOffset < 0) + throw new ArgumentOutOfRangeException(nameof(outputOffset)); + + if (outputOffset + OutputBlockSize > outputBuffer.Length) + throw new ArgumentOutOfRangeException(nameof(outputOffset), "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + + var counterLen = _counter.Length; + var counterOutput = new byte[counterLen]; + var outputLen = _aes.TransformBlock(_counter, 0, counterLen, counterOutput, 0); + + for (var i = 0; i < counterLen; i++) + outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ counterOutput[i]); + + for (var i = counterLen; --i >= 0 && ++_counter[i] == 0;) { } + + return outputLen; + } + + public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount) + { + ArgumentNullException.ThrowIfNull(inputBuffer); + + if (inputOffset < 0) + throw new ArgumentOutOfRangeException(nameof(inputOffset)); + + if (inputCount <= 0) + throw new ArgumentOutOfRangeException(nameof(inputCount)); + + if (inputOffset + inputCount > inputBuffer.Length) + throw new ArgumentOutOfRangeException(nameof(inputOffset), "Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection."); + + var blockSize = InputBlockSize; + var counterOutput = new byte[blockSize]; + var offset = 0; + var outputBuffer = new byte[inputCount]; + + for (int i = 0, count = inputCount / blockSize; i < count; i += blockSize) + offset += TransformBlock(inputBuffer, inputOffset + i, blockSize, outputBuffer, offset); + + _aes.TransformBlock(_counter, 0, blockSize, counterOutput, 0); + + for (int i = 0, count = inputCount % blockSize; i < count; i++) + outputBuffer[offset + i] = (byte)(inputBuffer[inputOffset + offset + i] ^ counterOutput[i]); + + Reset(); + + return outputBuffer; + } + + private void Reset() => _nonce.CopyTo((Memory)_counter); +} diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs index 4d5a43935d78..39bdc30e5c21 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs @@ -5,91 +5,90 @@ using Nethermind.Network.Rlpx; using NUnit.Framework; -namespace Nethermind.Network.Test.Rlpx +namespace Nethermind.Network.Test.Rlpx; + +[TestFixture] +public class FrameCipherTests { - [TestFixture] - public class FrameCipherTests + [Test] + public void Can_do_roundtrip() + { + byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + byte[] encrypted = new byte[16]; + byte[] decrypted = new byte[16]; + + FrameCipher frameCipher = new(NetTestVectors.AesSecret); + frameCipher.Encrypt(message, 0, 16, encrypted, 0); + frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); + Assert.AreEqual(message, decrypted); + } + + [Test] + public void Can_run_twice() + { + byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + byte[] encrypted = new byte[16]; + byte[] decrypted = new byte[16]; + + FrameCipher frameCipher = new(NetTestVectors.AesSecret); + frameCipher.Encrypt(message, 0, 16, encrypted, 0); + frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); + Assert.AreEqual(message, decrypted); + + Array.Clear(encrypted, 0, encrypted.Length); + Array.Clear(decrypted, 0, decrypted.Length); + frameCipher.Encrypt(message, 0, 16, encrypted, 0); + frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); + Assert.AreEqual(message, decrypted); + } + + [Test] + public void Should_not_return_same_value_when_used_twice_with_same_input() + { + byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + byte[] encrypted1 = new byte[16]; + byte[] encrypted2 = new byte[16]; + + FrameCipher frameCipher = new(NetTestVectors.AesSecret); + frameCipher.Encrypt(message, 0, 16, encrypted1, 0); + frameCipher.Encrypt(message, 0, 16, encrypted2, 0); + Assert.AreNotEqual(encrypted1, encrypted2); + } + + [Test] + public void Can_run_twice_longer_message() { - [Test] - public void Can_do_roundtrip() - { - byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - byte[] encrypted = new byte[16]; - byte[] decrypted = new byte[16]; - - FrameCipher frameCipher = new(NetTestVectors.AesSecret); - frameCipher.Encrypt(message, 0, 16, encrypted, 0); - frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); - Assert.AreEqual(message, decrypted); - } - - [Test] - public void Can_run_twice() - { - byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - byte[] encrypted = new byte[16]; - byte[] decrypted = new byte[16]; - - FrameCipher frameCipher = new(NetTestVectors.AesSecret); - frameCipher.Encrypt(message, 0, 16, encrypted, 0); - frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); - Assert.AreEqual(message, decrypted); - - Array.Clear(encrypted, 0, encrypted.Length); - Array.Clear(decrypted, 0, decrypted.Length); - frameCipher.Encrypt(message, 0, 16, encrypted, 0); - frameCipher.Decrypt(encrypted, 0, 16, decrypted, 0); - Assert.AreEqual(message, decrypted); - } - - [Test] - public void Should_not_return_same_value_when_used_twice_with_same_input() - { - byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - byte[] encrypted1 = new byte[16]; - byte[] encrypted2 = new byte[16]; - - FrameCipher frameCipher = new(NetTestVectors.AesSecret); - frameCipher.Encrypt(message, 0, 16, encrypted1, 0); - frameCipher.Encrypt(message, 0, 16, encrypted2, 0); - Assert.AreNotEqual(encrypted1, encrypted2); - } - - [Test] - public void Can_run_twice_longer_message() - { - int length = 16; - - byte[] message = new byte[length * 2]; - message[3] = 123; - message[4] = 123; - message[5] = 12; - - byte[] encrypted = new byte[length]; - byte[] decrypted = new byte[2 * length]; - - FrameCipher frameCipher = new(NetTestVectors.AesSecret); - frameCipher.Encrypt(message, 0, length, encrypted, 0); - frameCipher.Decrypt(encrypted, 0, length, decrypted, 0); - Assert.AreEqual(message, decrypted); - - Array.Clear(encrypted, 0, encrypted.Length); - Array.Clear(decrypted, 0, decrypted.Length); - frameCipher.Encrypt(message, 0, length, encrypted, 0); - frameCipher.Decrypt(encrypted, 0, length, decrypted, 0); - Assert.AreEqual(message, decrypted); - } - - [Test] - public void Can_do_inline() - { - byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; - byte[] messageClone = (byte[])message.Clone(); - - FrameCipher frameCipher = new(NetTestVectors.AesSecret); - frameCipher.Encrypt(messageClone, 0, 16, messageClone, 0); - frameCipher.Decrypt(messageClone, 0, 16, messageClone, 0); - Assert.AreEqual(message, messageClone); - } + int length = 16; + + byte[] message = new byte[length * 2]; + message[3] = 123; + message[4] = 123; + message[5] = 12; + + byte[] encrypted = new byte[length]; + byte[] decrypted = new byte[2 * length]; + + FrameCipher frameCipher = new(NetTestVectors.AesSecret); + frameCipher.Encrypt(message, 0, length, encrypted, 0); + frameCipher.Decrypt(encrypted, 0, length, decrypted, 0); + Assert.AreEqual(message, decrypted); + + Array.Clear(encrypted, 0, encrypted.Length); + Array.Clear(decrypted, 0, decrypted.Length); + frameCipher.Encrypt(message, 0, length, encrypted, 0); + frameCipher.Decrypt(encrypted, 0, length, decrypted, 0); + Assert.AreEqual(message, decrypted); + } + + [Test] + public void Can_do_inline() + { + byte[] message = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + byte[] messageClone = (byte[])message.Clone(); + + FrameCipher frameCipher = new(NetTestVectors.AesSecret); + frameCipher.Encrypt(messageClone, 0, 16, messageClone, 0); + frameCipher.Decrypt(messageClone, 0, 16, messageClone, 0); + Assert.AreEqual(message, messageClone); } } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs b/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs index 7d5822f8d750..a9b74b03fd32 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs @@ -2,46 +2,35 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics; - +using System.Security.Cryptography; using Nethermind.Crypto; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Engines; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; +namespace Nethermind.Network.Rlpx; -namespace Nethermind.Network.Rlpx +public class FrameCipher : IFrameCipher { - public class FrameCipher : IFrameCipher - { - private const int BlockSize = 16; - private const int KeySize = 32; + private const int KeySize = 32; - private readonly IBufferedCipher _decryptionCipher; - private readonly IBufferedCipher _encryptionCipher; + private readonly ICryptoTransform _decryptionCipher; + private readonly ICryptoTransform _encryptionCipher; - public FrameCipher(byte[] aesKey) - { - IBlockCipher aes = AesEngineX86Intrinsic.IsSupported ? new AesEngineX86Intrinsic() : new AesEngine(); - - Debug.Assert(aesKey.Length == KeySize, $"AES key expected to be {KeySize} bytes long"); + public FrameCipher(byte[] aesKey) + { + Debug.Assert(aesKey.Length == KeySize, $"AES key expected to be {KeySize} bytes long"); - _encryptionCipher = new BufferedBlockCipher(new SicBlockCipher(aes)); - _encryptionCipher.Init(true, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", aesKey), new byte[BlockSize])); + var aes = new AesCtr(aesKey); - _decryptionCipher = new BufferedBlockCipher(new SicBlockCipher(aes)); - _decryptionCipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", aesKey), new byte[BlockSize])); - } + _encryptionCipher = aes.CreateEncryptor(); + _decryptionCipher = aes.CreateDecryptor(); + } - public void Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) - { - _encryptionCipher.ProcessBytes(input, offset, length, output, outputOffset); - } + public void Encrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + _encryptionCipher.TransformBlock(input, offset, length, output, outputOffset); + } - public void Decrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) - { - _decryptionCipher.ProcessBytes(input, offset, length, output, outputOffset); - } + public void Decrypt(byte[] input, int offset, int length, byte[] output, int outputOffset) + { + _decryptionCipher.TransformBlock(input, offset, length, output, outputOffset); } } From 6f230461accec45abb644e835c8e5a9944ae67a7 Mon Sep 17 00:00:00 2001 From: Ruben Buniatyan Date: Sat, 4 Feb 2023 00:12:31 +0100 Subject: [PATCH 2/2] Revise implementation --- src/Nethermind/Nethermind.Crypto/AesCtr.cs | 74 +++---------------- .../Nethermind.Crypto/AesCtrTransform.cs | 12 +-- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/src/Nethermind/Nethermind.Crypto/AesCtr.cs b/src/Nethermind/Nethermind.Crypto/AesCtr.cs index ea29be12f627..4b125ae1530a 100644 --- a/src/Nethermind/Nethermind.Crypto/AesCtr.cs +++ b/src/Nethermind/Nethermind.Crypto/AesCtr.cs @@ -13,55 +13,29 @@ namespace Nethermind.Crypto; public class AesCtr : SymmetricAlgorithm { private readonly Aes _aes; - private byte[] _nonce; - private readonly byte[] _zeroIV; /// /// Initializes a new instance of the AesCtr class - /// with a provided key and initialization vector (IV/nonce) if any. + /// with a provided key and initialization vector (IV) if any. /// /// The secret key to use for this instance. - /// The IV/nonce to use for this instance. + /// The IV to use for this instance. public AesCtr(byte[]? key = null, byte[]? iv = null) { _aes = Aes.Create(); + _aes.IV = iv ?? new byte[_aes.IV.Length]; _aes.Mode = CipherMode.ECB; _aes.Padding = PaddingMode.None; if (key is not null) _aes.Key = key; - - _zeroIV = new byte[_aes.BlockSize / 8]; - _aes.IV = _zeroIV; - IV = iv ?? _zeroIV; } - ///// - ///// Initializes a new instance of the AesCtr class with a provided instance. - ///// - ///// - ///// - ///// The aes parameter is null. - ///// - ///// The cipher mode or padding is invalid. - //public AesCtr(Aes aes) - //{ - // _aes = aes ?? throw new ArgumentNullException(nameof(aes)); - - // if (_aes.Mode != CipherMode.ECB) - // throw new CryptographicException("Invalid cipher mode."); - - // if (_aes.Padding != PaddingMode.None) - // throw new CryptographicException("Invalid padding mode."); - - // _zeroIV = new byte[_aes.BlockSize / 8]; - //} - public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV = null) => CreateEncryptor(rgbKey, rgbIV); public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV = null) - => new AesCtrTransform(_aes.CreateEncryptor(rgbKey, _zeroIV), rgbIV ?? _nonce); + => new AesCtrTransform(_aes.CreateEncryptor(rgbKey, rgbIV), rgbIV ?? _aes.IV); protected override void Dispose(bool disposing) { @@ -69,7 +43,7 @@ protected override void Dispose(bool disposing) _aes.Dispose(); } - public override void GenerateIV() => RandomNumberGenerator.Fill(_nonce); + public override void GenerateIV() => _aes.GenerateIV(); public override void GenerateKey() => _aes.GenerateKey(); @@ -85,19 +59,7 @@ public override int FeedbackSize set => throw new NotSupportedException(); } - public override byte[] IV - { - get => _nonce; - set - { - ArgumentNullException.ThrowIfNull(value); - - if (value.Length % (BlockSize / 8) != 0) - throw new CryptographicException("Specified initialization vector (IV) does not match the block size for this algorithm."); - - _nonce = value; - } - } + public override byte[] IV { get => _aes.IV; set => _aes.IV = value; } public override byte[] Key { get => _aes.Key; set => _aes.Key = value; } @@ -107,33 +69,21 @@ public override byte[] IV public override KeySizes[] LegalKeySizes => _aes.LegalKeySizes; + /// Gets the mode for operation of the symmetric algorithm. /// - /// Supports only. - /// + /// Setting the cipher mode is not supported. public override CipherMode Mode { get => _aes.Mode; - set - { - if (value != CipherMode.ECB) - throw new CryptographicException("Invalid cipher mode."); - - _aes.Mode = value; - } + set => throw new NotSupportedException(); } + /// Gets the padding mode used in the symmetric algorithm. /// - /// Supports only. - /// + /// Setting the padding mode is not supported. public override PaddingMode Padding { get => _aes.Padding; - set - { - if (value != PaddingMode.None) - throw new CryptographicException("Invalid padding mode."); - - _aes.Padding = value; - } + set => throw new NotSupportedException(); } } diff --git a/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs b/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs index f91acc09d6f0..b6153e392701 100644 --- a/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs +++ b/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs @@ -10,13 +10,13 @@ internal sealed class AesCtrTransform : ICryptoTransform { private readonly ICryptoTransform _aes; private readonly byte[] _counter; - private readonly byte[] _nonce; + private readonly byte[] _iv; - public AesCtrTransform(ICryptoTransform aes, ReadOnlySpan nonce) + public AesCtrTransform(ICryptoTransform aes, ReadOnlySpan iv) { _aes = aes ?? throw new ArgumentNullException(nameof(aes)); - _counter = nonce.ToArray(); - _nonce = nonce.ToArray(); + _counter = iv.ToArray(); + _iv = iv.ToArray(); } public bool CanReuseTransform => _aes.CanReuseTransform; @@ -82,7 +82,7 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input var offset = 0; var outputBuffer = new byte[inputCount]; - for (int i = 0, count = inputCount / blockSize; i < count; i += blockSize) + for (var i = 0; i + blockSize <= inputCount; i += blockSize) offset += TransformBlock(inputBuffer, inputOffset + i, blockSize, outputBuffer, offset); _aes.TransformBlock(_counter, 0, blockSize, counterOutput, 0); @@ -95,5 +95,5 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input return outputBuffer; } - private void Reset() => _nonce.CopyTo((Memory)_counter); + private void Reset() => _iv.CopyTo((Memory)_counter); }