Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions src/Nethermind/Nethermind.Crypto/AesCtr.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Represents an Advanced Encryption Standard (AES) key
/// to be used with the Counter (CTR) mode of operation.
/// </summary>
public class AesCtr : SymmetricAlgorithm
{
private readonly Aes _aes;

/// <summary>
/// Initializes a new instance of the AesCtr class
/// with a provided key and initialization vector (IV) if any.
/// </summary>
/// <param name="key">The secret key to use for this instance.</param>
/// <param name="iv">The IV to use for this instance.</param>
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; }

/// <summary>
/// The feedback size is not supported for the CTR cipher mode.
/// </summary>
/// <exception cref="NotSupportedException"/>
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;

/// <summary>Gets the mode for operation of the symmetric algorithm.</summary>
/// <returns><see cref="CipherMode.ECB"/></returns>
/// <exception cref="NotSupportedException">Setting the cipher mode is not supported.</exception>
public override CipherMode Mode
{
get => _aes.Mode;
set => throw new NotSupportedException();
}

/// <summary>Gets the padding mode used in the symmetric algorithm.</summary>
/// <returns><see cref="PaddingMode.None"/></returns>
/// <exception cref="NotSupportedException">Setting the padding mode is not supported.</exception>
public override PaddingMode Padding
{
get => _aes.Padding;
set => throw new NotSupportedException();
}
}
99 changes: 99 additions & 0 deletions src/Nethermind/Nethermind.Crypto/AesCtrTransform.cs
Original file line number Diff line number Diff line change
@@ -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<byte> 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<byte>)_counter);
}
167 changes: 83 additions & 84 deletions src/Nethermind/Nethermind.Network.Test/Rlpx/FrameCipherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Loading