diff --git a/AesExtra/AesCmac.cs b/AesExtra/AesCmac.cs index 63cadfe..6a25c13 100644 --- a/AesExtra/AesCmac.cs +++ b/AesExtra/AesCmac.cs @@ -6,6 +6,7 @@ using System.Buffers; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Runtime.Versioning; using System.Security.Cryptography; namespace Dorssel.Security.Cryptography; @@ -21,8 +22,35 @@ public sealed class AesCmac const int BLOCKSIZE = 16; // bytes const int BitsPerByte = 8; + #region CryptoConfig + static readonly object RegistrationLock = new(); + static bool TriedRegisterOnce; + + /// <summary> + /// Registers the <see cref="AesCmac"/> class with <see cref="CryptoConfig"/>, such that it can be created by name. + /// </summary> + /// <seealso cref="CryptoConfig.CreateFromName(string)"/> + /// <remarks> + /// <see cref="CryptoConfig"/> is not supported in browsers. + /// </remarks> +#if !NETSTANDARD2_0 + [UnsupportedOSPlatform("browser")] +#endif + public static void RegisterWithCryptoConfig() + { + lock (RegistrationLock) + { + if (!TriedRegisterOnce) + { + TriedRegisterOnce = true; + CryptoConfig.AddAlgorithm(typeof(AesCmac), nameof(AesCmac), typeof(AesCmac).FullName!); + } + } + } + /// <inheritdoc cref="KeyedHashAlgorithm.Create()" path="/summary" /> /// <returns>A new <see cref="AesCmac" /> instance.</returns> + [Obsolete("Use one of the constructors instead.")] public static new AesCmac Create() { return new AesCmac(); @@ -33,86 +61,196 @@ public sealed class AesCmac #if !NETSTANDARD2_0 [RequiresUnreferencedCode("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] #endif - public static new KeyedHashAlgorithm? Create(string algorithmName) + public static new AesCmac? Create(string algorithmName) + { + if (algorithmName is null) + { + throw new ArgumentNullException(nameof(algorithmName)); + } + // Our class is sealed, so there definitely is no other implementation. + return algorithmName == nameof(AesCmac) || algorithmName == typeof(AesCmac).FullName! ? new AesCmac() : null; + } + #endregion + + /// <exception cref="CryptographicException"><paramref name="keySize"/> is other than 128, 192, or 256 bits.</exception> + static void ThrowIfInvalidKeySize(int keySize) + { + if (keySize is not (128 or 192 or 256)) + { + throw new CryptographicException("Specified key size is valid for this algorithm."); + } + } + + /// <exception cref="CryptographicException">The <paramref name="key"/> length is other than 16, 24, or 32 bytes (128, 192, or 256 bits).</exception> + static void ThrowIfInvalidKey(ReadOnlySpan<byte> key) + { + if (key.Length is not (16 or 24 or 32)) + { + throw new CryptographicException("Specified key is not a valid size for this algorithm."); + } + } + + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="ThrowIfInvalidKey(ReadOnlySpan{byte})"/> + static void ThrowIfInvalidKey(byte[] key) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + ThrowIfInvalidKey(key.AsSpan()); + } + + void InitializeFixedValues() + { + HashSizeValue = BLOCKSIZE * 8; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCmac" /> class with a randomly generated 256-bit key. + /// </summary> + public AesCmac() + : this(256) { - return algorithmName == nameof(AesCmac) ? Create() : KeyedHashAlgorithm.Create(algorithmName); } /// <summary> /// Initializes a new instance of the <see cref="AesCmac" /> class with a randomly generated key. /// </summary> /// <param name="keySize">The size, in bits, of the randomly generated key.</param> - public AesCmac(int keySize = 256) + /// <inheritdoc cref="ThrowIfInvalidKeySize(int)"/> + public AesCmac(int keySize) { - AesEcb = Aes.Create(); - AesEcb.Mode = CipherMode.ECB; // DevSkim: ignore DS187371 - AesEcb.BlockSize = BLOCKSIZE * BitsPerByte; - AesEcb.KeySize = keySize; - HashSizeValue = BLOCKSIZE * 8; + ThrowIfInvalidKeySize(keySize); + + InitializeFixedValues(); + + KeyValue = new byte[keySize / BitsPerByte]; + using var rng = RandomNumberGenerator.Create(); + rng.GetBytes(KeyValue); } /// <summary> /// Initializes a new instance of the <see cref="AesCmac" /> class with the specified key data. /// </summary> /// <param name="key">The secret key for AES-CMAC algorithm.</param> + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="AesCmac(ReadOnlySpan{byte})" path="/exception"/> public AesCmac(byte[] key) - : this() + : this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key)))) { - Key = key; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCmac" /> class with the specified key data. + /// </summary> + /// <param name="key">The secret key for AES-CMAC algorithm.</param> + /// <inheritdoc cref="ThrowIfInvalidKey(ReadOnlySpan{byte})"/> + public AesCmac(ReadOnlySpan<byte> key) + { + ThrowIfInvalidKey(key); + + InitializeFixedValues(); + + KeyValue = key.ToArray(); } #region IDisposable + bool IsDisposed; + /// <inheritdoc cref="KeyedHashAlgorithm.Dispose(bool)" /> protected override void Dispose(bool disposing) { + if (IsDisposed) + { + return; + } + if (disposing) { - CryptographicOperations.ZeroMemory(KeyValue); - CryptographicOperations.ZeroMemory(K1Value); - CryptographicOperations.ZeroMemory(K2Value); - CryptographicOperations.ZeroMemory(C); - CryptographicOperations.ZeroMemory(Partial); - AesEcb.Dispose(); - CryptoTransformValue?.Dispose(); - CryptoTransformValue = null; - K1Value = null; - K2Value = null; - PartialLength = 0; - State = 0; + AesEcbTransformValue?.Dispose(); } + + CryptographicOperations.ZeroMemory(KeyValue); + CryptographicOperations.ZeroMemory(K1Value); + CryptographicOperations.ZeroMemory(K2Value); + CryptographicOperations.ZeroMemory(C); + CryptographicOperations.ZeroMemory(Partial); + K1Value = null; + K2Value = null; + PartialLength = 0; + State = 0; + AesEcbTransformValue = null; + IsDisposed = true; + base.Dispose(disposing); } #endregion + /// <exception cref="ObjectDisposedException">The <see cref="AesCmac"/> instance has been disposed.</exception> + void ThrowIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(AesCmac)); + } + } + /// <inheritdoc /> + /// <inheritdoc cref="ThrowIfInvalidKey(byte[])"/> + /// <inheritdoc cref="ThrowIfDisposed"/> + /// <exception cref="InvalidOperationException">An attempt was made to change the <see cref="Key"/> during a computation.</exception> public override byte[] Key { - get => AesEcb.Key; + get + { + ThrowIfDisposed(); + + return (byte[])KeyValue.Clone(); + } set { + // Input validation + + ThrowIfInvalidKey(value); + + // State validation + + ThrowIfDisposed(); + if (State != 0) { throw new InvalidOperationException("Key cannot be changed during a computation."); } - AesEcb.Key = value; + + // Side effects + + CryptographicOperations.ZeroMemory(KeyValue); CryptographicOperations.ZeroMemory(K1Value); CryptographicOperations.ZeroMemory(K2Value); - CryptoTransformValue?.Dispose(); - CryptoTransformValue = null; + AesEcbTransformValue?.Dispose(); + AesEcbTransformValue = null; K1Value = null; K2Value = null; + + KeyValue = (byte[])value.Clone(); } } - readonly Aes AesEcb; - ICryptoTransform? CryptoTransformValue; + ICryptoTransform? AesEcbTransformValue; - ICryptoTransform CryptoTransform + ICryptoTransform AesEcbTransform { get { - CryptoTransformValue ??= AesEcb.CreateEncryptor(); - return CryptoTransformValue; + if (AesEcbTransformValue is null) + { + using var aes = Aes.Create(); + aes.Mode = CipherMode.ECB; // DevSkim: ignore DS187371 + aes.BlockSize = BLOCKSIZE * BitsPerByte; + AesEcbTransformValue = aes.CreateEncryptor(KeyValue, null); + } + return AesEcbTransformValue; } } @@ -161,12 +299,15 @@ byte[] K2 [MethodImpl(MethodImplOptions.AggressiveInlining)] void CIPH_K_InPlace(byte[] X) { - _ = CryptoTransform.TransformBlock(X, 0, BLOCKSIZE, X, 0); + _ = AesEcbTransform.TransformBlock(X, 0, BLOCKSIZE, X, 0); } /// <inheritdoc cref="HashAlgorithm.Initialize" /> + /// <inheritdoc cref="ThrowIfDisposed"/> public override void Initialize() { + ThrowIfDisposed(); + // See: NIST SP 800-38B, Section 6.2, Step 5 C.AsSpan().Clear(); PartialLength = 0; @@ -187,6 +328,9 @@ void AddBlock(ReadOnlySpan<byte> block) /// <inheritdoc /> protected override void HashCore(byte[] array, int ibStart, int cbSize) { + // This is only called by HashAlgorithm, which is known to behave well. + // We skip input validation for performance reasons; there is no unsafe code. + UncheckedHashCore(array.AsSpan(ibStart, cbSize)); } @@ -196,6 +340,9 @@ protected override #endif void HashCore(ReadOnlySpan<byte> source) { + // This is only called by HashAlgorithm, which is known to behave well. + // We skip input validation for performance reasons; there is no unsafe code. + UncheckedHashCore(source); } @@ -258,6 +405,9 @@ protected override #endif bool TryHashFinal(Span<byte> destination, out int bytesWritten) { + // This is only called by HashAlgorithm, which promises to never call us with a destination that is too short. + // We skip input validation for performance reasons; there is no unsafe code. + UncheckedHashFinal(destination); bytesWritten = BLOCKSIZE; return true; @@ -345,26 +495,6 @@ static async ValueTask UncheckedOneShotAsync(byte[] key, Stream source, Memory<b } } - /// <exception cref="CryptographicException">The <paramref name="key"/> length is other than 16, 24, or 32 bytes (128, 192, or 256 bits).</exception> - static void ThrowIfInvalidKey(ReadOnlySpan<byte> key) - { - if (key.Length is not (16 or 24 or 32)) - { - throw new CryptographicException("Specified key is not a valid size for this algorithm.", nameof(key)); - } - } - - /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> - /// <inheritdoc cref="ThrowIfInvalidKey(ReadOnlySpan{byte})"/> - static void ThrowIfInvalidKey(byte[] key) - { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - ThrowIfInvalidKey(key.AsSpan()); - } - /// <exception cref="ArgumentNullException"><paramref name="source"/> is <see langword="null"/>.</exception> static void ThrowIfInvalidSource(byte[] source) { diff --git a/AesExtra/AesCtr.cs b/AesExtra/AesCtr.cs index cb96209..87c66e2 100644 --- a/AesExtra/AesCtr.cs +++ b/AesExtra/AesCtr.cs @@ -2,8 +2,8 @@ // // SPDX-License-Identifier: MIT -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Runtime.Versioning; using System.Security.Cryptography; namespace Dorssel.Security.Cryptography; @@ -21,7 +21,34 @@ public sealed class AesCtr const PaddingMode FixedPaddingValue = PaddingMode.None; const int FixedFeedbackSizeValue = BLOCKSIZE * BitsPerByte; + #region CryptoConfig + static readonly object RegistrationLock = new(); + static bool TriedRegisterOnce; + + /// <summary> + /// Registers the <see cref="AesCtr"/> class with <see cref="CryptoConfig"/>, such that it can be created by name. + /// </summary> + /// <seealso cref="CryptoConfig.CreateFromName(string)"/> + /// <remarks> + /// <see cref="CryptoConfig"/> is not supported in browsers. + /// </remarks> +#if !NETSTANDARD2_0 + [UnsupportedOSPlatform("browser")] +#endif + public static void RegisterWithCryptoConfig() + { + lock (RegistrationLock) + { + if (!TriedRegisterOnce) + { + TriedRegisterOnce = true; + CryptoConfig.AddAlgorithm(typeof(AesCtr), nameof(AesCtr), typeof(AesCtr).FullName!); + } + } + } + /// <inheritdoc cref="Aes.Create()" /> + [Obsolete("Use one of the constructors instead.")] public static new AesCtr Create() { return new AesCtr(); @@ -32,14 +59,72 @@ public sealed class AesCtr #if !NETSTANDARD2_0 [RequiresUnreferencedCode("The default algorithm implementations might be removed, use strong type references like 'RSA.Create()' instead.")] #endif - public static new Aes? Create(string algorithmName) + public static new AesCtr? Create(string algorithmName) + { + if (algorithmName is null) + { + throw new ArgumentNullException(nameof(algorithmName)); + } + // Our class is sealed, so there definitely is no other implementation. + return algorithmName == nameof(AesCtr) || algorithmName == typeof(AesCtr).FullName! ? new AesCtr() : null; + } + #endregion + + /// <exception cref="CryptographicException"><paramref name="keySize"/> is other than 128, 192, or 256 bits.</exception> + static void ThrowIfInvalidKeySize(int keySize) { - return algorithmName == nameof(AesCtr) ? Create() : Aes.Create(algorithmName); + if (keySize is not (128 or 192 or 256)) + { + throw new CryptographicException("Specified key size is valid for this algorithm."); + } } - AesCtr() + /// <exception cref="CryptographicException">The <paramref name="key"/> length is other than 16, 24, or 32 bytes (128, 192, or 256 bits).</exception> + static void ThrowIfInvalidKey(ReadOnlySpan<byte> key) + { + if (key.Length is not (16 or 24 or 32)) + { + throw new CryptographicException("Specified key is not a valid size for this algorithm."); + } + } + + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="ThrowIfInvalidKey(ReadOnlySpan{byte})"/> + static void ThrowIfInvalidKey(byte[] key, string argumentName = "key") + { + if (key is null) + { + throw new ArgumentNullException(argumentName); + } + ThrowIfInvalidKey(key.AsSpan()); + } + + /// <exception cref="ArgumentNullException"><paramref name="iv"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> + static void ThrowIfInvalidIV(byte[] iv, string argumentName = "iv") + { + if (iv is null) + { + throw new ArgumentNullException(argumentName); + } + ThrowIfInvalidIV(iv.AsSpan()); + } + + /// <exception cref="ArgumentException"> + /// <paramref name="iv"/> is the incorrect length. + /// Callers are expected to pass an initialization vector that is exactly <see cref="SymmetricAlgorithm.BlockSize"/> in length, + /// converted to bytes (`BlockSize / 8`). + /// </exception> + static void ThrowIfInvalidIV(ReadOnlySpan<byte> iv, string argumentName = "iv") + { + if (iv.Length != BLOCKSIZE) + { + throw new ArgumentException("Specified initial counter (IV) does not match the block size for this algorithm.", argumentName); + } + } + + void InitializeFixedValues() { - KeySizeValue = 256; ModeValue = FixedModeValue; PaddingValue = FixedPaddingValue; FeedbackSizeValue = FixedFeedbackSizeValue; @@ -48,18 +133,211 @@ public sealed class AesCtr LegalKeySizesValue = [new(128, 256, 64)]; } - #region IDisposable - /// <inheritdoc cref="SymmetricAlgorithm.Dispose(bool)" /> - protected override void Dispose(bool disposing) + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with a randomly generated 256-bit key and an initial counter of zero. + /// </summary> + public AesCtr() + : this(256) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with a randomly generated key and an initial counter of zero. + /// </summary> + /// <param name="keySize">The size, in bits, of the randomly generated key.</param> + /// <inheritdoc cref="ThrowIfInvalidKeySize(int)"/> + public AesCtr(int keySize) + { + ThrowIfInvalidKeySize(keySize); + + InitializeFixedValues(); + + KeySizeValue = keySize; + IVValue = new byte[BLOCKSIZE]; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with the specified key data and a randomly generated initial counter. + /// </summary> + /// <param name="key">The secret key for the AES-CTR algorithm.</param> + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="AesCtr(ReadOnlySpan{byte})" path="/exception"/> + public AesCtr(byte[] key) + : this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key)))) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with the specified key data and a randomly generated initial counter. + /// </summary> + /// <param name="key">The secret key for the AES-CTR algorithm.</param> + public AesCtr(ReadOnlySpan<byte> key) + { + ThrowIfInvalidKey(key); + + InitializeFixedValues(); + + KeyValue = key.ToArray(); + KeySizeValue = key.Length * BitsPerByte; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with the specified key data and initial counter. + /// </summary> + /// <param name="key">The secret key for the AES-CTR algorithm.</param> + /// <param name="iv">The initialization vector (initial counter).</param> + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> + /// <exception cref="ArgumentNullException"><paramref name="iv"/> is <see langword="null"/>.</exception> + /// <inheritdoc cref="AesCtr(ReadOnlySpan{byte}, ReadOnlySpan{byte})" path="/exception"/> + public AesCtr(byte[] key, byte[] iv) : + this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key))), + new ReadOnlySpan<byte>(iv ?? throw new ArgumentNullException(nameof(iv)))) + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="AesCtr" /> class with the specified key data and initial counter. + /// </summary> + /// <param name="key">The secret key for the AES-CTR algorithm.</param> + /// <param name="iv">The initialization vector (initial counter).</param> + /// <inheritdoc cref="ThrowIfInvalidKey(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> + public AesCtr(ReadOnlySpan<byte> key, ReadOnlySpan<byte> iv) + { + ThrowIfInvalidKey(key); + ThrowIfInvalidIV(iv); + + InitializeFixedValues(); + + KeyValue = key.ToArray(); + KeySizeValue = key.Length * BitsPerByte; + IVValue = iv.ToArray(); + } + + void PurgeKeyValue() { CryptographicOperations.ZeroMemory(KeyValue); KeyValue = null; + } + + void PurgeIVValue() + { CryptographicOperations.ZeroMemory(IVValue); IVValue = null; + } + + #region IDisposable + bool IsDisposed; + + /// <inheritdoc cref="SymmetricAlgorithm.Dispose(bool)" /> + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + PurgeKeyValue(); + PurgeIVValue(); + IsDisposed = true; + base.Dispose(disposing); } #endregion + /// <exception cref="ObjectDisposedException">The <see cref="AesCtr"/> instance has been disposed.</exception> + void ThrowIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(AesCtr)); + } + } + + /// <inheritdoc path="/summary"/> + /// <inheritdoc path="/returns"/> + /// <remarks> + /// Setting this property always resets the key to a new random value, even if the key size + /// is set to current value. + /// </remarks> + /// <inheritdoc cref="ThrowIfInvalidKeySize"/> + /// <inheritdoc cref="ThrowIfDisposed"/> + public override int KeySize + { + get + { + ThrowIfDisposed(); + + return KeySizeValue; + } + + set + { + ThrowIfInvalidKeySize(value); + + ThrowIfDisposed(); + + PurgeKeyValue(); + KeySizeValue = value; + } + } + + /// <inheritdoc path="/summary"/> + /// <inheritdoc path="/returns"/> + /// <inheritdoc cref="ThrowIfInvalidKey(byte[], string)"/> + /// <inheritdoc cref="ThrowIfDisposed"/> + public override byte[] Key + { + get + { + ThrowIfDisposed(); + + UncheckedGenerateKeyValueIfNull(); + return (byte[])KeyValue!.Clone(); + } + + set + { + ThrowIfInvalidKey(value, nameof(Key)); + + ThrowIfDisposed(); + + PurgeKeyValue(); + KeySizeValue = value.Length * 8; + + KeyValue = (byte[])value.Clone(); + } + } + + /// <inheritdoc path="/summary"/> + /// <inheritdoc path="/returns"/> + /// <remarks> + /// For AES-CTR, the initialization vector (IV) is the initial counter. + /// </remarks> + /// <inheritdoc cref="ThrowIfInvalidIV(byte[], string)"/> + /// <inheritdoc cref="ThrowIfDisposed"/> + public override byte[] IV + { + get + { + ThrowIfDisposed(); + + UncheckedGenerateIVValueIfNull(); + return (byte[])IVValue!.Clone(); + } + + set + { + ThrowIfInvalidIV(value, nameof(IV)); + + ThrowIfDisposed(); + + PurgeIVValue(); + + IVValue = (byte[])value.Clone(); + } + } + /// <inheritdoc cref="AesManaged.Mode" /> /// <remarks><see cref="AesCtr"/> always pretends to use <see cref="CipherMode.CTS" />.</remarks> public override CipherMode Mode @@ -102,41 +380,106 @@ public override int FeedbackSize } } - // CTR.Encrypt === CTR.Decrypt; the transform is entirely symmetric. - static AesCtrTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV) + /// <inheritdoc/> + /// <inheritdoc cref="ThrowIfDisposed"/> + public override ICryptoTransform CreateDecryptor() { - return rgbIV is not null ? new(rgbKey, rgbIV) - : throw new CryptographicException("The cipher mode specified requires that an initialization vector(IV) be used."); + ThrowIfDisposed(); + + UncheckedGenerateKeyValueIfNull(); + UncheckedGenerateIVValueIfNull(); + return new AesCtrTransform(KeyValue!, IVValue!); } - /// <inheritdoc cref="AesManaged.CreateDecryptor(byte[], byte[])" /> + /// <summary> + /// Creates a symmetric decryptor object with the specified key and initial counter (IV). + /// </summary> + /// <param name="rgbKey">The secret key to use for the symmetric algorithm.</param> + /// <param name="rgbIV">The initialization vector (initial counter).</param> + /// <returns>A symmetric decryptor object.</returns> + /// <inheritdoc cref="ThrowIfDisposed"/> + /// <inheritdoc cref="ThrowIfInvalidKey(byte[],string)"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateTransform(rgbKey, rgbIV); + ThrowIfDisposed(); + + ThrowIfInvalidKey(rgbKey, nameof(rgbKey)); + ThrowIfInvalidIV(rgbIV ?? throw new CryptographicException("The cipher mode specified requires that an initialization vector (IV) be used."), + nameof(rgbIV)); + + return new AesCtrTransform(rgbKey, rgbIV); } - /// <inheritdoc cref="AesManaged.CreateEncryptor(byte[], byte[])" /> + /// <inheritdoc/> + /// <inheritdoc cref="ThrowIfDisposed"/> + public override ICryptoTransform CreateEncryptor() + { + ThrowIfDisposed(); + + UncheckedGenerateKeyValueIfNull(); + UncheckedGenerateIVValueIfNull(); + return new AesCtrTransform(KeyValue!, IVValue!); + } + + /// <summary> + /// Creates a symmetric encryptor object with the specified key and initial counter (IV). + /// </summary> + /// <param name="rgbKey">The secret key to use for the symmetric algorithm.</param> + /// <param name="rgbIV">The initialization vector (initial counter).</param> + /// <returns>A symmetric encryptor object.</returns> + /// <inheritdoc cref="ThrowIfDisposed"/> + /// <inheritdoc cref="ThrowIfInvalidKey(byte[],string)"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV) { - return CreateTransform(rgbKey, rgbIV); + ThrowIfDisposed(); + + ThrowIfInvalidKey(rgbKey, nameof(rgbKey)); + ThrowIfInvalidIV(rgbIV ?? throw new CryptographicException("The cipher mode specified requires that an initialization vector (IV) be used."), + nameof(rgbIV)); + + return new AesCtrTransform(rgbKey, rgbIV); + } + + void UncheckedGenerateIVValueIfNull() + { + if (IVValue is null) + { + IVValue = new byte[BLOCKSIZE]; + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(IVValue); + } } /// <inheritdoc cref="AesManaged.GenerateIV" /> + /// <inheritdoc cref="ThrowIfDisposed"/> public override void GenerateIV() { - CryptographicOperations.ZeroMemory(IVValue); - using var randomNumberGenerator = RandomNumberGenerator.Create(); - IVValue = new byte[BLOCKSIZE]; - randomNumberGenerator.GetBytes(IVValue); + ThrowIfDisposed(); + + PurgeIVValue(); + UncheckedGenerateIVValueIfNull(); + } + + void UncheckedGenerateKeyValueIfNull() + { + if (KeyValue is null) + { + KeyValue = new byte[KeySizeValue / BitsPerByte]; + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(KeyValue); + } } /// <inheritdoc cref="AesManaged.GenerateKey" /> + /// <inheritdoc cref="ThrowIfDisposed"/> public override void GenerateKey() { - CryptographicOperations.ZeroMemory(KeyValue); - using var randomNumberGenerator = RandomNumberGenerator.Create(); - KeyValue = new byte[KeySize / BitsPerByte]; - randomNumberGenerator.GetBytes(KeyValue); + ThrowIfDisposed(); + + PurgeKeyValue(); + UncheckedGenerateKeyValueIfNull(); } #region Modern_SymmetricAlgorithm @@ -149,30 +492,6 @@ static void ThrowIfInvalidInput(byte[] input) } } - /// <exception cref="ArgumentNullException"><paramref name="iv"/> is <see langword="null"/>.</exception> - /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte})"/> - static void ThrowIfInvalidIV(byte[] iv) - { - if (iv is null) - { - throw new ArgumentNullException(nameof(iv)); - } - ThrowIfInvalidIV(iv.AsSpan()); - } - - /// <exception cref="ArgumentException"> - /// <paramref name="iv"/> is the incorrect length. - /// Callers are expected to pass an initialization vector that is exactly <see cref="SymmetricAlgorithm.BlockSize"/> in length, - /// converted to bytes (`BlockSize / 8`). - /// </exception> - static void ThrowIfInvalidIV(ReadOnlySpan<byte> iv) - { - if (iv.Length != BLOCKSIZE) - { - throw new ArgumentException("Specified initial counter (IV) does not match the block size for this algorithm.", nameof(iv)); - } - } - /// <exception cref="ArgumentException">The buffer in <paramref name="destination"/> is too small to hold the transformed data.</exception> static void ThrowIfInvalidDestination(Span<byte> destination, int requiredLength) { @@ -189,14 +508,17 @@ static void ThrowIfInvalidDestination(Span<byte> destination, int requiredLength /// <param name="iv">The initialization vector (initial counter).</param> /// <returns>The transformed data.</returns> /// <inheritdoc cref="ThrowIfInvalidInput(byte[])"/> - /// <inheritdoc cref="ThrowIfInvalidIV(byte[])"/> + /// <inheritdoc cref="ThrowIfInvalidIV(byte[],string)"/> public byte[] TransformCtr(byte[] input, byte[] iv) { ThrowIfInvalidInput(input); ThrowIfInvalidIV(iv); + ThrowIfDisposed(); + var output = new byte[input.Length]; - using var transform = new AesCtrTransform(Key, iv); + UncheckedGenerateKeyValueIfNull(); + using var transform = new AesCtrTransform(KeyValue!, iv); transform.UncheckedTransform(input, output); return output; } @@ -207,13 +529,16 @@ public byte[] TransformCtr(byte[] input, byte[] iv) /// <param name="input">The data to transform.</param> /// <param name="iv">The initialization vector (initial counter).</param> /// <returns>The transformed data.</returns> - /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> public byte[] TransformCtr(ReadOnlySpan<byte> input, ReadOnlySpan<byte> iv) { ThrowIfInvalidIV(iv); + ThrowIfDisposed(); + var output = new byte[input.Length]; - using var transform = new AesCtrTransform(Key, iv); + UncheckedGenerateKeyValueIfNull(); + using var transform = new AesCtrTransform(KeyValue!, iv); transform.UncheckedTransform(input, output); return output; } @@ -225,14 +550,17 @@ public byte[] TransformCtr(ReadOnlySpan<byte> input, ReadOnlySpan<byte> iv) /// <param name="iv">The initialization vector (initial counter).</param> /// <param name="destination">The buffer to receive the transformed data.</param> /// <returns>The total number of bytes written to <paramref name="destination"/>.</returns> - /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> /// <inheritdoc cref="ThrowIfInvalidDestination(Span{byte}, int)"/> public int TransformCtr(ReadOnlySpan<byte> input, ReadOnlySpan<byte> iv, Span<byte> destination) { ThrowIfInvalidIV(iv); ThrowIfInvalidDestination(destination, input.Length); - using var transform = new AesCtrTransform(Key, iv); + ThrowIfDisposed(); + + UncheckedGenerateKeyValueIfNull(); + using var transform = new AesCtrTransform(KeyValue!, iv); transform.UncheckedTransform(input, destination); return input.Length; } @@ -247,18 +575,21 @@ public int TransformCtr(ReadOnlySpan<byte> input, ReadOnlySpan<byte> iv, Span<by /// <returns> /// <see langword="true"/> if <paramref name="destination"/> was large enough to receive the transformed data; otherwise, <see langword="false"/>. /// </returns> - /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfInvalidIV(ReadOnlySpan{byte},string)"/> public bool TryTransformCtr(ReadOnlySpan<byte> input, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten) { ThrowIfInvalidIV(iv); + ThrowIfDisposed(); + if (destination.Length < input.Length) { bytesWritten = 0; return false; } - using var transform = new AesCtrTransform(Key, iv); + UncheckedGenerateKeyValueIfNull(); + using var transform = new AesCtrTransform(KeyValue!, iv); transform.UncheckedTransform(input, destination); bytesWritten = input.Length; return true; diff --git a/AesExtra/AesSiv.cs b/AesExtra/AesSiv.cs index de7d98c..e856b8f 100644 --- a/AesExtra/AesSiv.cs +++ b/AesExtra/AesSiv.cs @@ -17,7 +17,7 @@ public sealed class AesSiv /// Initializes a new instance of the <see cref="AesSiv"/> class with a provided key. /// </summary> /// <param name="key">The secret key to use for this instance.</param> - /// <exception cref="ArgumentNullException" /> + /// <exception cref="ArgumentNullException"><paramref name="key"/> is <see langword="null"/>.</exception> public AesSiv(byte[] key) : this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key)))) { @@ -31,7 +31,7 @@ public AesSiv(ReadOnlySpan<byte> key) { if (key.Length is not (32 or 48 or 64)) { - throw new CryptographicException("Specified key is not a valid size for this algorithm.", nameof(key)); + throw new CryptographicException("Specified key is not a valid size for this algorithm."); } using var cmacKey = new SecureByteArray(key[..(key.Length / 2)]); @@ -64,6 +64,7 @@ public void Dispose() } #endregion + /// <exception cref="ObjectDisposedException">The <see cref="AesSiv"/> instance has been disposed.</exception> void ThrowIfDisposed() { if (IsDisposed) @@ -219,6 +220,7 @@ static void ThrowIfInvalidAssociatedData(ReadOnlySpan<ReadOnlyMemory<byte>> asso /// <inheritdoc cref="ThrowIfInvalidCiphertext(byte[])"/> /// <inheritdoc cref="ThrowIfInvalidCiphertext(Span{byte}, ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidAssociatedData(byte[][])"/> + /// <inheritdoc cref="ThrowIfDisposed"/> public void Encrypt(byte[] plaintext, byte[] ciphertext, params byte[][] associatedData) { // Input validation @@ -258,6 +260,7 @@ public void Encrypt(byte[] plaintext, byte[] ciphertext, params byte[][] associa /// <param name="ciphertext">The byte array to receive the encrypted contents, prepended with the synthetic IV.</param> /// <param name="associatedData">Extra data associated with this message, which must also be provided during decryption.</param> /// <inheritdoc cref="ThrowIfInvalidCiphertext(Span{byte}, ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfDisposed"/> public void Encrypt(ReadOnlySpan<byte> plaintext, Span<byte> ciphertext, ReadOnlySpan<byte> associatedData) { // Input validation @@ -289,6 +292,7 @@ public void Encrypt(ReadOnlySpan<byte> plaintext, Span<byte> ciphertext, ReadOnl /// <param name="associatedData">Extra data associated with this message, which must also be provided during decryption.</param> /// <inheritdoc cref="ThrowIfInvalidCiphertext(Span{byte}, ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidAssociatedData(ReadOnlySpan{ReadOnlyMemory{byte}})"/> + /// <inheritdoc cref="ThrowIfDisposed"/> public void Encrypt(ReadOnlySpan<byte> plaintext, Span<byte> ciphertext, params ReadOnlySpan<ReadOnlyMemory<byte>> associatedData) { // Input validation @@ -326,6 +330,7 @@ public void Encrypt(ReadOnlySpan<byte> plaintext, Span<byte> ciphertext, params /// <inheritdoc cref="ThrowIfInvalidPlaintext(byte[])"/> /// <inheritdoc cref="ThrowIfInvalidPlaintext(Span{byte}, ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidAssociatedData(byte[][])"/> + /// <inheritdoc cref="ThrowIfDisposed"/> /// <exception cref="CryptographicException">The tag value could not be verified.</exception> public void Decrypt(byte[] ciphertext, byte[] plaintext, params byte[][] associatedData) { @@ -373,6 +378,7 @@ public void Decrypt(byte[] ciphertext, byte[] plaintext, params byte[][] associa /// <param name="associatedData">Extra data associated with this message, which must match the value provided during encryption.</param> /// <inheritdoc cref="ThrowIfInvalidCiphertext(ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidPlaintext(Span{byte}, ReadOnlySpan{byte})"/> + /// <inheritdoc cref="ThrowIfDisposed"/> /// <exception cref="CryptographicException">The tag value could not be verified.</exception> public void Decrypt(ReadOnlySpan<byte> ciphertext, Span<byte> plaintext, ReadOnlySpan<byte> associatedData) { @@ -413,6 +419,7 @@ public void Decrypt(ReadOnlySpan<byte> ciphertext, Span<byte> plaintext, ReadOnl /// <inheritdoc cref="ThrowIfInvalidCiphertext(ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidPlaintext(Span{byte}, ReadOnlySpan{byte})"/> /// <inheritdoc cref="ThrowIfInvalidAssociatedData(ReadOnlySpan{ReadOnlyMemory{byte}})"/> + /// <inheritdoc cref="ThrowIfDisposed"/> /// <exception cref="CryptographicException">The tag value could not be verified.</exception> public void Decrypt(ReadOnlySpan<byte> ciphertext, Span<byte> plaintext, params ReadOnlySpan<ReadOnlyMemory<byte>> associatedData) { diff --git a/UnitTests/AesCmac_KAT.cs b/UnitTests/AesCmac_KAT.cs index 84c971a..d1eb952 100644 --- a/UnitTests/AesCmac_KAT.cs +++ b/UnitTests/AesCmac_KAT.cs @@ -12,8 +12,7 @@ sealed class AesCmac_KAT [NistAesCmacSampleDataSource] public void NistExample_ComputeHash(NistAesCmacSampleTestVector testVector) { - using var aesCmac = AesCmac.Create(); - aesCmac.Key = testVector.Key.ToArray(); + using var aesCmac = new AesCmac(testVector.Key.Span); var tag = aesCmac.ComputeHash(testVector.PT.ToArray()); CollectionAssert.AreEqual(testVector.Tag.ToArray(), tag); } diff --git a/UnitTests/AesCmac_Tests.cs b/UnitTests/AesCmac_Tests.cs index 8a38ea4..0dcde0a 100644 --- a/UnitTests/AesCmac_Tests.cs +++ b/UnitTests/AesCmac_Tests.cs @@ -68,20 +68,48 @@ public override void Write(byte[] buffer, int offset, int count) } } + [TestMethod] + public void RegisterWithCryptoConfig() + { + AesCmac.RegisterWithCryptoConfig(); + using var aesCmac = (AesCmac?)CryptoConfig.CreateFromName("AesCmac"); + Assert.IsNotNull(aesCmac); + } + + [TestMethod] + public void RegisterWithCryptoConfig_Twice() + { + AesCmac.RegisterWithCryptoConfig(); + AesCmac.RegisterWithCryptoConfig(); + using var aesCmac = (AesCmac?)CryptoConfig.CreateFromName("Dorssel.Security.Cryptography.AesCmac"); + Assert.IsNotNull(aesCmac); + } + [TestMethod] public void Create() { - using var keyedHashAlgorithm = AesCmac.Create(); - Assert.IsNotNull(keyedHashAlgorithm); +#pragma warning disable CS0618 // Type or member is obsolete + using var aesCmac = AesCmac.Create(); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(aesCmac); } [TestMethod] public void Create_Name() { #pragma warning disable CS0618 // Type or member is obsolete - using var keyedHashAlgorithm = AesCmac.Create("AesCmac"); + using var aesCmac = AesCmac.Create("AesCmac"); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(aesCmac); + } + + [TestMethod] + public void Create_FullName() + { +#pragma warning disable CS0618 // Type or member is obsolete + using var aesCmac = AesCmac.Create("Dorssel.Security.Cryptography.AesCmac"); #pragma warning restore CS0618 // Type or member is obsolete - Assert.IsNotNull(keyedHashAlgorithm); + Assert.IsNotNull(aesCmac); } [TestMethod] @@ -90,7 +118,7 @@ public void Create_NullNameFails() Assert.ThrowsException<ArgumentNullException>(() => { #pragma warning disable CS0618 // Type or member is obsolete - using var keyedHashAlgorithm = AesCmac.Create(null!); + using var aesCmac = AesCmac.Create(null!); #pragma warning restore CS0618 // Type or member is obsolete }); } @@ -99,37 +127,70 @@ public void Create_NullNameFails() public void Create_OtherNameReturnsNull() { #pragma warning disable CS0618 // Type or member is obsolete - using var keyedHashAlgorithm = AesCmac.Create("SomeOtherName"); + using var aesCmac = AesCmac.Create("SomeOtherName"); #pragma warning restore CS0618 // Type or member is obsolete - Assert.IsNull(keyedHashAlgorithm); + Assert.IsNull(aesCmac); } [TestMethod] public void Constructor_Default() { using var aesCmac = new AesCmac(); + + Assert.AreEqual(256, aesCmac.Key.Length * 8); + CollectionAssert.AreNotEqual(new byte[aesCmac.Key.Length], aesCmac.Key); } [TestMethod] [DataRow(128)] [DataRow(192)] [DataRow(256)] - public void Constructor_WithKey(int keySize) + public void Constructor_Int(int keySize) + { + using var aesCmac = new AesCmac(keySize); + + Assert.AreEqual(keySize, aesCmac.Key.Length * 8); + CollectionAssert.AreNotEqual(new byte[aesCmac.Key.Length], aesCmac.Key); + } + + [TestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_Int_Invalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aesCmac = new AesCmac(keySize); + }); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_Array(int keySize) { using var aesCmac = new AesCmac(new byte[keySize / 8]); } [TestMethod] - public void Constructor_WithInvalidKeySize() + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_Array_Invalid(int keySize) { Assert.ThrowsException<CryptographicException>(() => { - using var aesCmac = new AesCmac(new byte[42]); + using var aesCmac = new AesCmac(new byte[keySize / 8]); }); } [TestMethod] - public void Constructor_WithNullKey() + public void Constructor_Array_Null() { Assert.ThrowsException<ArgumentNullException>(() => { @@ -137,6 +198,28 @@ public void Constructor_WithNullKey() }); } + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_ReadOnlySpan(int keySize) + { + using var aesCmac = new AesCmac(new byte[keySize / 8].AsSpan()); + } + + [TestMethod] + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_ReadOnlySpan_Invalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aesCmac = new AesCmac(new byte[keySize / 8].AsSpan()); + }); + } + [TestMethod] public void Dispose() { @@ -179,6 +262,18 @@ public void Key_ChangeWhileBusy() }); } + [TestMethod] + public void Key_ChangeAfterDispose() + { + using var aesCmac = new AesCmac(); + aesCmac.Dispose(); + + Assert.ThrowsException<ObjectDisposedException>(() => + { + aesCmac.Key = TestKey; + }); + } + [TestMethod] public void ComputeHash_Segmented() { diff --git a/UnitTests/AesCtr_KAT.cs b/UnitTests/AesCtr_KAT.cs index 0a98408..e36af73 100644 --- a/UnitTests/AesCtr_KAT.cs +++ b/UnitTests/AesCtr_KAT.cs @@ -12,9 +12,7 @@ sealed class AesCtr_KAT [NistAesCtrSampleDataSource] public void Encrypt_Write(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); - aes.IV = testVector.InitialCounter.ToArray(); + using var aes = new AesCtr(testVector.Key.Span, testVector.InitialCounter.Span); using var plaintextStream = new MemoryStream(testVector.Plaintext.ToArray()); using var ciphertextStream = new MemoryStream(); { @@ -30,9 +28,7 @@ public void Encrypt_Write(NistAesCtrSampleTestVector testVector) [NistAesCtrSampleDataSource] public void Encrypt_Read(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); - aes.IV = testVector.InitialCounter.ToArray(); + using var aes = new AesCtr(testVector.Key.Span, testVector.InitialCounter.Span); using var plaintextStream = new MemoryStream(testVector.Plaintext.ToArray()); using var ciphertextStream = new MemoryStream(); { @@ -48,9 +44,7 @@ public void Encrypt_Read(NistAesCtrSampleTestVector testVector) [NistAesCtrSampleDataSource] public void Decrypt_Write(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); - aes.IV = testVector.InitialCounter.ToArray(); + using var aes = new AesCtr(testVector.Key.Span, testVector.InitialCounter.Span); using var ciphertextStream = new MemoryStream(testVector.Ciphertext.ToArray()); using var plaintextStream = new MemoryStream(); { @@ -66,9 +60,7 @@ public void Decrypt_Write(NistAesCtrSampleTestVector testVector) [NistAesCtrSampleDataSource] public void Decrypt_Read(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); - aes.IV = testVector.InitialCounter.ToArray(); + using var aes = new AesCtr(testVector.Key.Span, testVector.InitialCounter.Span); using var ciphertextStream = new MemoryStream(testVector.Ciphertext.ToArray()); using var plaintextStream = new MemoryStream(); { @@ -84,8 +76,7 @@ public void Decrypt_Read(NistAesCtrSampleTestVector testVector) [NistAesCtrSampleDataSource] public void Encrypt_TransformCtr_Array_Array(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = aes.TransformCtr(testVector.Plaintext.ToArray(), testVector.InitialCounter.ToArray()); @@ -97,8 +88,7 @@ public void Encrypt_TransformCtr_Array_Array(NistAesCtrSampleTestVector testVect [NistAesCtrSampleDataSource] public void Encrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = aes.TransformCtr(testVector.Plaintext.Span, testVector.InitialCounter.Span); @@ -110,8 +100,7 @@ public void Encrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan(NistAesCtrSampleTestV [NistAesCtrSampleDataSource] public void Encrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan_Span(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = new byte[testVector.Ciphertext.Length]; var count = aes.TransformCtr(testVector.Plaintext.Span, testVector.InitialCounter.Span, destination); @@ -125,8 +114,7 @@ public void Encrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan_Span(NistAesCtrSample [NistAesCtrSampleDataSource] public void Encrypt_TryTransformCtr(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = new byte[testVector.Ciphertext.Length]; var success = aes.TryTransformCtr(testVector.Plaintext.Span, testVector.InitialCounter.Span, destination, out var bytesWritten); @@ -141,8 +129,7 @@ public void Encrypt_TryTransformCtr(NistAesCtrSampleTestVector testVector) [NistAesCtrSampleDataSource] public void Decrypt_TransformCtr_Array_Array(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = aes.TransformCtr(testVector.Ciphertext.ToArray(), testVector.InitialCounter.ToArray()); @@ -154,8 +141,7 @@ public void Decrypt_TransformCtr_Array_Array(NistAesCtrSampleTestVector testVect [NistAesCtrSampleDataSource] public void Decrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = aes.TransformCtr(testVector.Ciphertext.Span, testVector.InitialCounter.Span); @@ -167,8 +153,7 @@ public void Decrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan(NistAesCtrSampleTestV [NistAesCtrSampleDataSource] public void Decrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan_Span(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = new byte[testVector.Plaintext.Length]; var count = aes.TransformCtr(testVector.Ciphertext.Span, testVector.InitialCounter.Span, destination); @@ -182,8 +167,7 @@ public void Decrypt_TransformCtr_ReadOnlySpan_ReadOnlySpan_Span(NistAesCtrSample [NistAesCtrSampleDataSource] public void Decrypt_TryTransformCtr(NistAesCtrSampleTestVector testVector) { - using var aes = AesCtr.Create(); - aes.Key = testVector.Key.ToArray(); + using var aes = new AesCtr(testVector.Key.Span); var destination = new byte[testVector.Plaintext.Length]; var success = aes.TryTransformCtr(testVector.Ciphertext.Span, testVector.InitialCounter.Span, destination, out var bytesWritten); diff --git a/UnitTests/AesCtr_Tests.cs b/UnitTests/AesCtr_Tests.cs index b891a67..8f48aeb 100644 --- a/UnitTests/AesCtr_Tests.cs +++ b/UnitTests/AesCtr_Tests.cs @@ -24,15 +24,38 @@ sealed class AesCtr_Tests static readonly byte[] TestMessage = [1, 2, 3, 4, 5]; - static readonly byte[] TestInvalidIV = new byte[BLOCKSIZE - 1]; + static readonly byte[] TestIVInvalid = new byte[BLOCKSIZE - 1]; + + static byte[] TestKeyNull => null!; + + static byte[] TestIVNull => null!; [TestMethod] - public void Create() + public void RegisterWithCryptoConfig() + { + AesCtr.RegisterWithCryptoConfig(); + using var aes = (AesCtr?)CryptoConfig.CreateFromName("AesCtr"); + Assert.IsNotNull(aes); + } + + [TestMethod] + public void RegisterWithCryptoConfig_Twice() { - using var aes = AesCtr.Create(); + AesCtr.RegisterWithCryptoConfig(); + AesCtr.RegisterWithCryptoConfig(); + using var aes = (AesCtr?)CryptoConfig.CreateFromName("Dorssel.Security.Cryptography.AesCtr"); Assert.IsNotNull(aes); } + [TestMethod] + public void Create() + { +#pragma warning disable CS0618 // Type or member is obsolete + using var aesCtr = AesCtr.Create(); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(aesCtr); + } + [TestMethod] public void Create_Name() { @@ -42,6 +65,16 @@ public void Create_Name() Assert.IsNotNull(aes); } + [TestMethod] + public void Create_FullName() + { +#pragma warning disable CS0618 // Type or member is obsolete + using var aes = AesCtr.Create("Dorssel.Security.Cryptography.AesCtr"); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.IsNotNull(aes); + } + + [TestMethod] public void Create_NullNameFails() { @@ -62,17 +95,205 @@ public void Create_OtherNameReturnsNull() Assert.IsNull(aes); } + [TestMethod] + public void Constructor() + { + using var aes = new AesCtr(); + + Assert.AreEqual(256, aes.KeySize); + Assert.AreEqual(256 / 8, aes.Key.Length); + CollectionAssert.AreNotEqual(new byte[aes.Key.Length], aes.Key); + Assert.AreEqual(256, aes.KeySize); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_Int(int keySize) + { + using var aes = new AesCtr(keySize); + + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize / 8, aes.Key.Length); + CollectionAssert.AreNotEqual(new byte[aes.Key.Length], aes.Key); + Assert.AreEqual(keySize, aes.KeySize); + } + + [TestMethod] + [DataRow(0)] + [DataRow(1)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_Int_Invalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aes = new AesCtr(keySize); + }); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_Array(int keySize) + { + using var aes = new AesCtr(new byte[keySize / 8]); + + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize / 8, aes.Key.Length); + Assert.AreEqual(keySize, aes.KeySize); + } + + [TestMethod] + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_Array_Invalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aes = new AesCtr(new byte[keySize / 8]); + }); + } + + [TestMethod] + public void Constructor_Array_Null() + { + Assert.ThrowsException<ArgumentNullException>(() => + { + using var aes = new AesCtr(TestKeyNull); + }); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_ReadOnlySpan(int keySize) + { + using var aes = new AesCtr(new byte[keySize / 8].AsSpan()); + + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize / 8, aes.Key.Length); + Assert.AreEqual(keySize, aes.KeySize); + } + + [TestMethod] + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_ReadOnlySpan_Invalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aes = new AesCtr(new byte[keySize / 8].AsSpan()); + }); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_Array_Array(int keySize) + { + using var aes = new AesCtr(new byte[keySize / 8], TestIV); + + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize / 8, aes.Key.Length); + Assert.AreEqual(keySize, aes.KeySize); + } + + [TestMethod] + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_Array_Array_KeyInvalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aes = new AesCtr(new byte[keySize / 8], TestIV); + }); + } + + [TestMethod] + public void Constructor_Array_Array_KeyNull() + { + Assert.ThrowsException<ArgumentNullException>(() => + { + using var aes = new AesCtr(TestKeyNull, TestIV); + }); + } + + [TestMethod] + public void Constructor_Array_Array_IVInvalid() + { + Assert.ThrowsException<ArgumentException>(() => + { + using var aes = new AesCtr(TestKey, TestIVInvalid); + }); + } + + [TestMethod] + public void Constructor_Array_Array_IVNull() + { + Assert.ThrowsException<ArgumentNullException>(() => + { + using var aes = new AesCtr(TestKey, TestIVNull); + }); + } + + [TestMethod] + [DataRow(128)] + [DataRow(192)] + [DataRow(256)] + public void Constructor_ReadOnlySpan_ReadOnlySpan(int keySize) + { + using var aes = new AesCtr(new byte[keySize / 8].AsSpan(), TestIV.AsSpan()); + + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize / 8, aes.Key.Length); + Assert.AreEqual(keySize, aes.KeySize); + } + + [TestMethod] + [DataRow(0)] + [DataRow(16)] + [DataRow(24)] + [DataRow(32)] + public void Constructor_ReadOnlySpan_ReadOnlySpan_KeyInvalid(int keySize) + { + Assert.ThrowsException<CryptographicException>(() => + { + using var aes = new AesCtr(new byte[keySize / 8].AsSpan(), TestIV.AsSpan()); + }); + } + + [TestMethod] + public void Constructor_ReadOnlySpan_ReadOnlySpan_IVInvalid() + { + Assert.ThrowsException<ArgumentException>(() => + { + using var aes = new AesCtr(TestKey.AsSpan(), TestIVInvalid.AsSpan()); + }); + } + [TestMethod] public void Dispose() { - var aes = AesCtr.Create(); + var aes = new AesCtr(); aes.Dispose(); } [TestMethod] public void Dispose_Double() { - var aes = AesCtr.Create(); + var aes = new AesCtr(); aes.Dispose(); aes.Dispose(); } @@ -80,7 +301,7 @@ public void Dispose_Double() [TestMethod] public void Mode_SetUnchanged() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.AreEqual(CipherMode.CTS, aes.Mode); // DevSkim: ignore DS187371 aes.Mode = CipherMode.CTS; // DevSkim: ignore DS187371 Assert.AreEqual(CipherMode.CTS, aes.Mode); // DevSkim: ignore DS187371 @@ -89,7 +310,7 @@ public void Mode_SetUnchanged() [TestMethod] public void Mode_CannotChange() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.AreEqual(CipherMode.CTS, aes.Mode); // DevSkim: ignore DS187371 Assert.ThrowsException<CryptographicException>(() => { @@ -101,7 +322,7 @@ public void Mode_CannotChange() [TestMethod] public void Padding_SetUnchanged() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.AreEqual(PaddingMode.None, aes.Padding); aes.Padding = PaddingMode.None; Assert.AreEqual(PaddingMode.None, aes.Padding); @@ -110,7 +331,7 @@ public void Padding_SetUnchanged() [TestMethod] public void Padding_CannotChange() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var padding = aes.Padding; Assert.AreEqual(PaddingMode.None, padding); Assert.ThrowsException<CryptographicException>(() => @@ -123,7 +344,7 @@ public void Padding_CannotChange() [TestMethod] public void FeedbackSize_SetUnchanged() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.AreEqual(aes.BlockSize, aes.FeedbackSize); aes.FeedbackSize = aes.BlockSize; Assert.AreEqual(aes.BlockSize, aes.FeedbackSize); @@ -132,7 +353,7 @@ public void FeedbackSize_SetUnchanged() [TestMethod] public void FeedbackSize_CannotChange() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.AreEqual(aes.BlockSize, aes.FeedbackSize); Assert.ThrowsException<CryptographicException>(() => { @@ -144,7 +365,7 @@ public void FeedbackSize_CannotChange() [TestMethod] public void KeySize_AllValid() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); foreach (var legalKeySize in aes.LegalKeySizes) { for (var keySize = legalKeySize.MinSize; keySize <= legalKeySize.MaxSize; keySize += Math.Max(legalKeySize.SkipSize, 1)) @@ -156,10 +377,80 @@ public void KeySize_AllValid() } } + [TestMethod] + public void Key_AllValid() + { + using var aes = new AesCtr(); + foreach (var legalKeySize in aes.LegalKeySizes) + { + for (var keySize = legalKeySize.MinSize; keySize <= legalKeySize.MaxSize; keySize += Math.Max(legalKeySize.SkipSize, 1)) + { + aes.Key = new byte[keySize / 8]; + Assert.AreEqual(keySize, aes.KeySize); + Assert.AreEqual(keySize, aes.Key.Length * 8); + } + } + } + + [TestMethod] + public void Key_Null() + { + using var aes = new AesCtr(); + + Assert.ThrowsException<ArgumentNullException>(() => + { + aes.Key = TestKeyNull; + }); + } + + [TestMethod] + public void Key_AfterDispose() + { + using var aes = new AesCtr(); + aes.Dispose(); + + Assert.ThrowsException<ObjectDisposedException>(() => + { + aes.Key = TestKey; + }); + } + + [TestMethod] + public void IV() + { + using var aes = new AesCtr(); + + aes.IV = TestIV; + CollectionAssert.AreEqual(TestIV, aes.IV); + } + + [TestMethod] + public void IV_Null() + { + using var aes = new AesCtr(); + + Assert.ThrowsException<ArgumentNullException>(() => + { + aes.IV = TestIVNull; + }); + } + + [TestMethod] + public void IV_AfterDispose() + { + using var aes = new AesCtr(); + aes.Dispose(); + + Assert.ThrowsException<ObjectDisposedException>(() => + { + aes.IV = TestIV; + }); + } + [TestMethod] public void BlockSize_AllValid() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); foreach (var legalBlockSize in aes.LegalBlockSizes) { for (var blockSize = legalBlockSize.MinSize; blockSize <= legalBlockSize.MaxSize; blockSize += Math.Max(legalBlockSize.SkipSize, 1)) @@ -176,7 +467,7 @@ public void BlockSize_AllValid() [DataRow(256)] public void GenerateIV_HasCorrectLength(int keySize) { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); aes.KeySize = keySize; aes.GenerateIV(); Assert.AreEqual(aes.BlockSize, aes.IV.Length * 8); @@ -188,7 +479,7 @@ public void GenerateIV_HasCorrectLength(int keySize) [DataRow(256)] public void GenerateKey_HasCorrectLength(int keySize) { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); aes.KeySize = keySize; aes.GenerateKey(); Assert.AreEqual(keySize, aes.Key.Length * 8); @@ -197,41 +488,75 @@ public void GenerateKey_HasCorrectLength(int keySize) [TestMethod] public void CreateEncryptor() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); using var _ = aes.CreateEncryptor(); } [TestMethod] - public void CreateEncryptor_Null() + public void CreateEncryptor_Array_Array() + { + using var aes = new AesCtr(); + using var _ = aes.CreateEncryptor(TestKey, TestIV); + } + + [TestMethod] + public void CreateEncryptor_Array_Array_KeyNull() + { + using var aes = new AesCtr(); + Assert.ThrowsException<ArgumentNullException>(() => + { + using var _ = aes.CreateEncryptor(TestKeyNull, TestIV); + }); + } + + [TestMethod] + public void CreateEncryptor_Array_Array_IVNull() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<CryptographicException>(() => { - using var _ = aes.CreateEncryptor(TestKey, null); + using var _ = aes.CreateEncryptor(TestKey, TestIVNull); }); } [TestMethod] public void CreateDecryptor() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); using var _ = aes.CreateDecryptor(); } [TestMethod] - public void CreateDecryptor_Null() + public void CreateDecryptor_Array_Array() + { + using var aes = new AesCtr(); + using var _ = aes.CreateDecryptor(TestKey, TestIV); + } + + [TestMethod] + public void CreateDecryptor_Array_Array_KeyNull() + { + using var aes = new AesCtr(); + Assert.ThrowsException<ArgumentNullException>(() => + { + using var _ = aes.CreateDecryptor(TestKeyNull, TestIV); + }); + } + + [TestMethod] + public void CreateDecryptor_Array_Array_IVNull() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<CryptographicException>(() => { - using var _ = aes.CreateDecryptor(TestKey, null); + using var _ = aes.CreateDecryptor(TestKey, TestIVNull); }); } [TestMethod] public void TransformCtr_Array_Array() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); aes.TransformCtr(TestMessage, TestIV); } @@ -239,40 +564,40 @@ public void TransformCtr_Array_Array() [TestMethod] public void TransformCtr_Null_Array() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<ArgumentNullException>(() => { - aes.TransformCtr(null!, TestIV); + aes.TransformCtr(TestKeyNull, TestIV); }); } [TestMethod] public void TransformCtr_Array_Null() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<ArgumentNullException>(() => { - aes.TransformCtr(TestMessage, null!); + aes.TransformCtr(TestMessage, TestIVNull); }); } [TestMethod] public void TransformCtr_Array_Array_InvalidIV() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<ArgumentException>(() => { - aes.TransformCtr(TestMessage, TestInvalidIV); + aes.TransformCtr(TestMessage, TestIVInvalid); }); } [TestMethod] public void TransformCtr_ReadOnlySpan_ReadOnlySpan() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); aes.TransformCtr(TestMessage.AsSpan(), TestIV.AsSpan()); } @@ -280,18 +605,18 @@ public void TransformCtr_ReadOnlySpan_ReadOnlySpan() [TestMethod] public void TransformCtr_ReadOnlySpan_ReadOnlySpan_InvalidIV() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); Assert.ThrowsException<ArgumentException>(() => { - aes.TransformCtr(TestMessage.AsSpan(), TestInvalidIV.AsSpan()); + aes.TransformCtr(TestMessage.AsSpan(), TestIVInvalid.AsSpan()); }); } [TestMethod] public void TransformCtr_ReadOnlySpan_ReadOnlySpan_Span() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length]; aes.TransformCtr(TestMessage.AsSpan(), TestIV.AsSpan(), destination); @@ -300,19 +625,19 @@ public void TransformCtr_ReadOnlySpan_ReadOnlySpan_Span() [TestMethod] public void TransformCtr_ReadOnlySpan_ReadOnlySpan_Span_InvalidIV() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length]; Assert.ThrowsException<ArgumentException>(() => { - aes.TransformCtr(TestMessage, TestInvalidIV, destination); + aes.TransformCtr(TestMessage, TestIVInvalid, destination); }); } [TestMethod] public void TransformCtr_ReadOnlySpan_ReadOnlySpan_Span_Short() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length - 1]; Assert.ThrowsException<ArgumentException>(() => @@ -324,7 +649,7 @@ public void TransformCtr_ReadOnlySpan_ReadOnlySpan_Span_Short() [TestMethod] public void TryTransformCtr() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length]; aes.TryTransformCtr(TestMessage.AsSpan(), TestIV.AsSpan(), destination, out _); @@ -333,19 +658,19 @@ public void TryTransformCtr() [TestMethod] public void TryTransformCtr_InvalidIV() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length]; Assert.ThrowsException<ArgumentException>(() => { - aes.TryTransformCtr(TestMessage.AsSpan(), TestInvalidIV, destination, out _); + aes.TryTransformCtr(TestMessage.AsSpan(), TestIVInvalid, destination, out _); }); } [TestMethod] public void TryTransformCtr_Short() { - using var aes = AesCtr.Create(); + using var aes = new AesCtr(); var destination = new byte[TestMessage.Length - 1]; var success = aes.TryTransformCtr(TestMessage, TestIV, destination, out var bytesWritten); diff --git a/UnitTests/NistAesCtrSampleTestVector.cs b/UnitTests/NistAesCtrSampleTestVector.cs index 5443fef..ebd992d 100644 --- a/UnitTests/NistAesCtrSampleTestVector.cs +++ b/UnitTests/NistAesCtrSampleTestVector.cs @@ -93,79 +93,79 @@ static NistAesCtrSampleTestVector() var testVectors = new List<NistAesCtrSampleTestVector> { new("F.5.1", "CTR-AES128.Encrypt", @" - Key 2b7e151628aed2a6abf7158809cf4f3c - Init. Counter f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff + Key 2B7E151628AED2A6ABF7158809CF4F3C + Init. Counter F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF Block #1 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff - Output Block ec8cdf7398607cb0f2d21675ea9ea1e4 - Plaintext 6bc1bee22e409f96e93d7e117393172a - Ciphertext 874d6191b620e3261bef6864990db6ce + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF + Output Block EC8CDF7398607CB0F2D21675EA9EA1E4 + Plaintext 6BC1BEE22E409F96E93D7E117393172A + Ciphertext 874D6191B620E3261BEF6864990DB6CE Block #2 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff00 - Output Block 362b7c3c6773516318a077d7fc5073ae - Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 - Ciphertext 9806f66b7970fdff8617187bb9fffdff + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF00 + Output Block 362B7C3C6773516318A077D7FC5073AE + Plaintext AE2D8A571E03AC9C9EB76FAC45AF8E51 + Ciphertext 9806F66B7970FDFF8617187BB9FFFDFF Block #3 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff01 - Output Block 6a2cc3787889374fbeb4c81b17ba6c44 - Plaintext 30c81c46a35ce411e5fbc1191a0a52ef - Ciphertext 5ae4df3edbd5d35e5b4f09020db03eab + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF01 + Output Block 6A2CC3787889374FBEB4C81B17BA6C44 + Plaintext 30C81C46A35CE411E5FBC1191A0A52EF + Ciphertext 5AE4DF3EDBD5D35E5B4F09020DB03EAB Block #4 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff02 - Output Block e89c399ff0f198c6d40a31db156cabfe - Plaintext f69f2445df4f9b17ad2b417be66c3710 - Ciphertext 1e031dda2fbe03d1792170a0f3009cee + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF02 + Output Block E89C399FF0F198C6D40A31DB156CABFE + Plaintext F69F2445DF4F9B17AD2B417BE66C3710 + Ciphertext 1E031DDA2FBE03D1792170A0F3009CEE "), new("F.5.3", "CTR-AES192.Encrypt", @" - Key 8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b - Init. Counter f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff + Key 8E73B0F7DA0E6452C810F32B809079E562F8EAD2522C6B7B + Init. Counter F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF Block #1 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff - Output Block 717d2dc639128334a6167a488ded7921 - Plaintext 6bc1bee22e409f96e93d7e117393172a - Ciphertext 1abc932417521ca24f2b0459fe7e6e0b + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF + Output Block 717D2DC639128334A6167A488DED7921 + Plaintext 6BC1BEE22E409F96E93D7E117393172A + Ciphertext 1ABC932417521CA24F2B0459FE7E6E0B Block #2 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff00 - Output Block a72eb3bb14a556734b7bad6ab16100c5 - Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 - Ciphertext 090339ec0aa6faefd5ccc2c6f4ce8e94 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF00 + Output Block A72EB3BB14A556734B7BAD6AB16100C5 + Plaintext AE2D8A571E03AC9C9EB76FAC45AF8E51 + Ciphertext 090339EC0AA6FAEFD5CCC2C6F4CE8E94 Block #3 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff01 - Output Block 2efeae2d72b722613446dc7f4c2af918 - Plaintext 30c81c46a35ce411e5fbc1191a0a52ef - Ciphertext 1e36b26bd1ebc670d1bd1d665620abf7 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF01 + Output Block 2EFEAE2D72B722613446DC7F4C2AF918 + Plaintext 30C81C46A35CE411E5FBC1191A0A52EF + Ciphertext 1E36B26BD1EBC670D1BD1D665620ABF7 Block #4 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff02 - Output Block b9e783b30dd7924ff7bc9b97beaa8740 - Plaintext f69f2445df4f9b17ad2b417be66c3710 - Ciphertext 4f78a7f6d29809585a97daec58c6b050 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF02 + Output Block B9E783B30DD7924FF7BC9B97BEAA8740 + Plaintext F69F2445DF4F9B17AD2B417BE66C3710 + Ciphertext 4F78A7F6D29809585A97DAEC58C6B050 "), new("F.5.5", "CTR-AES256.Encrypt", @" - Key 603deb1015ca71be2b73aef0857d7781 - 1f352c073b6108d72d9810a30914dff4 - Init. Counter f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff + Key 603DEB1015CA71BE2B73AEF0857D7781 + 1F352C073B6108D72D9810A30914DFF4 + Init. Counter F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF Block #1 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff - Output Block 0bdf7df1591716335e9a8b15c860c502 - Plaintext 6bc1bee22e409f96e93d7e117393172a - Ciphertext 601ec313775789a5b7a7f504bbf3d228 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF + Output Block 0BDF7DF1591716335E9A8B15C860C502 + Plaintext 6BC1BEE22E409F96E93D7E117393172A + Ciphertext 601EC313775789A5B7A7F504BBF3D228 Block #2 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff00 - Output Block 5a6e699d536119065433863c8f657b94 - Plaintext ae2d8a571e03ac9c9eb76fac45af8e51 - Ciphertext f443e3ca4d62b59aca84e990cacaf5c5 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF00 + Output Block 5A6E699D536119065433863C8F657B94 + Plaintext AE2D8A571E03AC9C9EB76FAC45AF8E51 + Ciphertext F443E3CA4D62B59ACA84E990CACAF5C5 Block #3 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff01 - Output Block 1bc12c9c01610d5d0d8bd6a3378eca62 - Plaintext 30c81c46a35ce411e5fbc1191a0a52ef - Ciphertext 2b0930daa23de94ce87017ba2d84988d + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF01 + Output Block 1BC12C9C01610D5D0D8BD6A3378ECA62 + Plaintext 30C81C46A35CE411E5FBC1191A0A52EF + Ciphertext 2B0930DAA23DE94CE87017BA2D84988D Block #4 - Input Block f0f1f2f3f4f5f6f7f8f9fafbfcfdff02 - Output Block 2956e1c8693536b1bee99c73a31576b6 - Plaintext f69f2445df4f9b17ad2b417be66c3710 - Ciphertext dfc9c58db67aada613c2dd08457941a6 + Input Block F0F1F2F3F4F5F6F7F8F9FAFBFCFDFF02 + Output Block 2956E1C8693536B1BEE99C73A31576B6 + Plaintext F69F2445DF4F9B17AD2B417BE66C3710 + Ciphertext DFC9C58DB67AADA613C2DD08457941A6 "), }; diff --git a/UnitTests/RfcAesSivTestVector.cs b/UnitTests/RfcAesSivTestVector.cs index 749dd90..450042d 100644 --- a/UnitTests/RfcAesSivTestVector.cs +++ b/UnitTests/RfcAesSivTestVector.cs @@ -74,41 +74,41 @@ static RfcAesSivTestVector() var testVectors = new List<RfcAesSivTestVector> { new("Deterministic", @" - fffefdfc fbfaf9f8 f7f6f5f4 f3f2f1f0 - f0f1f2f3 f4f5f6f7 f8f9fafb fcfdfeff + FFFEFDFC FBFAF9F8 F7F6F5F4 F3F2F1F0 + F0F1F2F3 F4F5F6F7 F8F9FAFB FCFDFEFF ", [ @" - 10111213 14151617 18191a1b 1c1d1e1f + 10111213 14151617 18191A1B 1C1D1E1F 20212223 24252627 " ], null , @" - 11223344 55667788 99aabbcc ddee + 11223344 55667788 99AABBCC DDEE ", @" - 85632d07 c6e8f37f 950acd32 0a2ecc93 - 40c02b96 90c4dc04 daef7f6a fe5c + 85632D07 C6E8F37F 950ACD32 0A2ECC93 + 40C02B96 90C4DC04 DAEF7F6A FE5C " ), new("Nonce-Based", @" - 7f7e7d7c 7b7a7978 77767574 73727170 - 40414243 44454647 48494a4b 4c4d4e4f + 7F7E7D7C 7B7A7978 77767574 73727170 + 40414243 44454647 48494A4B 4C4D4E4F ", [ @" - 00112233 44556677 8899aabb ccddeeff - deaddada deaddada ffeeddcc bbaa9988 + 00112233 44556677 8899AABB CCDDEEFF + DEADDADA DEADDADA FFEEDDCC BBAA9988 77665544 33221100 ", @" - 10203040 50607080 90a0 + 10203040 50607080 90A0 " ], @" - 09f91102 9d74e35b d84156c5 635688c0 + 09F91102 9D74E35B D84156C5 635688C0 ", @" - 74686973 20697320 736f6d65 20706c61 - 696e7465 78742074 6f20656e 63727970 - 74207573 696e6720 5349562d 414553 + 74686973 20697320 736F6D65 20706C61 + 696E7465 78742074 6F20656E 63727970 + 74207573 696E6720 5349562D 414553 ", @" - 7bdb6e3b 432667eb 06f4d14b ff2fbd0f - cb900f2f ddbe4043 26601965 c889bf17 - dba77ceb 094fa663 b7a3f748 ba8af829 - ea64ad54 4a272e9c 485b62a3 fd5c0d + 7BDB6E3B 432667EB 06F4D14B FF2FBD0F + CB900F2F DDBE4043 26601965 C889BF17 + DBA77CEB 094FA663 B7A3F748 BA8AF829 + EA64AD54 4A272E9C 485B62A3 FD5C0D " ), };