Implement AesGcm constructors accepting a tag size and obsolete the existing ones
authorKevin Jones <kevin@vcsjones.com>
Mon, 15 May 2023 17:28:00 +0000 (13:28 -0400)
committerGitHub <noreply@github.com>
Mon, 15 May 2023 17:28:00 +0000 (13:28 -0400)
docs/project/list-of-diagnostics.md
src/libraries/Common/src/System/Obsoletions.cs
src/libraries/System.Security.Cryptography/ref/System.Security.Cryptography.cs
src/libraries/System.Security.Cryptography/src/Resources/Strings.resx
src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesGcm.cs
src/libraries/System.Security.Cryptography/tests/AesGcmTests.cs

index 2dfe110..7c814cd 100644 (file)
@@ -107,6 +107,7 @@ The PR that reveals the implementation of the `<IncludeInternalObsoleteAttribute
 |  __`SYSLIB0050`__ | Formatter-based serialization is obsolete and should not be used. |
 |  __`SYSLIB0051`__ | This API supports obsolete formatter-based serialization. It should not be called or extended by application code. |
 |  __`SYSLIB0052`__ | This API supports obsolete mechanisms for Regex extensibility. It is not supported. |
+|  __`SYSLIB0053`__ | AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size. |
 
 ## Analyzer Warnings
 
index 668e38d..79e7ccb 100644 (file)
@@ -168,5 +168,8 @@ namespace System
 
         internal const string RegexExtensibilityImplMessage = "This API supports obsolete mechanisms for Regex extensibility. It is not supported.";
         internal const string RegexExtensibilityDiagId = "SYSLIB0052";
+
+        internal const string AesGcmTagConstructorMessage = "AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.";
+        internal const string AesGcmTagConstructorDiagId = "SYSLIB0053";
     }
 }
index 5ca9137..425b0e3 100644 (file)
@@ -123,11 +123,16 @@ namespace System.Security.Cryptography
     [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")]
     public sealed partial class AesGcm : System.IDisposable
     {
+        [System.ObsoleteAttribute("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId="SYSLIB0053", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
         public AesGcm(byte[] key) { }
+        public AesGcm(byte[] key, int tagSizeInBytes) { }
+        [System.ObsoleteAttribute("AesGcm should indicate the required tag size for encryption and decryption. Use a constructor that accepts the tag size.", DiagnosticId="SYSLIB0053", UrlFormat="https://aka.ms/dotnet-warnings/{0}")]
         public AesGcm(System.ReadOnlySpan<byte> key) { }
+        public AesGcm(System.ReadOnlySpan<byte> key, int tagSizeInBytes) { }
         public static bool IsSupported { get { throw null; } }
         public static System.Security.Cryptography.KeySizes NonceByteSizes { get { throw null; } }
         public static System.Security.Cryptography.KeySizes TagByteSizes { get { throw null; } }
+        public int? TagSizeInBytes { get { throw null; } }
         public void Decrypt(byte[] nonce, byte[] ciphertext, byte[] tag, byte[] plaintext, byte[]? associatedData = null) { }
         public void Decrypt(System.ReadOnlySpan<byte> nonce, System.ReadOnlySpan<byte> ciphertext, System.ReadOnlySpan<byte> tag, System.Span<byte> plaintext, System.ReadOnlySpan<byte> associatedData = default(System.ReadOnlySpan<byte>)) { }
         public void Dispose() { }
index 0172bc6..1fd0873 100644 (file)
   <data name="Cryptography_HashNotYetFinalized" xml:space="preserve">
     <value>Hash must be finalized before the hash value is retrieved.</value>
   </data>
+  <data name="Cryptography_IncorrectTagLength" xml:space="preserve">
+    <value>The size of the specified tag does not match the expected size of {0}.</value>
+  </data>
   <data name="Cryptography_InvalidBlockSize" xml:space="preserve">
     <value>Specified block size is not valid for this algorithm.</value>
   </data>
index 104d280..476b991 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
 using System.Runtime.Versioning;
 using Internal.Cryptography;
 
@@ -14,6 +15,7 @@ namespace System.Security.Cryptography
         private const int NonceSize = 12;
         public static KeySizes NonceByteSizes { get; } = new KeySizes(NonceSize, NonceSize, 1);
 
+        [Obsolete(Obsoletions.AesGcmTagConstructorMessage, DiagnosticId = Obsoletions.AesGcmTagConstructorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
         public AesGcm(ReadOnlySpan<byte> key)
         {
             ThrowIfNotSupported();
@@ -22,16 +24,82 @@ namespace System.Security.Cryptography
             ImportKey(key);
         }
 
+        [Obsolete(Obsoletions.AesGcmTagConstructorMessage, DiagnosticId = Obsoletions.AesGcmTagConstructorDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
         public AesGcm(byte[] key)
+            : this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key))))
         {
-            ThrowIfNotSupported();
+        }
 
-            ArgumentNullException.ThrowIfNull(key);
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="AesGcm" /> class with a provided key and required tag size.
+        /// </summary>
+        /// <param name="key">The secret key to use for this instance.</param>
+        /// <param name="tagSizeInBytes">The size of the tag, in bytes, that encryption and decryption must use.</param>
+        /// <exception cref="CryptographicException">
+        ///   The <paramref name="key" /> parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits).
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   The <paramref name="tagSizeInBytes" /> parameter is an unsupported tag size indicated by
+        ///   <see cref="TagByteSizes" />.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not support AES-GCM.
+        /// </exception>
+        /// <remarks>
+        ///   The <paramref name="tagSizeInBytes" /> parameter is used to indicate that the tag parameter in <c>Encrypt</c>
+        ///   or <c>Decrypt</c> must be exactly this size. Indicating the required tag size prevents issues where callers
+        ///   of <c>Decrypt</c> may supply a tag as input and that input is truncated to an unexpected size.
+        /// </remarks>
+        public AesGcm(ReadOnlySpan<byte> key, int tagSizeInBytes)
+        {
+            ThrowIfNotSupported();
 
             AesAEAD.CheckKeySize(key.Length);
+
+            if (!tagSizeInBytes.IsLegalSize(TagByteSizes))
+            {
+                throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tagSizeInBytes));
+            }
+
+            TagSizeInBytes = tagSizeInBytes;
             ImportKey(key);
         }
 
+        /// <summary>
+        ///   Initializes a new instance of the <see cref="AesGcm" /> class with a provided key and required tag size.
+        /// </summary>
+        /// <param name="key">The secret key to use for this instance.</param>
+        /// <param name="tagSizeInBytes">The size of the tag, in bytes, that encryption and decryption must use.</param>
+        /// <exception cref="ArgumentNullException">The <paramref name="key" /> parameter is null.</exception>
+        /// <exception cref="CryptographicException">
+        ///   The <paramref name="key" /> parameter length is other than 16, 24, or 32 bytes (128, 192, or 256 bits).
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   The <paramref name="tagSizeInBytes" /> parameter is an unsupported tag size indicated by
+        ///   <see cref="TagByteSizes" />.
+        /// </exception>
+        /// <exception cref="PlatformNotSupportedException">
+        ///   The current platform does not support AES-GCM.
+        /// </exception>
+        /// <remarks>
+        ///   The <paramref name="tagSizeInBytes" /> parameter is used to indicate that the tag parameter in <c>Encrypt</c>
+        ///   or <c>Decrypt</c> must be exactly this size. Indicating the required tag size prevents issues where callers
+        ///   of <c>Decrypt</c> may supply a tag as input and that input is truncated to an unexpected size.
+        /// </remarks>
+        public AesGcm(byte[] key, int tagSizeInBytes)
+            : this(new ReadOnlySpan<byte>(key ?? throw new ArgumentNullException(nameof(key))), tagSizeInBytes)
+        {
+        }
+
+        /// <summary>
+        /// Gets the size of the tag, in bytes.
+        /// </summary>
+        /// <value>
+        /// The size of the tag that must be used for encryption or decryption, or <see langword="null" /> if the
+        /// tag size is unspecified.
+        /// </value>
+        public int? TagSizeInBytes { get; }
+
         public void Encrypt(byte[] nonce, byte[] plaintext, byte[] ciphertext, byte[] tag, byte[]? associatedData = null)
         {
             ArgumentNullException.ThrowIfNull(nonce);
@@ -74,7 +142,7 @@ namespace System.Security.Cryptography
             DecryptCore(nonce, ciphertext, tag, plaintext, associatedData);
         }
 
-        private static void CheckParameters(
+        private void CheckParameters(
             ReadOnlySpan<byte> plaintext,
             ReadOnlySpan<byte> ciphertext,
             ReadOnlySpan<byte> nonce,
@@ -86,8 +154,20 @@ namespace System.Security.Cryptography
             if (!nonce.Length.IsLegalSize(NonceByteSizes))
                 throw new ArgumentException(SR.Cryptography_InvalidNonceLength, nameof(nonce));
 
-            if (!tag.Length.IsLegalSize(TagByteSizes))
+            if (TagSizeInBytes is int tagSizeInBytes)
+            {
+                // constructor promise
+                Debug.Assert(tagSizeInBytes.IsLegalSize(TagByteSizes));
+
+                if (tag.Length != tagSizeInBytes)
+                {
+                    throw new ArgumentException(SR.Format(SR.Cryptography_IncorrectTagLength, tagSizeInBytes), nameof(tag));
+                }
+            }
+            else if (!tag.Length.IsLegalSize(TagByteSizes))
+            {
                 throw new ArgumentException(SR.Cryptography_InvalidTagLength, nameof(tag));
+            }
         }
 
         private static void ThrowIfNotSupported()
index 741b280..46e70b2 100644 (file)
@@ -29,7 +29,7 @@ namespace System.Security.Cryptography.Tests
             RandomNumberGenerator.Fill(key);
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 aesGcm.Encrypt(nonce, plaintext, ciphertext, tag, additionalData);
 
@@ -51,7 +51,12 @@ namespace System.Security.Cryptography.Tests
         public static void InvalidKeyLength(int keyLength)
         {
             byte[] key = new byte[keyLength];
+#pragma warning disable SYSLIB0053
             Assert.Throws<CryptographicException>(() => new AesGcm(key));
+            Assert.Throws<CryptographicException>(() => new AesGcm(key.AsSpan()));
+#pragma warning restore SYSLIB0053
+            Assert.Throws<CryptographicException>(() => new AesGcm(key, AesGcm.TagByteSizes.MinSize));
+            Assert.Throws<CryptographicException>(() => new AesGcm(key.AsSpan(), AesGcm.TagByteSizes.MinSize));
         }
 
         [Theory]
@@ -68,7 +73,7 @@ namespace System.Security.Cryptography.Tests
             RandomNumberGenerator.Fill(key);
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MinSize))
             {
                 Assert.Throws<ArgumentException>("nonce", () => aesGcm.Encrypt(nonce, plaintext, ciphertext, tag));
             }
@@ -88,7 +93,7 @@ namespace System.Security.Cryptography.Tests
             RandomNumberGenerator.Fill(key);
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, AesGcm.TagByteSizes.MinSize))
             {
                 aesGcm.Encrypt(nonce, plaintext, ciphertext, tag);
 
@@ -101,7 +106,7 @@ namespace System.Security.Cryptography.Tests
         [Theory]
         [MemberData(nameof(GetInvalidTagSizes))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
-        public static void InvalidTagSize(int tagSize)
+        public static void InvalidTagSizeForUnspecifiedRequiredTag(int tagSize)
         {
             int dataLength = 30;
             byte[] plaintext = Enumerable.Range(1, dataLength).Select((x) => (byte)x).ToArray();
@@ -112,13 +117,26 @@ namespace System.Security.Cryptography.Tests
             RandomNumberGenerator.Fill(key);
             RandomNumberGenerator.Fill(nonce);
 
+#pragma warning disable SYSLIB0053
             using (var aesGcm = new AesGcm(key))
+#pragma warning restore SYSLIB0053
             {
                 Assert.Throws<ArgumentException>("tag", () => aesGcm.Encrypt(nonce, plaintext, ciphertext, tag));
+                Assert.Throws<ArgumentException>("tag", () => aesGcm.Decrypt(nonce, ciphertext, tag, plaintext));
             }
         }
 
         [Theory]
+        [MemberData(nameof(GetInvalidTagSizes))]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
+        public static void InvalidTagSizeForRequiredTag(int tagSize)
+        {
+            byte[] key = new byte[32];
+            Assert.Throws<ArgumentException>("tagSizeInBytes", () => new AesGcm(key, tagSize));
+            Assert.Throws<ArgumentException>("tagSizeInBytes", () => new AesGcm(key.AsSpan(), tagSize));
+        }
+
+        [Theory]
         [MemberData(nameof(GetValidTagSizes))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
         public static void ValidTagSize(int tagSize)
@@ -132,7 +150,7 @@ namespace System.Security.Cryptography.Tests
             RandomNumberGenerator.Fill(key);
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tagSize))
             {
                 aesGcm.Encrypt(nonce, plaintext, ciphertext, tag);
 
@@ -142,6 +160,37 @@ namespace System.Security.Cryptography.Tests
             }
         }
 
+        [Theory]
+        [InlineData(12)]
+        [InlineData(13)]
+        [InlineData(14)]
+        [InlineData(15)]
+        public static void TagSizeDoesNotMatchConstructorRequirement(int wrongTagSize)
+        {
+            byte[] key = new byte[16];
+            byte[] nonce = new byte[12];
+            byte[] plaintext = new byte[1];
+            byte[] ciphertext = new byte[1];
+            byte[] tag = new byte[wrongTagSize];
+            int tagSize = 16;
+
+            using (var aesGcm = new AesGcm(key, tagSize))
+            {
+                ArgumentException ex;
+                ex = Assert.Throws<ArgumentException>("tag", () => aesGcm.Encrypt(nonce, plaintext, ciphertext, tag));
+                Assert.Contains(tagSize.ToString(), ex.Message);
+
+                ex = Assert.Throws<ArgumentException>("tag", () => aesGcm.Encrypt(nonce.AsSpan(), plaintext, ciphertext, tag));
+                Assert.Contains(tagSize.ToString(), ex.Message);
+
+                ex = Assert.Throws<ArgumentException>("tag", () => aesGcm.Decrypt(nonce, ciphertext, tag, plaintext));
+                Assert.Contains(tagSize.ToString(), ex.Message);
+
+                ex = Assert.Throws<ArgumentException>("tag", () => aesGcm.Decrypt(nonce.AsSpan(), ciphertext, tag, plaintext));
+                Assert.Contains(tagSize.ToString(), ex.Message);
+            }
+        }
+
         [Fact]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
         public static void TwoEncryptionsAndDecryptionsUsingOneInstance()
@@ -165,7 +214,7 @@ namespace System.Security.Cryptography.Tests
                 "da").HexToByteArray();
             byte[] expectedTag2 = "9c75d006640ff4fb68c60c9548a45cf8".HexToByteArray();
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, expectedTag1.Length))
             {
                 byte[] ciphertext1 = new byte[originalData1.Length];
                 byte[] tag1 = new byte[expectedTag1.Length];
@@ -173,16 +222,19 @@ namespace System.Security.Cryptography.Tests
                 Assert.Equal(expectedCiphertext1, ciphertext1);
                 Assert.Equal(expectedTag1, tag1);
 
+                byte[] plaintext1 = new byte[originalData1.Length];
+                aesGcm.Decrypt(nonce1, ciphertext1, tag1, plaintext1);
+                Assert.Equal(originalData1, plaintext1);
+            }
+
+            using (var aesGcm = new AesGcm(key, expectedTag2.Length))
+            {
                 byte[] ciphertext2 = new byte[originalData2.Length];
                 byte[] tag2 = new byte[expectedTag2.Length];
                 aesGcm.Encrypt(nonce2, originalData2, ciphertext2, tag2, associatedData2);
                 Assert.Equal(expectedCiphertext2, ciphertext2);
                 Assert.Equal(expectedTag2, tag2);
 
-                byte[] plaintext1 = new byte[originalData1.Length];
-                aesGcm.Decrypt(nonce1, ciphertext1, tag1, plaintext1);
-                Assert.Equal(originalData1, plaintext1);
-
                 byte[] plaintext2 = new byte[originalData2.Length];
                 aesGcm.Decrypt(nonce2, ciphertext2, tag2, plaintext2, associatedData2);
                 Assert.Equal(originalData2, plaintext2);
@@ -200,7 +252,7 @@ namespace System.Security.Cryptography.Tests
             byte[] ciphertext = new byte[ctLen];
             byte[] tag = new byte[16];
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 Assert.Throws<ArgumentException>(() => aesGcm.Encrypt(nonce, plaintext, ciphertext, tag));
                 Assert.Throws<ArgumentException>(() => aesGcm.Decrypt(nonce, ciphertext, tag, plaintext));
@@ -210,7 +262,10 @@ namespace System.Security.Cryptography.Tests
         [Fact]
         public static void NullKey()
         {
+#pragma warning disable SYSLIB0053
             Assert.Throws<ArgumentNullException>(() => new AesGcm((byte[])null));
+#pragma warning restore SYSLIB0053
+            Assert.Throws<ArgumentNullException>(() => new AesGcm((byte[])null, AesGcm.TagByteSizes.MinSize));
         }
 
         [Fact]
@@ -222,7 +277,7 @@ namespace System.Security.Cryptography.Tests
             byte[] ciphertext = new byte[0];
             byte[] tag = new byte[16];
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Encrypt((byte[])null, plaintext, ciphertext, tag));
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Decrypt((byte[])null, ciphertext, tag, plaintext));
@@ -238,7 +293,7 @@ namespace System.Security.Cryptography.Tests
             byte[] ciphertext = new byte[0];
             byte[] tag = new byte[16];
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Encrypt(nonce, (byte[])null, ciphertext, tag));
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Decrypt(nonce, ciphertext, tag, (byte[])null));
@@ -254,7 +309,7 @@ namespace System.Security.Cryptography.Tests
             byte[] plaintext = new byte[0];
             byte[] tag = new byte[16];
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Encrypt(nonce, plaintext, (byte[])null, tag));
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Decrypt(nonce, (byte[])null, tag, plaintext));
@@ -270,7 +325,7 @@ namespace System.Security.Cryptography.Tests
             byte[] plaintext = new byte[0];
             byte[] ciphertext = new byte[0];
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tagSizeInBytes: 16))
             {
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Encrypt(nonce, plaintext, ciphertext, (byte[])null));
                 Assert.Throws<ArgumentNullException>(() => aesGcm.Decrypt(nonce, ciphertext, (byte[])null, plaintext));
@@ -288,7 +343,7 @@ namespace System.Security.Cryptography.Tests
             byte[] tag = new byte[16];
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 aesGcm.Encrypt(nonce, data, data, tag);
                 Assert.NotEqual(originalPlaintext, data);
@@ -309,7 +364,7 @@ namespace System.Security.Cryptography.Tests
             byte[] tag = new byte[16];
             RandomNumberGenerator.Fill(nonce);
 
-            using (var aesGcm = new AesGcm(key))
+            using (var aesGcm = new AesGcm(key, tag.Length))
             {
                 aesGcm.Encrypt(nonce, data, data, tag);
                 Assert.NotEqual(originalPlaintext, data);
@@ -325,9 +380,11 @@ namespace System.Security.Cryptography.Tests
         [Theory]
         [MemberData(nameof(GetNistGcmTestCases))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
-        public static void AesGcmNistTests(AEADTest testCase)
+        public static void AesGcmNistTestsUnspecifiedTagSize(AEADTest testCase)
         {
+#pragma warning disable SYSLIB0053
             using (var aesGcm = new AesGcm(testCase.Key))
+#pragma warning restore SYSLIB0053
             {
                 byte[] ciphertext = new byte[testCase.Plaintext.Length];
                 byte[] tag = new byte[testCase.Tag.Length];
@@ -360,71 +417,82 @@ namespace System.Security.Cryptography.Tests
         [Theory]
         [MemberData(nameof(GetNistGcmTestCases))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
-        public static void AesGcmNistTestsTamperTag(AEADTest testCase)
+        public static void AesGcmNistTestsSpecifiedTagSize(AEADTest testCase)
         {
-            using (var aesGcm = new AesGcm(testCase.Key))
+            if (PlatformDetection.IsOSX && testCase.Tag.Length != CryptoKitSupportedTagSizeInBytes)
             {
-                if (PlatformDetection.IsOSX && testCase.Tag.Length != CryptoKitSupportedTagSizeInBytes)
-                {
-                    byte[] plaintext = new byte[testCase.Plaintext.Length];
-                    byte[] tamperedTag = testCase.Tag.AsSpan().ToArray();
-                    tamperedTag[0] ^= 1;
+                Assert.Throws<ArgumentException>("tagSizeInBytes", () => new AesGcm(testCase.Key, testCase.Tag.Length));
+            }
+            else
+            {
+                byte[] ciphertext = new byte[testCase.Plaintext.Length];
+                byte[] tag = new byte[testCase.Tag.Length];
 
-                    Assert.Throws<ArgumentException>("tag", () =>
-                    {
-                        aesGcm.Decrypt(testCase.Nonce, testCase.Ciphertext, tamperedTag, plaintext, testCase.AssociatedData);
-                    });
-                }
-                else
+                using (var aesGcm = new AesGcm(testCase.Key, testCase.Tag.Length))
                 {
-                    byte[] ciphertext = new byte[testCase.Plaintext.Length];
-                    byte[] tag = new byte[testCase.Tag.Length];
                     aesGcm.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
                     Assert.Equal(testCase.Ciphertext, ciphertext);
                     Assert.Equal(testCase.Tag, tag);
 
-                    tag[0] ^= 1;
-
                     byte[] plaintext = new byte[testCase.Plaintext.Length];
-                    RandomNumberGenerator.Fill(plaintext);
-                    Assert.Throws<AuthenticationTagMismatchException>(
-                        () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData));
-                    Assert.Equal(new byte[plaintext.Length], plaintext);
+                    aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData);
+                    Assert.Equal(testCase.Plaintext, plaintext);
                 }
             }
         }
 
         [Theory]
+        [MemberData(nameof(GetNistGcmTestCases))]
+        [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
+        public static void AesGcmNistTestsTamperTag(AEADTest testCase)
+        {
+            if (PlatformDetection.IsOSX && testCase.Tag.Length != CryptoKitSupportedTagSizeInBytes)
+            {
+                return;
+            }
+
+            using (var aesGcm = new AesGcm(testCase.Key, testCase.Tag.Length))
+            {
+                byte[] ciphertext = new byte[testCase.Plaintext.Length];
+                byte[] tag = new byte[testCase.Tag.Length];
+                aesGcm.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
+                Assert.Equal(testCase.Ciphertext, ciphertext);
+                Assert.Equal(testCase.Tag, tag);
+
+                tag[0] ^= 1;
+
+                byte[] plaintext = new byte[testCase.Plaintext.Length];
+                RandomNumberGenerator.Fill(plaintext);
+                Assert.Throws<AuthenticationTagMismatchException>(
+                    () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData));
+                Assert.Equal(new byte[plaintext.Length], plaintext);
+            }
+        }
+
+        [Theory]
         [MemberData(nameof(GetNistGcmTestCasesWithNonEmptyPT))]
         [ActiveIssue("https://github.com/dotnet/runtime/issues/51332", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst)]
         public static void AesGcmNistTestsTamperCiphertext(AEADTest testCase)
         {
-            using (var aesGcm = new AesGcm(testCase.Key))
+            if (PlatformDetection.IsOSX && testCase.Tag.Length != CryptoKitSupportedTagSizeInBytes)
             {
-                if (PlatformDetection.IsOSX && testCase.Tag.Length != CryptoKitSupportedTagSizeInBytes)
-                {
-                    byte[] tamperedCiphertext = testCase.Ciphertext.AsSpan().ToArray();
-                    tamperedCiphertext[0] ^= 1;
-                    Assert.Throws<ArgumentException>("tag", () =>
-                    {
-                        aesGcm.Decrypt(testCase.Nonce, tamperedCiphertext, testCase.Tag, testCase.Plaintext, testCase.AssociatedData);
-                    });
-                }
-                else
-                {
-                    byte[] ciphertext = new byte[testCase.Plaintext.Length];
-                    byte[] tag = new byte[testCase.Tag.Length];
-                    aesGcm.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
-                    Assert.Equal(testCase.Ciphertext, ciphertext);
-                    Assert.Equal(testCase.Tag, tag);
+                return;
+            }
 
-                    ciphertext[0] ^= 1;
+            using (var aesGcm = new AesGcm(testCase.Key, testCase.Tag.Length))
+            {
+                byte[] ciphertext = new byte[testCase.Plaintext.Length];
+                byte[] tag = new byte[testCase.Tag.Length];
+                aesGcm.Encrypt(testCase.Nonce, testCase.Plaintext, ciphertext, tag, testCase.AssociatedData);
+                Assert.Equal(testCase.Ciphertext, ciphertext);
+                Assert.Equal(testCase.Tag, tag);
 
-                    byte[] plaintext = RandomNumberGenerator.GetBytes(testCase.Plaintext.Length);
-                    Assert.Throws<AuthenticationTagMismatchException>(
-                        () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData));
-                    AssertExtensions.FilledWith<byte>(0, plaintext);
-                }
+                ciphertext[0] ^= 1;
+
+                byte[] plaintext = RandomNumberGenerator.GetBytes(testCase.Plaintext.Length);
+                Assert.Throws<AuthenticationTagMismatchException>(
+                    () => aesGcm.Decrypt(testCase.Nonce, ciphertext, tag, plaintext, testCase.AssociatedData));
+                AssertExtensions.FilledWith<byte>(0, plaintext);
             }
         }
 
@@ -437,7 +505,7 @@ namespace System.Security.Cryptography.Tests
             byte[] ciphertext = Array.Empty<byte>();
             byte[] tag = "58e2fccefa7e3061367f1d57a4e7455a".HexToByteArray();
 
-            AesGcm aesGcm = new AesGcm(key);
+            AesGcm aesGcm = new AesGcm(key, tag.Length);
             aesGcm.Dispose();
 
             Assert.Throws<ObjectDisposedException>(() => aesGcm.Encrypt(nonce, plaintext, ciphertext, new byte[tag.Length]));
@@ -923,8 +991,13 @@ namespace System.Security.Cryptography.Tests
         {
             byte[] key = RandomNumberGenerator.GetBytes(256 / 8);
 
+#pragma warning disable SYSLIB0053
             Assert.Throws<PlatformNotSupportedException>(() => new AesGcm(key));
             Assert.Throws<PlatformNotSupportedException>(() => new AesGcm(key.AsSpan()));
+#pragma warning restore SYSLIB0053
+
+            Assert.Throws<PlatformNotSupportedException>(() => new AesGcm(key, AesGcm.TagByteSizes.MinSize));
+            Assert.Throws<PlatformNotSupportedException>(() => new AesGcm(key.AsSpan(), AesGcm.TagByteSizes.MinSize));
         }
 
         [Fact]