From 38ed2d6fe92464f854e27d8e47cfe1414ba3acd6 Mon Sep 17 00:00:00 2001
From: Kevin Jones <kevin@vcsjones.com>
Date: Sat, 18 Apr 2020 20:55:01 -0400
Subject: [PATCH 1/2] Add GetCiphertextLength for CBC, CFB, and ECB.

---
 ...System.Security.Cryptography.Primitives.cs |   3 +
 .../src/Resources/Strings.resx                |  15 ++
 .../Cryptography/SymmetricAlgorithm.cs        | 203 +++++++++++++++
 .../tests/SymmetricAlgorithmTests.cs          | 235 ++++++++++++++++++
 ...urity.Cryptography.Primitives.Tests.csproj |   1 +
 5 files changed, 457 insertions(+)
 create mode 100644 src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs

diff --git a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs
index 0c7b73074e25f6..dbe106511bcdbf 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs
+++ b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs
@@ -248,6 +248,9 @@ public void Dispose() { }
         protected virtual void Dispose(bool disposing) { }
         public abstract void GenerateIV();
         public abstract void GenerateKey();
+        public int GetCiphertextLengthCbc(int plaintextLength, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.PKCS7) { throw null; }
+        public int GetCiphertextLengthCfb(int plaintextLength, System.Security.Cryptography.PaddingMode paddingMode = System.Security.Cryptography.PaddingMode.None, int feedbackSizeInBits = 8) { throw null; }
+        public int GetCiphertextLengthEcb(int plaintextLength, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
         public bool ValidKeySize(int bitLength) { throw null; }
     }
 }
diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
index a1b8604ebcd3a0..e930a5af7f9cac 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
+++ b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
@@ -72,6 +72,9 @@
   <data name="Argument_StreamNotWritable" xml:space="preserve">
     <value>Stream was not writable.</value>
   </data>
+  <data name="Argument_BitsMustBeWholeBytes" xml:space="preserve">
+    <value>The value specified in bits must be a whole number of bytes.</value>
+  </data>
   <data name="ArgumentOutOfRange_NeedNonNegNum" xml:space="preserve">
     <value>Non-negative number required.</value>
   </data>
@@ -108,6 +111,18 @@
   <data name="Cryptography_InvalidHashAlgorithmOid" xml:space="preserve">
     <value>The specified OID ({0}) does not represent a known hash algorithm.</value>
   </data>
+  <data name="Cryptography_UnsupportedBlockSize" xml:space="preserve">
+    <value>The algorithm's block size is not supported.</value>
+  </data>
+  <data name="Cryptography_MatchBlockSize" xml:space="preserve">
+    <value>The specified plaintext size is not valid for the the padding and block size.</value>
+  </data>
+  <data name="Cryptography_MatchFeedbackSize" xml:space="preserve">
+    <value>The specified plaintext size is not valid for the the padding and feedback size.</value>
+  </data>
+  <data name="Cryptography_PlaintextTooLarge" xml:space="preserve">
+    <value>The specified plaintext size is too large.</value>
+  </data>
   <data name="NotSupported_SubclassOverride" xml:space="preserve">
     <value>Method not supported. Derived class must override.</value>
   </data>
diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
index 9ad7b01d7b6cf5..5c1112a19add02 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
+++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
@@ -222,6 +222,209 @@ public bool ValidKeySize(int bitLength)
             return bitLength.IsLegalSize(validSizes);
         }
 
