From b50438899e231d8c8f38343e16ee32eb01335cf0 Mon Sep 17 00:00:00 2001 From: Frans van Dorsselaer <17404029+dorssel@users.noreply.github.com> Date: Tue, 14 Jan 2025 02:52:23 +0100 Subject: [PATCH] Improve constructors and CryptoConfig support --- AesExtra/AesCmac.cs | 234 ++++++++++--- AesExtra/AesCtr.cs | 443 +++++++++++++++++++++--- AesExtra/AesSiv.cs | 11 +- UnitTests/AesCmac_KAT.cs | 3 +- UnitTests/AesCmac_Tests.cs | 117 ++++++- UnitTests/AesCtr_KAT.cs | 40 +-- UnitTests/AesCtr_Tests.cs | 407 +++++++++++++++++++--- UnitTests/NistAesCtrSampleTestVector.cs | 110 +++--- UnitTests/RfcAesSivTestVector.cs | 38 +- 9 files changed, 1137 insertions(+), 266 deletions(-) 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; + + /// + /// Registers the class with , such that it can be created by name. + /// + /// + /// + /// is not supported in browsers. + /// +#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!); + } + } + } + /// /// A new instance. + [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 + + /// is other than 128, 192, or 256 bits. + 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."); + } + } + + /// The length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). + static void ThrowIfInvalidKey(ReadOnlySpan key) + { + if (key.Length is not (16 or 24 or 32)) + { + throw new CryptographicException("Specified key is not a valid size for this algorithm."); + } + } + + /// is . + /// + static void ThrowIfInvalidKey(byte[] key) + { + if (key is null) + { + throw new ArgumentNullException(nameof(key)); + } + ThrowIfInvalidKey(key.AsSpan()); + } + + void InitializeFixedValues() + { + HashSizeValue = BLOCKSIZE * 8; + } + + /// + /// Initializes a new instance of the class with a randomly generated 256-bit key. + /// + public AesCmac() + : this(256) { - return algorithmName == nameof(AesCmac) ? Create() : KeyedHashAlgorithm.Create(algorithmName); } /// /// Initializes a new instance of the class with a randomly generated key. /// /// The size, in bits, of the randomly generated key. - public AesCmac(int keySize = 256) + /// + 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); } /// /// Initializes a new instance of the class with the specified key data. /// /// The secret key for AES-CMAC algorithm. + /// is . + /// public AesCmac(byte[] key) - : this() + : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key)))) { - Key = key; + } + + /// + /// Initializes a new instance of the class with the specified key data. + /// + /// The secret key for AES-CMAC algorithm. + /// + public AesCmac(ReadOnlySpan key) + { + ThrowIfInvalidKey(key); + + InitializeFixedValues(); + + KeyValue = key.ToArray(); } #region IDisposable + bool IsDisposed; + /// 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 + /// The instance has been disposed. + void ThrowIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(AesCmac)); + } + } + /// + /// + /// + /// An attempt was made to change the during a computation. 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); } /// + /// 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 block) /// 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 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 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, MemoryThe length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). - static void ThrowIfInvalidKey(ReadOnlySpan 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)); - } - } - - /// is . - /// - static void ThrowIfInvalidKey(byte[] key) - { - if (key is null) - { - throw new ArgumentNullException(nameof(key)); - } - ThrowIfInvalidKey(key.AsSpan()); - } - /// is . 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; + + /// + /// Registers the class with , such that it can be created by name. + /// + /// + /// + /// is not supported in browsers. + /// +#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!); + } + } + } + /// + [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 + + /// is other than 128, 192, or 256 bits. + 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() + /// The length is other than 16, 24, or 32 bytes (128, 192, or 256 bits). + static void ThrowIfInvalidKey(ReadOnlySpan key) + { + if (key.Length is not (16 or 24 or 32)) + { + throw new CryptographicException("Specified key is not a valid size for this algorithm."); + } + } + + /// is . + /// + static void ThrowIfInvalidKey(byte[] key, string argumentName = "key") + { + if (key is null) + { + throw new ArgumentNullException(argumentName); + } + ThrowIfInvalidKey(key.AsSpan()); + } + + /// is . + /// + static void ThrowIfInvalidIV(byte[] iv, string argumentName = "iv") + { + if (iv is null) + { + throw new ArgumentNullException(argumentName); + } + ThrowIfInvalidIV(iv.AsSpan()); + } + + /// + /// is the incorrect length. + /// Callers are expected to pass an initialization vector that is exactly in length, + /// converted to bytes (`BlockSize / 8`). + /// + static void ThrowIfInvalidIV(ReadOnlySpan 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 - /// - protected override void Dispose(bool disposing) + /// + /// Initializes a new instance of the class with a randomly generated 256-bit key and an initial counter of zero. + /// + public AesCtr() + : this(256) + { + } + + /// + /// Initializes a new instance of the class with a randomly generated key and an initial counter of zero. + /// + /// The size, in bits, of the randomly generated key. + /// + public AesCtr(int keySize) + { + ThrowIfInvalidKeySize(keySize); + + InitializeFixedValues(); + + KeySizeValue = keySize; + IVValue = new byte[BLOCKSIZE]; + } + + /// + /// Initializes a new instance of the class with the specified key data and a randomly generated initial counter. + /// + /// The secret key for the AES-CTR algorithm. + /// is . + /// + public AesCtr(byte[] key) + : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key)))) + { + } + + /// + /// Initializes a new instance of the class with the specified key data and a randomly generated initial counter. + /// + /// The secret key for the AES-CTR algorithm. + public AesCtr(ReadOnlySpan key) + { + ThrowIfInvalidKey(key); + + InitializeFixedValues(); + + KeyValue = key.ToArray(); + KeySizeValue = key.Length * BitsPerByte; + } + + /// + /// Initializes a new instance of the class with the specified key data and initial counter. + /// + /// The secret key for the AES-CTR algorithm. + /// The initialization vector (initial counter). + /// is . + /// is . + /// + public AesCtr(byte[] key, byte[] iv) : + this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key))), + new ReadOnlySpan(iv ?? throw new ArgumentNullException(nameof(iv)))) + { + } + + /// + /// Initializes a new instance of the class with the specified key data and initial counter. + /// + /// The secret key for the AES-CTR algorithm. + /// The initialization vector (initial counter). + /// + /// + public AesCtr(ReadOnlySpan key, ReadOnlySpan 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; + + /// + protected override void Dispose(bool disposing) + { + if (IsDisposed) + { + return; + } + + PurgeKeyValue(); + PurgeIVValue(); + IsDisposed = true; + base.Dispose(disposing); } #endregion + /// The instance has been disposed. + void ThrowIfDisposed() + { + if (IsDisposed) + { + throw new ObjectDisposedException(nameof(AesCtr)); + } + } + + /// + /// + /// + /// Setting this property always resets the key to a new random value, even if the key size + /// is set to current value. + /// + /// + /// + public override int KeySize + { + get + { + ThrowIfDisposed(); + + return KeySizeValue; + } + + set + { + ThrowIfInvalidKeySize(value); + + ThrowIfDisposed(); + + PurgeKeyValue(); + KeySizeValue = value; + } + } + + /// + /// + /// + /// + 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(); + } + } + + /// + /// + /// + /// For AES-CTR, the initialization vector (IV) is the initial counter. + /// + /// + /// + public override byte[] IV + { + get + { + ThrowIfDisposed(); + + UncheckedGenerateIVValueIfNull(); + return (byte[])IVValue!.Clone(); + } + + set + { + ThrowIfInvalidIV(value, nameof(IV)); + + ThrowIfDisposed(); + + PurgeIVValue(); + + IVValue = (byte[])value.Clone(); + } + } + /// /// always pretends to use . 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) + /// + /// + 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!); } - /// + /// + /// Creates a symmetric decryptor object with the specified key and initial counter (IV). + /// + /// The secret key to use for the symmetric algorithm. + /// The initialization vector (initial counter). + /// A symmetric decryptor object. + /// + /// + /// 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); } - /// + /// + /// + public override ICryptoTransform CreateEncryptor() + { + ThrowIfDisposed(); + + UncheckedGenerateKeyValueIfNull(); + UncheckedGenerateIVValueIfNull(); + return new AesCtrTransform(KeyValue!, IVValue!); + } + + /// + /// Creates a symmetric encryptor object with the specified key and initial counter (IV). + /// + /// The secret key to use for the symmetric algorithm. + /// The initialization vector (initial counter). + /// A symmetric encryptor object. + /// + /// + /// 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); + } } /// + /// 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); + } } /// + /// 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) } } - /// is . - /// - static void ThrowIfInvalidIV(byte[] iv) - { - if (iv is null) - { - throw new ArgumentNullException(nameof(iv)); - } - ThrowIfInvalidIV(iv.AsSpan()); - } - - /// - /// is the incorrect length. - /// Callers are expected to pass an initialization vector that is exactly in length, - /// converted to bytes (`BlockSize / 8`). - /// - static void ThrowIfInvalidIV(ReadOnlySpan iv) - { - if (iv.Length != BLOCKSIZE) - { - throw new ArgumentException("Specified initial counter (IV) does not match the block size for this algorithm.", nameof(iv)); - } - } - /// The buffer in is too small to hold the transformed data. static void ThrowIfInvalidDestination(Span destination, int requiredLength) { @@ -189,14 +508,17 @@ static void ThrowIfInvalidDestination(Span destination, int requiredLength /// The initialization vector (initial counter). /// The transformed data. /// - /// + /// 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) /// The data to transform. /// The initialization vector (initial counter). /// The transformed data. - /// + /// public byte[] TransformCtr(ReadOnlySpan input, ReadOnlySpan 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 input, ReadOnlySpan iv) /// The initialization vector (initial counter). /// The buffer to receive the transformed data. /// The total number of bytes written to . - /// + /// /// public int TransformCtr(ReadOnlySpan input, ReadOnlySpan iv, Span 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 input, ReadOnlySpan iv, Span /// if was large enough to receive the transformed data; otherwise, . /// - /// + /// public bool TryTransformCtr(ReadOnlySpan input, ReadOnlySpan iv, Span 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 class with a provided key. /// /// The secret key to use for this instance. - /// + /// is . public AesSiv(byte[] key) : this(new ReadOnlySpan(key ?? throw new ArgumentNullException(nameof(key)))) { @@ -31,7 +31,7 @@ public AesSiv(ReadOnlySpan 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 + /// The instance has been disposed. void ThrowIfDisposed() { if (IsDisposed) @@ -219,6 +220,7 @@ static void ThrowIfInvalidAssociatedData(ReadOnlySpan> asso /// /// /// + /// 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 /// The byte array to receive the encrypted contents, prepended with the synthetic IV. /// Extra data associated with this message, which must also be provided during decryption. /// + /// public void Encrypt(ReadOnlySpan plaintext, Span ciphertext, ReadOnlySpan associatedData) { // Input validation @@ -289,6 +292,7 @@ public void Encrypt(ReadOnlySpan plaintext, Span ciphertext, ReadOnl /// Extra data associated with this message, which must also be provided during decryption. /// /// + /// public void Encrypt(ReadOnlySpan plaintext, Span ciphertext, params ReadOnlySpan> associatedData) { // Input validation @@ -326,6 +330,7 @@ public void Encrypt(ReadOnlySpan plaintext, Span ciphertext, params /// /// /// + /// /// The tag value could not be verified. public void Decrypt(byte[] ciphertext, byte[] plaintext, params byte[][] associatedData) { @@ -373,6 +378,7 @@ public void Decrypt(byte[] ciphertext, byte[] plaintext, params byte[][] associa /// Extra data associated with this message, which must match the value provided during encryption. /// /// + /// /// The tag value could not be verified. public void Decrypt(ReadOnlySpan ciphertext, Span plaintext, ReadOnlySpan associatedData) { @@ -413,6 +419,7 @@ public void Decrypt(ReadOnlySpan ciphertext, Span plaintext, ReadOnl /// /// /// + /// /// The tag value could not be verified. public void Decrypt(ReadOnlySpan ciphertext, Span plaintext, params ReadOnlySpan> 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(() => { #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(() => + { + 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(() => { - 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(() => { @@ -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(() => + { + 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(() => + { + 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(() => + { + 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(() => + { + using var aes = new AesCtr(new byte[keySize / 8]); + }); + } + + [TestMethod] + public void Constructor_Array_Null() + { + Assert.ThrowsException(() => + { + 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(() => + { + 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(() => + { + using var aes = new AesCtr(new byte[keySize / 8], TestIV); + }); + } + + [TestMethod] + public void Constructor_Array_Array_KeyNull() + { + Assert.ThrowsException(() => + { + using var aes = new AesCtr(TestKeyNull, TestIV); + }); + } + + [TestMethod] + public void Constructor_Array_Array_IVInvalid() + { + Assert.ThrowsException(() => + { + using var aes = new AesCtr(TestKey, TestIVInvalid); + }); + } + + [TestMethod] + public void Constructor_Array_Array_IVNull() + { + Assert.ThrowsException(() => + { + 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(() => + { + using var aes = new AesCtr(new byte[keySize / 8].AsSpan(), TestIV.AsSpan()); + }); + } + + [TestMethod] + public void Constructor_ReadOnlySpan_ReadOnlySpan_IVInvalid() + { + Assert.ThrowsException(() => + { + 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(() => { @@ -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(() => @@ -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(() => { @@ -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(() => + { + aes.Key = TestKeyNull; + }); + } + + [TestMethod] + public void Key_AfterDispose() + { + using var aes = new AesCtr(); + aes.Dispose(); + + Assert.ThrowsException(() => + { + 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(() => + { + aes.IV = TestIVNull; + }); + } + + [TestMethod] + public void IV_AfterDispose() + { + using var aes = new AesCtr(); + aes.Dispose(); + + Assert.ThrowsException(() => + { + 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(() => + { + 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(() => { - 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(() => + { + 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(() => { - 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(() => { - 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(() => { - 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(() => { - 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(() => { - 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(() => { - 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(() => @@ -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(() => { - 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 { 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 { 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 " ), };