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..75639b420001c3 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 @@
Stream was not writable.
+
+ The value specified in bits must be a whole number of bytes.
+
Non-negative number required.
@@ -108,6 +111,15 @@
The specified OID ({0}) does not represent a known hash algorithm.
+
+ The specified plaintext size is not valid for the the padding and block size.
+
+
+ The specified plaintext size is not valid for the the padding and feedback size.
+
+
+ The specified plaintext size is too large.
+
Method not supported. Derived class must override.
@@ -129,4 +141,7 @@
The algorithm's implementation is incorrect.
+
+ The algorithm's block size is not supported.
+
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..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
@@ -222,6 +222,212 @@ public bool ValidKeySize(int bitLength)
return bitLength.IsLegalSize(validSizes);
}
+ ///
+ /// Gets the length of a ciphertext with a given padding mode and plaintext length in ECB mode.
+ ///
+ /// The padding mode used to pad the plaintext to the algorithm's block size.
+ /// The plaintext length, in bytes.
+ /// The length, in bytes, of the ciphertext with padding.
+ ///
+ ///
+ /// is a negative number.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// when padded is too large to represent as
+ /// a signed 32-bit integer.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a valid padding mode.
+ ///
+ ///
+ ///
+ ///
+ /// is not a positive integer.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a whole number of bytes. It must be divisible by 8.
+ ///
+ ///
+ ///
+ ///
+ /// The padding mode was used, but
+ /// is not a whole number of blocks.
+ ///
+ ///
+ public int GetCiphertextLengthEcb(int plaintextLength, PaddingMode paddingMode) =>
+ GetCiphertextLengthBlockAligned(plaintextLength, paddingMode);
+
+ ///
+ /// Gets the length of a ciphertext with a given padding mode and plaintext length in CBC mode.
+ ///
+ /// The padding mode used to pad the plaintext to the algorithm's block size.
+ /// The plaintext length, in bytes.
+ /// The length, in bytes, of the ciphertext with padding.
+ ///
+ ///
+ /// is a negative number.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// when padded is too large to represent as
+ /// a signed 32-bit integer.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a valid padding mode.
+ ///
+ ///
+ ///
+ ///
+ /// is not a positive integer.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a whole number of bytes. It must be divisible by 8.
+ ///
+ ///
+ ///
+ ///
+ /// The padding mode was used, but
+ /// is not a whole number of blocks.
+ ///
+ ///
+ 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 InvalidOperationException(SR.InvalidOperation_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);
+ }
+ }
+
+ ///
+ /// Gets the length of a ciphertext with a given padding mode and plaintext length in CFB mode.
+ ///
+ /// The padding mode used to pad the plaintext to the feedback size.
+ /// The plaintext length, in bytes.
+ /// The feedback size, in bits.
+ /// The length, in bytes, of the ciphertext with padding.
+ ///
+ ///
+ /// is not a positive number.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is a negative number.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// when padded is too large to represent as
+ /// a signed 32-bit integer.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a valid padding mode.
+ ///
+ ///
+ ///
+ ///
+ /// The padding mode was used, but
+ /// is not a whole number of blocks.
+ ///
+ ///
+ /// - or -
+ ///
+ ///
+ /// is not a whole number of bytes. It must be divisible by 8.
+ ///
+ ///
+ ///
+ /// accepts any value that is a valid feedback size, regardless if the algorithm
+ /// supports the specified feedback size.
+ ///
+ 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..8edfa2b0650aa5
--- /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("plaintextLength", () => alg.GetCiphertextLengthCbc(-1, mode));
+ AssertExtensions.Throws("plaintextLength", () => alg.GetCiphertextLengthEcb(-1, mode));
+ AssertExtensions.Throws("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("plaintextLength", () => alg.GetCiphertextLengthCbc(0x7FFFFFF1, mode));
+ AssertExtensions.Throws("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("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(() => alg.GetCiphertextLengthCbc(16, mode));
+ Assert.Throws(() => alg.GetCiphertextLengthEcb(16, mode));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllPaddingModes))]
+ public static void GetCiphertextLengthCfb_ThrowsForNonByteFeedbackSize(PaddingMode mode)
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm();
+ AssertExtensions.Throws("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(() => alg.GetCiphertextLengthCbc(16, mode));
+ Assert.Throws(() => alg.GetCiphertextLengthEcb(16, mode));
+ }
+
+ [Theory]
+ [MemberData(nameof(AllPaddingModes))]
+ public static void GetCiphertextLengthCfb_ThrowsForZeroFeedbackSize(PaddingMode mode)
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm();
+ AssertExtensions.Throws("feedbackSizeInBits", () =>
+ alg.GetCiphertextLengthCfb(16, mode, 0));
+ }
+
+ [Fact]
+ public static void GetCiphertextLength_ThrowsForInvalidPaddingMode()
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+ PaddingMode mode = (PaddingMode)(-1);
+ Assert.Throws("paddingMode", () => alg.GetCiphertextLengthCbc(16, mode));
+ Assert.Throws("paddingMode", () => alg.GetCiphertextLengthEcb(16, mode));
+ Assert.Throws("paddingMode", () => alg.GetCiphertextLengthCfb(16, mode));
+ }
+
+ [Fact]
+ public static void GetCiphertextLengthBlock_NoPaddingAndPlaintextSizeNotBlockAligned()
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+ Assert.Throws("plaintextLength", () => alg.GetCiphertextLengthCbc(17, PaddingMode.None));
+ Assert.Throws("plaintextLength", () => alg.GetCiphertextLengthEcb(17, PaddingMode.None));
+ }
+
+ [Fact]
+ public static void GetCiphertextLengthCfb_NoPaddingAndPlaintextSizeNotFeedbackAligned()
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm();
+ Assert.Throws("plaintextLength", () =>
+ alg.GetCiphertextLengthCfb(17, PaddingMode.None, feedbackSizeInBits: 128));
+ }
+
+ public static IEnumerable