+        /// <summary>
+        /// Gets the length of a ciphertext with a given padding mode and plaintext length in ECB mode.
+        /// </summary>
+        /// <param name="paddingMode">The padding mode used to pad the plaintext to the algorithm's block size.</param>
+        /// <param name="plaintextLength">The plaintext length, in bytes.</param>
+        /// <returns>The length, in bytes, of the ciphertext with padding.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> is a negative number.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> when padded is too large to represent as
+        ///   a signed 32-bit integer.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="paddingMode" /> is not a valid padding mode.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <para>
+        ///   <see cref="BlockSize" /> is not a positive integer.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <see cref="BlockSize" /> is not a whole number of bytes. It must be divisible by 8.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <para>
+        ///   The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
+        ///   is not a whole number of blocks.
+        ///   </para>
+        /// </exception>
+        public int GetCiphertextLengthEcb(int plaintextLength, PaddingMode paddingMode) =>
+            GetCiphertextLengthBlockAligned(plaintextLength, paddingMode);
+
+        /// <summary>
+        /// Gets the length of a ciphertext with a given padding mode and plaintext length in CBC mode.
+        /// </summary>
+        /// <param name="paddingMode">The padding mode used to pad the plaintext to the algorithm's block size.</param>
+        /// <param name="plaintextLength">The plaintext length, in bytes.</param>
+        /// <returns>The length, in bytes, of the ciphertext with padding.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> is a negative number.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> when padded is too large to represent as
+        ///   a signed 32-bit integer.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="paddingMode" /> is not a valid padding mode.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="CryptographicException">
+        ///   <para>
+        ///   <see cref="BlockSize" /> is not a positive integer.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <see cref="BlockSize" /> is not a whole number of bytes. It must be divisible by 8.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <para>
+        ///   The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
+        ///   is not a whole number of blocks.
+        ///   </para>
+        /// </exception>
+        public int GetCiphertextLengthCbc(int plaintextLength, PaddingMode paddingMode = PaddingMode.PKCS7) =>
+            GetCiphertextLengthBlockAligned(plaintextLength, paddingMode);
+
+        private int GetCiphertextLengthBlockAligned(int plaintextLength, PaddingMode paddingMode)
+        {
+            if (plaintextLength < 0)
+                throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+            int blockSizeBits = BlockSize; // The BlockSize property is in bits.
+
+            if (blockSizeBits <= 0 || (blockSizeBits & 0b111) != 0)
+                throw new CryptographicException(SR.Cryptography_UnsupportedBlockSize);
+
+            int blockSizeBytes = blockSizeBits >> 3;
+            int wholeBlocks = Math.DivRem(plaintextLength, blockSizeBytes, out int remainder) * blockSizeBytes;
+
+            switch (paddingMode)
+            {
+                case PaddingMode.None when remainder != 0:
+                    throw new ArgumentException(SR.Cryptography_MatchBlockSize, nameof(plaintextLength));
+                case PaddingMode.None:
+                case PaddingMode.Zeros when remainder == 0:
+                    return plaintextLength;
+                case PaddingMode.Zeros:
+                case PaddingMode.PKCS7:
+                case PaddingMode.ANSIX923:
+                case PaddingMode.ISO10126:
+                    if (int.MaxValue - wholeBlocks < blockSizeBytes)
+                    {
+                        throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.Cryptography_PlaintextTooLarge);
+                    }
+
+                    return wholeBlocks + blockSizeBytes;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(paddingMode), SR.Cryptography_InvalidPaddingMode);
+            }
+        }
+
+
+        /// <summary>
+        /// Gets the length of a ciphertext with a given padding mode and plaintext length in CFB mode.
+        /// </summary>
+        /// <param name="paddingMode">The padding mode used to pad the plaintext to the feedback size.</param>
+        /// <param name="plaintextLength">The plaintext length, in bytes.</param>
+        /// <param name="feedbackSizeInBits">The feedback size, in bits.</param>
+        /// <returns>The length, in bytes, of the ciphertext with padding.</returns>
+        /// <exception cref="ArgumentOutOfRangeException">
+        ///   <para>
+        ///   <paramref name="feedbackSizeInBits" /> is not a positive number.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> is a negative number.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="plaintextLength" /> when padded is too large to represent as
+        ///   a signed 32-bit integer.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="paddingMode" /> is not a valid padding mode.
+        ///   </para>
+        /// </exception>
+        /// <exception cref="ArgumentException">
+        ///   <para>
+        ///   The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
+        ///   is not a whole number of blocks.
+        ///   </para>
+        ///   <para>
+        ///   - or -
+        ///   </para>
+        ///   <para>
+        ///   <paramref name="feedbackSizeInBits" /> is not a whole number of bytes. It must be divisible by 8.
+        ///   </para>
+        /// </exception>
+        public int GetCiphertextLengthCfb(int plaintextLength, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8)
+        {
+            if (plaintextLength < 0)
+                throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+            if (feedbackSizeInBits <= 0)
+                throw new ArgumentOutOfRangeException(nameof(feedbackSizeInBits), SR.ArgumentOutOfRange_NeedPosNum);
+
+            if ((feedbackSizeInBits & 0b111) != 0)
+                throw new ArgumentException(SR.Argument_BitsMustBeWholeBytes, nameof(feedbackSizeInBits));
+
+            int feedbackSizeInBytes = feedbackSizeInBits >> 3;
+            int feedbackAligned = Math.DivRem(plaintextLength, feedbackSizeInBytes, out int remainder) * feedbackSizeInBytes;
+
+            switch (paddingMode)
+            {
+                case PaddingMode.None when remainder != 0:
+                    throw new ArgumentException(SR.Cryptography_MatchFeedbackSize, nameof(plaintextLength));
+                case PaddingMode.None:
+                case PaddingMode.Zeros when remainder == 0:
+                    return plaintextLength;
+                case PaddingMode.Zeros:
+                case PaddingMode.PKCS7:
+                case PaddingMode.ANSIX923:
+                case PaddingMode.ISO10126:
+                    if (int.MaxValue - feedbackAligned < feedbackSizeInBytes)
+                    {
+                        throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.Cryptography_PlaintextTooLarge);
+                    }
+
+                    return feedbackAligned + feedbackSizeInBytes;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(paddingMode), SR.Cryptography_InvalidPaddingMode);
+            }
+        }
+
         protected CipherMode ModeValue;
         protected PaddingMode PaddingValue;
         protected byte[]? KeyValue;
diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs
new file mode 100644
index 00000000000000..74dea680ab6d81
--- /dev/null
+++ b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs
@@ -0,0 +1,235 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Security.Cryptography.Primitives.Tests
+{
+    public static class SymmetricAlgorithmTests
+    {
+        [Theory]
+        [MemberData(nameof(CiphertextLengthTheories))]
+        public static void GetCiphertextLengthBlock_ValidInputs(
+            PaddingMode mode,
+            int plaintextSize,
+            int expectedCiphertextSize,
+            int alignmentSizeInBits)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = alignmentSizeInBits };
+            int ciphertextSizeCbc = alg.GetCiphertextLengthCbc(plaintextSize, mode);
+            int ciphertextSizeEcb = alg.GetCiphertextLengthEcb(plaintextSize, mode);
+            Assert.Equal(expectedCiphertextSize, ciphertextSizeCbc);
+            Assert.Equal(expectedCiphertextSize, ciphertextSizeEcb);
+        }
+
+        [Theory]
+        [MemberData(nameof(CiphertextLengthTheories))]
+        public static void GetCiphertextLengthCfb_ValidInputs(
+            PaddingMode mode,
+            int plaintextSize,
+            int expectedCiphertextSize,
+            int alignmentSizeInBits)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm();
+            int ciphertextSizeCfb = alg.GetCiphertextLengthCfb(plaintextSize, mode, alignmentSizeInBits);
+            Assert.Equal(expectedCiphertextSize, ciphertextSizeCfb);
+        }
+
+        [Theory]
+        [MemberData(nameof(AllPaddingModes))]
+        public static void GetCiphertextLength_ThrowsForNegativeInput(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () => alg.GetCiphertextLengthCbc(-1, mode));
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () => alg.GetCiphertextLengthEcb(-1, mode));
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () => alg.GetCiphertextLengthCfb(-1, mode));
+        }
+
+        [Theory]
+        [InlineData(PaddingMode.ANSIX923)]
+        [InlineData(PaddingMode.ISO10126)]
+        [InlineData(PaddingMode.PKCS7)]
+        [InlineData(PaddingMode.Zeros)]
+        public static void GetCiphertextLengthBlock_ThrowsForOverflow(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () => alg.GetCiphertextLengthCbc(0x7FFFFFF1, mode));
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () => alg.GetCiphertextLengthEcb(0x7FFFFFF1, mode));
+        }
+
+        [Theory]
+        [InlineData(PaddingMode.ANSIX923)]
+        [InlineData(PaddingMode.ISO10126)]
+        [InlineData(PaddingMode.PKCS7)]
+        [InlineData(PaddingMode.Zeros)]
+        public static void GetCiphertextLengthCfb_ThrowsForOverflow(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm();
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("plaintextLength", () =>
+                alg.GetCiphertextLengthCfb(0x7FFFFFFF, mode, feedbackSizeInBits: 128));
+        }
+
+        [Theory]
+        [MemberData(nameof(AllPaddingModes))]
+        public static void GetCiphertextLengthBlock_ThrowsForNonByteBlockSize(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 5 };
+            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthCbc(16, mode));
+            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthEcb(16, mode));
+        }
+
+        [Theory]
+        [MemberData(nameof(AllPaddingModes))]
+        public static void GetCiphertextLengthCfb_ThrowsForNonByteFeedbackSize(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm();
+            AssertExtensions.Throws<ArgumentException>("feedbackSizeInBits", () =>
+                alg.GetCiphertextLengthCfb(16, mode, 7));
+        }
+
+        [Theory]
+        [MemberData(nameof(AllPaddingModes))]
+        public static void GetCiphertextLengthBlock_ThrowsForZeroBlockSize(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 0 };
+            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthCbc(16, mode));
+            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthEcb(16, mode));
+        }
+
+        [Theory]
+        [MemberData(nameof(AllPaddingModes))]
+        public static void GetCiphertextLengthCfb_ThrowsForZeroFeedbackSize(PaddingMode mode)
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm();
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("feedbackSizeInBits", () =>
+                alg.GetCiphertextLengthCfb(16, mode, 0));
+        }
+
+        [Fact]
+        public static void GetCiphertextLength_ThrowsForInvalidPaddingMode()
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+            PaddingMode mode = (PaddingMode)(-1);
+            Assert.Throws<ArgumentOutOfRangeException>("paddingMode", () => alg.GetCiphertextLengthCbc(16, mode));
+            Assert.Throws<ArgumentOutOfRangeException>("paddingMode", () => alg.GetCiphertextLengthEcb(16, mode));
+            Assert.Throws<ArgumentOutOfRangeException>("paddingMode", () => alg.GetCiphertextLengthCfb(16, mode));
+        }
+
+        [Fact]
+        public static void GetCiphertextLengthBlock_NoPaddingAndPlaintextSizeNotBlockAligned()
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+            Assert.Throws<ArgumentException>("plaintextLength", () => alg.GetCiphertextLengthCbc(17, PaddingMode.None));
+            Assert.Throws<ArgumentException>("plaintextLength", () => alg.GetCiphertextLengthEcb(17, PaddingMode.None));
+        }
+
+        [Fact]
+        public static void GetCiphertextLengthCfb_NoPaddingAndPlaintextSizeNotFeedbackAligned()
+        {
+            AnySizeAlgorithm alg = new AnySizeAlgorithm();
+            Assert.Throws<ArgumentException>("plaintextLength", () =>
+                alg.GetCiphertextLengthCfb(17, PaddingMode.None, feedbackSizeInBits: 128));
+        }
+
+        public static IEnumerable<object[]> CiphertextLengthTheories
+        {
+            get
+            {
+                // new object[] { PaddingMode mode, int plaintextSize, int expectedCiphertextSize, int alignmentSizeInBits }
+
+                PaddingMode[] fullPaddings = new[] {
+                    PaddingMode.ANSIX923,
+                    PaddingMode.ISO10126,
+                    PaddingMode.PKCS7,
+                };
+
+                foreach (PaddingMode mode in fullPaddings)
+                {
+                    // 128-bit aligned value
+                    yield return new object[] { mode, 0, 16, 128 };
+                    yield return new object[] { mode, 15, 16, 128 };
+                    yield return new object[] { mode, 16, 32, 128 };
+                    yield return new object[] { mode, 17, 32, 128 };
+                    yield return new object[] { mode, 1023, 1024, 128 };
+                    yield return new object[] { mode, 0x7FFFFFEF, 0x7FFFFFF0, 128 };
+
+                    // 64-bit aligned value
+                    yield return new object[] { mode, 0, 8, 64 };
+                    yield return new object[] { mode, 15, 16, 64 };
+                    yield return new object[] { mode, 16, 24, 64 };
+                    yield return new object[] { mode, 17, 24, 64 };
+                    yield return new object[] { mode, 1023, 1024, 64 };
+                    yield return new object[] { mode, 0x7FFFFFF7, 0x7FFFFFF8, 64 };
+
+                    // 8-bit aligned value
+                    yield return new object[] { mode, 0, 1, 8 };
+                    yield return new object[] { mode, 7, 8, 8 };
+                    yield return new object[] { mode, 16, 17, 8 };
+                    yield return new object[] { mode, 17, 18, 8 };
+                    yield return new object[] { mode, 1023, 1024, 8 };
+                    yield return new object[] { mode, 0x7FFFFFFE, 0x7FFFFFFF, 8 };
+
+                    // 176-bit (22 byte) aligned value
+                    yield return new object[] { mode, 0, 22, 176 };
+                    yield return new object[] { mode, 21, 22, 176 };
+                    yield return new object[] { mode, 22, 44, 176 };
+                    yield return new object[] { mode, 43, 44, 176 };
+                    yield return new object[] { mode, 1011, 1012, 176 };
+                    yield return new object[] { mode, 0x7FFFFFFD, 0x7FFFFFFE, 176 };
+                }
+
+                PaddingMode[] noPadOnAlignSize = new[] {
+                    PaddingMode.Zeros,
+                    PaddingMode.None,
+                };
+
+                foreach(PaddingMode mode in noPadOnAlignSize)
+                {
+                    // 128-bit aligned
+                    yield return new object[] { mode, 16, 16, 128 };
+                    yield return new object[] { mode, 00, 00, 128 };
+                    yield return new object[] { mode, 1024, 1024, 128 };
+                    yield return new object[] { mode, 0x7FFFFFF0, 0x7FFFFFF0, 128 };
+
+                    // 8-bit aligned
+                    yield return new object[] { mode, 0x7FFFFFFF, 0x7FFFFFFF, 8 };
+                }
+
+                // Pad only when length is not aligned
+                yield return new object[] { PaddingMode.Zeros, 15, 16, 128 };
+                yield return new object[] { PaddingMode.Zeros, 17, 32, 128 };
+                yield return new object[] { PaddingMode.Zeros, 0x7FFFFFEF, 0x7FFFFFF0, 128 };
+            }
+        }
+
+        public static IEnumerable<object[]> AllPaddingModes
+        {
+            get
+            {
+                yield return new object[] { PaddingMode.ANSIX923 };
+                yield return new object[] { PaddingMode.ISO10126 };
+                yield return new object[] { PaddingMode.PKCS7 };
+                yield return new object[] { PaddingMode.Zeros };
+                yield return new object[] { PaddingMode.None };
+            }
+        }
+
+        private class AnySizeAlgorithm : SymmetricAlgorithm
+        {
+            public override int BlockSize
+            {
+                get => BlockSizeValue;
+                set => BlockSizeValue = value;
+            }
+
+            public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[] rgbIV) =>
+                throw new NotImplementedException();
+            public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) =>
+                throw new NotImplementedException();
+            public override void GenerateIV() => throw new NotImplementedException();
+            public override void GenerateKey() => throw new NotImplementedException();
+        }
+    }
+}
diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj b/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj
index caa018377958b3..9fde6f5f339cb0 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj
+++ b/src/libraries/System.Security.Cryptography.Primitives/tests/System.Security.Cryptography.Primitives.Tests.csproj
@@ -22,6 +22,7 @@
     <Compile Include="Sum32Hash.cs" />
     <Compile Include="SymmetricAlgorithm\Minimal.cs" />
     <Compile Include="SymmetricAlgorithm\Trivial.cs" />
