diff --git a/src/Nethermind/Nethermind.Crypto/AesCtr.cs b/src/Nethermind/Nethermind.Crypto/AesCtr.cs new file mode 100644 index 000000000000..4b125ae1530a --- /dev/null +++ b/src/Nethermind/Nethermind.Crypto/AesCtr.cs @@ -0,0 +1,89 @@ +// 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; + + /// + /// Initializes a new instance of the AesCtr class + /// with a provided key and initialization vector (IV) if any. + /// + /// The secret key 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; + } + + 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, rgbIV), rgbIV ?? _aes.IV); + + protected override void Dispose(bool disposing) + { + if (disposing) + _aes.Dispose(); + } + + public override void GenerateIV() => _aes.GenerateIV(); + + 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 => _aes.IV; set => _aes.IV = 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; + + /// Gets the mode for operation of the symmetric algorithm. + /// + /// Setting the cipher mode is not supported. + public override CipherMode Mode + { + get => _aes.Mode; + set => throw new NotSupportedException(); + } + + /// Gets the padding mode used in the symmetric algorithm. + /// + /// Setting the padding mode is not supported. + public override PaddingMode Padding + { + get => _aes.Padding; + set => throw new NotSupportedException(); + } +} diff --git a/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs b/src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs new file mode 100644 index 000000000000..b6153e392701 --- /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[] _iv; + + public AesCtrTransform(ICryptoTransform aes, ReadOnlySpan iv) + { + _aes = aes ?? throw new ArgumentNullException(nameof(aes)); + _counter = iv.ToArray(); + _iv = iv.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 (var i = 0; i + blockSize <= inputCount; 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() => _iv.CopyTo((Memory)_counter); +} diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs index 67606f419e5b..5bbdaa4cecfc 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.That(decrypted, Is.EqualTo(message)); + } + + [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.That(decrypted, Is.EqualTo(message)); + + 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.That(decrypted, Is.EqualTo(message)); + } + + [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.That(encrypted2, Is.Not.EqualTo(encrypted1)); + } + + [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.That(decrypted, Is.EqualTo(message)); - } - - [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.That(decrypted, Is.EqualTo(message)); - - 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.That(decrypted, Is.EqualTo(message)); - } - - [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.That(encrypted2, Is.Not.EqualTo(encrypted1)); - } - - [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.That(decrypted, Is.EqualTo(message)); - - 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.That(decrypted, Is.EqualTo(message)); - } - - [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.That(messageClone, Is.EqualTo(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.That(decrypted, Is.EqualTo(message)); + + 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.That(decrypted, Is.EqualTo(message)); + } + + [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.That(messageClone, Is.EqualTo(message)); } } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs b/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs index 10cb64da1a9f..a9b74b03fd32 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/FrameCipher.cs @@ -2,41 +2,35 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Modes; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Security; +using System.Security.Cryptography; +using Nethermind.Crypto; namespace Nethermind.Network.Rlpx; public class FrameCipher : IFrameCipher { - private const int BlockSize = 16; 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 = AesUtilities.CreateEngine(); - 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); + _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); + _decryptionCipher.TransformBlock(input, offset, length, output, outputOffset); } }