+    <Compile Include="SymmetricAlgorithmTests.cs" />
     <Compile Include="ZeroMemoryTests.cs" />
     <Compile Include="$(CommonPath)System\Threading\Tasks\TaskToApm.cs" Link="Common\System\Threading\Tasks\TaskToApm.cs" />
     <Compile Include="$(CommonTestPath)Tests\System\IO\StreamConformanceTests.cs" Link="Common\System\IO\StreamConformanceTests.cs" />

From 314c00b829455b702c267e0035a7e0aa48dc645d Mon Sep 17 00:00:00 2001
From: Kevin Jones <kevin@vcsjones.com>
Date: Fri, 20 Nov 2020 11:58:29 -0500
Subject: [PATCH 2/2] Respond to code review feedback.

* Too many empty lines.
* Change CryptographicException to InvalidOperationException.
* Add a remark that CFB accepts and valid feedback size, even if
    the underlying algorithm does not support that feedback size.
---
 .../src/Resources/Strings.resx                            | 6 +++---
 .../System/Security/Cryptography/SymmetricAlgorithm.cs    | 7 +++++--
 .../tests/SymmetricAlgorithmTests.cs                      | 8 ++++----
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
index e930a5af7f9cac..75639b420001c3 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
+++ b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx
@@ -111,9 +111,6 @@
   <data name="Cryptography_InvalidHashAlgorithmOid" xml:space="preserve">
     <value>The specified OID ({0}) does not represent a known hash algorithm.</value>
   </data>
-  <data name="Cryptography_UnsupportedBlockSize" xml:space="preserve">
-    <value>The algorithm's block size is not supported.</value>
-  </data>
   <data name="Cryptography_MatchBlockSize" xml:space="preserve">
     <value>The specified plaintext size is not valid for the the padding and block size.</value>
   </data>
@@ -144,4 +141,7 @@
   <data name="InvalidOperation_IncorrectImplementation" xml:space="preserve">
     <value>The algorithm's implementation is incorrect.</value>
   </data>
+  <data name="InvalidOperation_UnsupportedBlockSize" xml:space="preserve">
+    <value>The algorithm's block size is not supported.</value>
+  </data>
 </root>
diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
index 5c1112a19add02..0ee176f21ec0ac 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
+++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs
@@ -318,7 +318,7 @@ private int GetCiphertextLengthBlockAligned(int plaintextLength, PaddingMode pad
             int blockSizeBits = BlockSize; // The BlockSize property is in bits.
 
             if (blockSizeBits <= 0 || (blockSizeBits & 0b111) != 0)
-                throw new CryptographicException(SR.Cryptography_UnsupportedBlockSize);
+                throw new InvalidOperationException(SR.InvalidOperation_UnsupportedBlockSize);
 
             int blockSizeBytes = blockSizeBits >> 3;
             int wholeBlocks = Math.DivRem(plaintextLength, blockSizeBytes, out int remainder) * blockSizeBytes;
@@ -345,7 +345,6 @@ private int GetCiphertextLengthBlockAligned(int plaintextLength, PaddingMode pad
             }
         }
 
-
         /// <summary>
         /// Gets the length of a ciphertext with a given padding mode and plaintext length in CFB mode.
         /// </summary>
@@ -389,6 +388,10 @@ private int GetCiphertextLengthBlockAligned(int plaintextLength, PaddingMode pad
         ///   <paramref name="feedbackSizeInBits" /> is not a whole number of bytes. It must be divisible by 8.
         ///   </para>
         /// </exception>
+        /// <remarks>
+        /// <paramref name="feedbackSizeInBits" /> accepts any value that is a valid feedback size, regardless if the algorithm
+        /// supports the specified feedback size.
+        /// </remarks>
         public int GetCiphertextLengthCfb(int plaintextLength, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8)
         {
             if (plaintextLength < 0)
diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs
index 74dea680ab6d81..8edfa2b0650aa5 100644
--- a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs
+++ b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs
@@ -76,8 +76,8 @@ public static void GetCiphertextLengthCfb_ThrowsForOverflow(PaddingMode mode)
         public static void GetCiphertextLengthBlock_ThrowsForNonByteBlockSize(PaddingMode mode)
         {
             AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 5 };
-            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthCbc(16, mode));
-            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthEcb(16, mode));
+            Assert.Throws<InvalidOperationException>(() => alg.GetCiphertextLengthCbc(16, mode));
+            Assert.Throws<InvalidOperationException>(() => alg.GetCiphertextLengthEcb(16, mode));
         }
 
         [Theory]
@@ -94,8 +94,8 @@ public static void GetCiphertextLengthCfb_ThrowsForNonByteFeedbackSize(PaddingMo
         public static void GetCiphertextLengthBlock_ThrowsForZeroBlockSize(PaddingMode mode)
         {
             AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 0 };
-            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthCbc(16, mode));
-            Assert.Throws<CryptographicException>(() => alg.GetCiphertextLengthEcb(16, mode));
+            Assert.Throws<InvalidOperationException>(() => alg.GetCiphertextLengthCbc(16, mode));
+            Assert.Throws<InvalidOperationException>(() => alg.GetCiphertextLengthEcb(16, mode));
         }
 
         [Theory]
