This change adds SymmetricAlgorithm.EncryptEcb, SymmetricAlgorithm.DecryptEcb, their
respective Try- and -Core methods, derived type implementations thereof, and tests.
There's an open question of should these members on on the base class throw or
"succeed if the Mode property is in agreement with the algorithm". While the latter is "nicer",
just throwing is easier to reason about, and that's the current behavior.
Co-authored-by: Jeremy Barton <jbarton@microsoft.com>
Debug.Assert(input.Length > 0);
Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
- int numBytesWritten;
+ int numBytesWritten = 0;
- if (_encrypting)
+ // BCryptEncrypt and BCryptDecrypt can do in place encryption, but if the buffers overlap
+ // the offset must be zero. In that case, we need to copy to a temporary location.
+ if (input.Overlaps(output, out int offset) && offset != 0)
{
- numBytesWritten = Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output);
+ byte[] rented = CryptoPool.Rent(output.Length);
+
+ try
+ {
+ numBytesWritten = BCryptTransform(input, rented);
+ rented.AsSpan(0, numBytesWritten).CopyTo(output);
+ }
+ finally
+ {
+ CryptoPool.Return(rented, clearSize: numBytesWritten);
+ }
}
else
{
- numBytesWritten = Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
+ numBytesWritten = BCryptTransform(input, output);
}
if (numBytesWritten != input.Length)
}
return numBytesWritten;
+
+ int BCryptTransform(ReadOnlySpan<byte> input, Span<byte> output)
+ {
+ return _encrypting ?
+ Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output) :
+ Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output);
+ }
}
public override int TransformFinal(ReadOnlySpan<byte> input, Span<byte> output)
return (byte[])(src.Clone());
}
- public static int GetPaddingSize(this SymmetricAlgorithm algorithm)
+ public static int GetPaddingSize(this SymmetricAlgorithm algorithm, CipherMode mode, int feedbackSizeBits)
{
// CFB8 does not require any padding at all
// otherwise, it is always required to pad for block size
- if (algorithm.Mode == CipherMode.CFB && algorithm.FeedbackSize == 8)
+ if (mode == CipherMode.CFB && feedbackSizeBits == 8)
return 1;
return algorithm.BlockSize / 8;
base.Dispose(disposing);
}
+ public override unsafe bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)
+ {
+ if (input.Length % PaddingSizeBytes != 0)
+ throw new CryptographicException(SR.Cryptography_PartialBlock);
+
+ // If there is no padding that needs to be removed, and the output buffer is large enough to hold
+ // the resulting plaintext, we can decrypt directly in to the output buffer.
+ // We do not do this for modes that require padding removal.
+ //
+ // This is not done for padded ciphertexts because we don't know if the padding is valid
+ // until it's been decrypted. We don't want to decrypt in to a user-supplied buffer and then throw
+ // a padding exception after we've already filled the user buffer with plaintext. We should only
+ // release the plaintext to the caller once we know the padding is valid.
+ if (!DepaddingRequired)
+ {
+ if (output.Length >= input.Length)
+ {
+ bytesWritten = BasicSymmetricCipher.TransformFinal(input, output);
+ return true;
+ }
+
+ // If no padding is going to be removed, we know the buffer is too small and we can bail out.
+ bytesWritten = 0;
+ return false;
+ }
+
+ byte[] rentedBuffer = CryptoPool.Rent(input.Length);
+ Span<byte> buffer = rentedBuffer.AsSpan(0, input.Length);
+ Span<byte> decryptedBuffer = default;
+
+ fixed (byte* pBuffer = buffer)
+ {
+ try
+ {
+ int transformWritten = BasicSymmetricCipher.TransformFinal(input, buffer);
+ decryptedBuffer = buffer.Slice(0, transformWritten);
+ int unpaddedLength = GetPaddingLength(decryptedBuffer); // validates padding
+
+ if (unpaddedLength > output.Length)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ decryptedBuffer.Slice(0, unpaddedLength).CopyTo(output);
+ bytesWritten = unpaddedLength;
+ return true;
+ }
+ finally
+ {
+ CryptographicOperations.ZeroMemory(decryptedBuffer);
+ CryptoPool.Return(rentedBuffer, clearSize: 0); // ZeroMemory clears the part of the buffer that was written to.
+ }
+ }
+ }
+
private void Reset()
{
if (_heldoverCipher != null)
return buffer;
}
+ public override bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten)
+ {
+ int ciphertextLength = GetCiphertextLength(input.Length);
+
+ if (output.Length < ciphertextLength)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ // Copy the input to the output, and apply padding if required. This will not throw since the
+ // output length has already been checked, and PadBlock will not copy from input to output
+ // until it has checked that it will be able to apply padding correctly.
+ int padWritten = PadBlock(input, output);
+
+ // Do an in-place encrypt. All of our implementations support this, either natively
+ // or making a temporary buffer themselves if in-place is not supported by the native
+ // implementation.
+ Span<byte> paddedOutput = output.Slice(0, padWritten);
+ bytesWritten = BasicSymmetricCipher.TransformFinal(paddedOutput, paddedOutput);
+
+ // After padding, we should have an even number of blocks, and the same applies
+ // to the transform.
+ Debug.Assert(padWritten == bytesWritten);
+ return true;
+ }
+
private int GetCiphertextLength(int plaintextLength)
{
Debug.Assert(plaintextLength >= 0);
//
internal abstract class UniversalCryptoTransform : ICryptoTransform
{
- public static ICryptoTransform Create(
+ public static UniversalCryptoTransform Create(
PaddingMode paddingMode,
BasicSymmetricCipher cipher,
bool encrypting)
return output;
}
+ public abstract bool TransformOneShot(ReadOnlySpan<byte> input, Span<byte> output, out int bytesWritten);
+
protected virtual void Dispose(bool disposing)
{
if (disposing)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.Aes.Tests
+{
+ using Aes = System.Security.Cryptography.Aes;
+
+ public partial class AesCipherTests
+ {
+ private static byte[] s_aes128OneShotKey =
+ new byte[] { 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12 };
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EcbRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ // Even though we have set the instance to use CFB, the Ecb one shots should
+ // always be done in ECB.
+ aes.FeedbackSize = 8;
+ aes.Mode = CipherMode.CFB;
+ aes.Padding = padding == PaddingMode.None ? PaddingMode.PKCS7 : PaddingMode.None;
+
+ byte[] encrypted = aes.EncryptEcb(plaintext, padding);
+ byte[] decrypted = aes.DecryptEcb(encrypted, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted[..plaintext.Length]);
+ AssertFilledWith(0, plaintext.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+
+ decrypted = aes.DecryptEcb(ciphertext, padding);
+ encrypted = aes.EncryptEcb(decrypted, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (plaintext.Length == 0)
+ {
+ // Can't have a ciphertext length shorter than zero.
+ return;
+ }
+
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[plaintext.Length - 1];
+
+ bool result = aes.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (ciphertext.Length == 0)
+ {
+ // Can't have a too small buffer for zero.
+ return;
+ }
+
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[ciphertext.Length - 1];
+
+ bool result = aes.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ Span<byte> destinationBuffer = new byte[expectedPlaintextSize];
+
+ bool result = aes.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ int expectedCiphertextSize = aes.GetCiphertextLengthEcb(plaintext.Length, padding);
+ Span<byte> destinationBuffer = new byte[expectedCiphertextSize];
+
+ bool result = aes.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(expectedCiphertextSize, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ // Padding is random so we can't validate the last block.
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+
+ Span<byte> largeBuffer = new byte[expectedPlaintextSize + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, expectedPlaintextSize);
+ largeBuffer.Fill(0xCC);
+
+ bool result = aes.TryDecryptEcb(
+ ciphertext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+
+ Span<byte> excess = largeBuffer.Slice(destinationBuffer.Length);
+ AssertFilledWith(0xCC, excess);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ Span<byte> largeBuffer = new byte[ciphertext.Length + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, ciphertext.Length);
+ largeBuffer.Fill(0xCC);
+
+ bool result = aes.TryEncryptEcb(
+ plaintext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+
+ AssertFilledWith(0xCC, largeBuffer.Slice(ciphertext.Length));
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ int destinationSize = Math.Max(expectedPlaintextSize, ciphertext.Length) + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize);
+ Span<byte> ciphertextBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ ciphertext.AsSpan().CopyTo(ciphertextBuffer);
+
+ bool result = aes.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(ciphertextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+
+ int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ Span<byte> plaintextBuffer = buffer.Slice(plaintextOffset, plaintext.Length);
+ plaintext.AsSpan().CopyTo(plaintextBuffer);
+
+ bool result = aes.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+ byte[] decrypted = aes.DecryptEcb(ciphertext.AsSpan(), padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+ byte[] encrypted = aes.EncryptEcb(plaintext.AsSpan(), padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+ byte[] decrypted = aes.DecryptEcb(ciphertext, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (Aes aes = AesFactory.Create())
+ {
+ aes.Key = s_aes128OneShotKey;
+ byte[] encrypted = aes.EncryptEcb(plaintext, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = aes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ private static void AssertFilledWith(byte value, ReadOnlySpan<byte> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(value, span[i]);
+ }
+ }
+
+ public static IEnumerable<object[]> EcbTestCases
+ {
+ get
+ {
+ // plaintext requires no padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D,
+ 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9,
+ 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20,
+ 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D,
+ 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9,
+ },
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D,
+ 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D,
+ 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9,
+ 0xC1, 0xCA, 0x44, 0xE8, 0x05, 0xFF, 0xCB, 0x6F,
+ 0x4D, 0x7F, 0xE9, 0x17, 0x12, 0xFE, 0xBB, 0xAC,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD8, 0xF5, 0x32, 0x53, 0x82, 0x89, 0xEF, 0x7D,
+ 0x06, 0xB5, 0x06, 0xA4, 0xFD, 0x5B, 0xE9, 0xC9,
+ 0xD3, 0xAA, 0x33, 0x5B, 0x93, 0xC2, 0x3D, 0x96,
+ 0xFD, 0x89, 0xB1, 0x8C, 0x47, 0x75, 0x65, 0xA8,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ // plaintext requires padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3,
+ 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F,
+ 0x82, 0x8D, 0x60, 0xDC, 0x44, 0x26, 0xCF, 0xDE,
+ 0xC9, 0x54, 0x33, 0x47, 0xE2, 0x9E, 0xF0, 0x8C,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3,
+ 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F,
+ 0x49, 0x39, 0x1B, 0x69, 0xA1, 0xF3, 0x66, 0xE4,
+ 0x3E, 0x40, 0x51, 0xB8, 0x05, 0x60, 0xDC, 0xFD,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3,
+ 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F,
+ 0xCD, 0x0D, 0xCD, 0xEA, 0xA2, 0x1F, 0xC1, 0xC3,
+ 0x81, 0xEE, 0x8A, 0x63, 0x94, 0x5F, 0x85, 0x43,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xC3, 0x03, 0x87, 0xCD, 0x79, 0x19, 0xB1, 0xC3,
+ 0x50, 0x2C, 0x9D, 0x7B, 0x1F, 0x8A, 0xBE, 0x0F,
+ 0x9C, 0xE4, 0x0D, 0x2F, 0xCD, 0x82, 0x25, 0x0E,
+ 0x13, 0xAB, 0x4B, 0x6B, 0xC0, 0x9A, 0x21, 0x2E,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ new byte[]
+ {
+ 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20,
+ 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5,
+ },
+
+ PaddingMode.PKCS7,
+ };
+ }
+ }
+ }
+}
int? feedbackSize = default)
{
byte[] decryptedBytes;
+ byte[] oneShotDecryptedBytes = null;
using (Aes aes = AesFactory.Create())
{
cryptoStream.CopyTo(output);
decryptedBytes = output.ToArray();
}
+
+ if (mode == CipherMode.ECB)
+ {
+ oneShotDecryptedBytes = aes.DecryptEcb(encryptedBytes, aes.Padding);
+ }
}
Assert.NotEqual(encryptedBytes, decryptedBytes);
Assert.Equal(expectedAnswer, decryptedBytes);
+
+ if (oneShotDecryptedBytes is not null)
+ {
+ Assert.Equal(expectedAnswer, oneShotDecryptedBytes);
+ }
}
private static void TestAesTransformDirectKey(
{
byte[] liveEncryptBytes;
byte[] liveDecryptBytes;
+ byte[] liveOneShotDecryptBytes = null;
+ byte[] liveOneShotEncryptBytes = null;
using (Aes aes = AesFactory.Create())
{
liveEncryptBytes = AesEncryptDirectKey(aes, key, iv, plainBytes);
liveDecryptBytes = AesDecryptDirectKey(aes, key, iv, cipherBytes);
+
+ if (cipherMode == CipherMode.ECB)
+ {
+ aes.Key = key;
+ liveOneShotDecryptBytes = aes.DecryptEcb(cipherBytes, paddingMode);
+ liveOneShotEncryptBytes = aes.EncryptEcb(plainBytes, paddingMode);
+ }
}
Assert.Equal(cipherBytes, liveEncryptBytes);
Assert.Equal(plainBytes, liveDecryptBytes);
+
+ if (liveOneShotDecryptBytes is not null)
+ {
+ Assert.Equal(plainBytes, liveOneShotDecryptBytes);
+ }
+
+ if (liveOneShotEncryptBytes is not null)
+ {
+ Assert.Equal(cipherBytes, liveOneShotEncryptBytes);
+ }
}
private static byte[] AesEncryptDirectKey(Aes aes, byte[] key, byte[] iv, byte[] plainBytes)
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.Des.Tests
+{
+ public partial class DesCipherTests
+ {
+ private static byte[] s_desOneShotKey = new byte[]
+ {
+ 0x74, 0x4B, 0x93, 0x3A, 0x96, 0x33, 0x61, 0xD6
+ };
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EcbRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ byte[] encrypted = des.EncryptEcb(plaintext, padding);
+ byte[] decrypted = des.DecryptEcb(encrypted, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted[..plaintext.Length]);
+ AssertFilledWith(0, plaintext.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+
+ decrypted = des.DecryptEcb(ciphertext, padding);
+ encrypted = des.EncryptEcb(decrypted, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (plaintext.Length == 0)
+ {
+ // Can't have a ciphertext length shorter than zero.
+ return;
+ }
+
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ Span<byte> destinationBuffer = new byte[plaintext.Length - 1];
+
+ bool result = des.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (ciphertext.Length == 0)
+ {
+ // Can't have a too small buffer for zero.
+ return;
+ }
+
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ Span<byte> destinationBuffer = new byte[ciphertext.Length - 1];
+
+ bool result = des.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ Span<byte> destinationBuffer = new byte[expectedPlaintextSize];
+
+ bool result = des.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ int expectedCiphertextSize = des.GetCiphertextLengthEcb(plaintext.Length, padding);
+ Span<byte> destinationBuffer = new byte[expectedCiphertextSize];
+
+ bool result = des.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(expectedCiphertextSize, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ // Padding is random so we can't validate the last block.
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+
+ Span<byte> largeBuffer = new byte[expectedPlaintextSize + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, expectedPlaintextSize);
+ largeBuffer.Fill(0xCC);
+
+ bool result = des.TryDecryptEcb(
+ ciphertext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+
+ Span<byte> excess = largeBuffer.Slice(destinationBuffer.Length);
+ AssertFilledWith(0xCC, excess);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ Span<byte> largeBuffer = new byte[ciphertext.Length + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, ciphertext.Length);
+ largeBuffer.Fill(0xCC);
+
+ bool result = des.TryEncryptEcb(
+ plaintext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+
+ AssertFilledWith(0xCC, largeBuffer.Slice(ciphertext.Length));
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ int destinationSize = Math.Max(expectedPlaintextSize, ciphertext.Length) + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize);
+ Span<byte> ciphertextBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ ciphertext.AsSpan().CopyTo(ciphertextBuffer);
+
+ bool result = des.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(ciphertextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+
+ int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ Span<byte> plaintextBuffer = buffer.Slice(plaintextOffset, plaintext.Length);
+ plaintext.AsSpan().CopyTo(plaintextBuffer);
+
+ bool result = des.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+ byte[] decrypted = des.DecryptEcb(ciphertext.AsSpan(), padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+ byte[] encrypted = des.EncryptEcb(plaintext.AsSpan(), padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+ byte[] decrypted = des.DecryptEcb(ciphertext, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (DES des = DESFactory.Create())
+ {
+ des.Key = s_desOneShotKey;
+ byte[] encrypted = des.EncryptEcb(plaintext, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = des.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ private static void AssertFilledWith(byte value, ReadOnlySpan<byte> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(value, span[i]);
+ }
+ }
+
+ public static IEnumerable<object[]> EcbTestCases
+ {
+ get
+ {
+ // plaintext requires no padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEE, 0x8B, 0xA7, 0xEE, 0x11, 0x84, 0x1D, 0xA2,
+ 0xC4, 0x16, 0xB4, 0x05, 0x83, 0xA0, 0x60, 0x37,
+ 0xED, 0xD9, 0xE3, 0xFC, 0xC6, 0x55, 0xDC, 0x32,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEE, 0x8B, 0xA7, 0xEE, 0x11, 0x84, 0x1D, 0xA2,
+ 0xC4, 0x16, 0xB4, 0x05, 0x83, 0xA0, 0x60, 0x37,
+ },
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEE, 0x8B, 0xA7, 0xEE, 0x11, 0x84, 0x1D, 0xA2,
+ 0xC4, 0x16, 0xB4, 0x05, 0x83, 0xA0, 0x60, 0x37,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEE, 0x8B, 0xA7, 0xEE, 0x11, 0x84, 0x1D, 0xA2,
+ 0xC4, 0x16, 0xB4, 0x05, 0x83, 0xA0, 0x60, 0x37,
+ 0xEC, 0x52, 0xA1, 0x7E, 0x52, 0x54, 0x6E, 0x9E,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEE, 0x8B, 0xA7, 0xEE, 0x11, 0x84, 0x1D, 0xA2,
+ 0xC4, 0x16, 0xB4, 0x05, 0x83, 0xA0, 0x60, 0x37,
+ 0x44, 0x4C, 0xA5, 0xC2, 0xCC, 0x54, 0xAC, 0xF9,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ // plaintext requires padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEA, 0x91, 0x68, 0xFE, 0x02, 0xFE, 0x57, 0x6F,
+ 0x60, 0x17, 0x05, 0xD5, 0x94, 0xA2, 0xF8, 0xE2,
+ 0x60, 0x8E, 0xC3, 0xB8, 0x09, 0x84, 0xCF, 0x3B,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEA, 0x91, 0x68, 0xFE, 0x02, 0xFE, 0x57, 0x6F,
+ 0x60, 0x17, 0x05, 0xD5, 0x94, 0xA2, 0xF8, 0xE2,
+ 0xE7, 0xA4, 0x10, 0xF1, 0x7B, 0xFF, 0x32, 0x4A,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEA, 0x91, 0x68, 0xFE, 0x02, 0xFE, 0x57, 0x6F,
+ 0x60, 0x17, 0x05, 0xD5, 0x94, 0xA2, 0xF8, 0xE2,
+ 0x92, 0x9A, 0x36, 0xFE, 0xA4, 0xB3, 0xEC, 0xA0,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xEA, 0x91, 0x68, 0xFE, 0x02, 0xFE, 0x57, 0x6F,
+ 0x60, 0x17, 0x05, 0xD5, 0x94, 0xA2, 0xF8, 0xE2,
+ 0xDB, 0x86, 0xA4, 0xAB, 0xDE, 0x05, 0xE4, 0xE7,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ new byte[]
+ {
+ 0xED, 0xD9, 0xE3, 0xFC, 0xC6, 0x55, 0xDC, 0x32,
+ },
+
+ PaddingMode.PKCS7,
+ };
+ }
+ }
+ }
+}
namespace System.Security.Cryptography.Encryption.Des.Tests
{
[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
- public static class DesCipherTests
+ public static partial class DesCipherTests
{
// These are the expected output of many decryptions. Changing these values requires re-generating test input.
private static readonly string s_multiBlockString = new ASCIIEncoding().GetBytes(
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.RC2.Tests
+{
+ using RC2 = System.Security.Cryptography.RC2;
+
+ public partial class RC2CipherTests
+ {
+ private static byte[] s_rc2OneShotKey = new byte[]
+ {
+ 0x83, 0x2F, 0x81, 0x1B, 0x61, 0x02, 0xCC, 0x8F,
+ 0x2F, 0x78, 0x10, 0x68, 0x06, 0xA6, 0x35, 0x50,
+ };
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EcbRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ byte[] encrypted = rc2.EncryptEcb(plaintext, padding);
+ byte[] decrypted = rc2.DecryptEcb(encrypted, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted[..plaintext.Length]);
+ AssertFilledWith(0, plaintext.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+
+ decrypted = rc2.DecryptEcb(ciphertext, padding);
+ encrypted = rc2.EncryptEcb(decrypted, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (plaintext.Length == 0)
+ {
+ // Can't have a ciphertext length shorter than zero.
+ return;
+ }
+
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[plaintext.Length - 1];
+
+ bool result = rc2.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (ciphertext.Length == 0)
+ {
+ // Can't have a too small buffer for zero.
+ return;
+ }
+
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[ciphertext.Length - 1];
+
+ bool result = rc2.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ Span<byte> destinationBuffer = new byte[expectedPlaintextSize];
+
+ bool result = rc2.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ int expectedCiphertextSize = rc2.GetCiphertextLengthEcb(plaintext.Length, padding);
+ Span<byte> destinationBuffer = new byte[expectedCiphertextSize];
+
+ bool result = rc2.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(expectedCiphertextSize, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ // Padding is random so we can't validate the last block.
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+
+ Span<byte> largeBuffer = new byte[expectedPlaintextSize + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, expectedPlaintextSize);
+ largeBuffer.Fill(0xCC);
+
+ bool result = rc2.TryDecryptEcb(
+ ciphertext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+
+ Span<byte> excess = largeBuffer.Slice(destinationBuffer.Length);
+ AssertFilledWith(0xCC, excess);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ Span<byte> largeBuffer = new byte[ciphertext.Length + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, ciphertext.Length);
+ largeBuffer.Fill(0xCC);
+
+ bool result = rc2.TryEncryptEcb(
+ plaintext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+
+ AssertFilledWith(0xCC, largeBuffer.Slice(ciphertext.Length));
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ int destinationSize = Math.Max(expectedPlaintextSize, ciphertext.Length) + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize);
+ Span<byte> ciphertextBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ ciphertext.AsSpan().CopyTo(ciphertextBuffer);
+
+ bool result = rc2.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(ciphertextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+
+ int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ Span<byte> plaintextBuffer = buffer.Slice(plaintextOffset, plaintext.Length);
+ plaintext.AsSpan().CopyTo(plaintextBuffer);
+
+ bool result = rc2.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+ byte[] decrypted = rc2.DecryptEcb(ciphertext.AsSpan(), padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+ byte[] encrypted = rc2.EncryptEcb(plaintext.AsSpan(), padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+ byte[] decrypted = rc2.DecryptEcb(ciphertext, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (RC2 rc2 = RC2Factory.Create())
+ {
+ rc2.Key = s_rc2OneShotKey;
+ byte[] encrypted = rc2.EncryptEcb(plaintext, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = rc2.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ private static void AssertFilledWith(byte value, ReadOnlySpan<byte> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(value, span[i]);
+ }
+ }
+
+ public static IEnumerable<object[]> EcbTestCases
+ {
+ get
+ {
+ // plaintext that is block aligned
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x8A, 0x65, 0xEB, 0x8C, 0x62, 0x14, 0xDD, 0x83,
+ 0x71, 0x0F, 0x1B, 0x21, 0xAD, 0x5F, 0xCD, 0xC1,
+ 0x9D, 0x70, 0x70, 0x58, 0x47, 0x5A, 0xD0, 0xC8,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x8A, 0x65, 0xEB, 0x8C, 0x62, 0x14, 0xDD, 0x83,
+ 0x71, 0x0F, 0x1B, 0x21, 0xAD, 0x5F, 0xCD, 0xC1,
+ },
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x8A, 0x65, 0xEB, 0x8C, 0x62, 0x14, 0xDD, 0x83,
+ 0x71, 0x0F, 0x1B, 0x21, 0xAD, 0x5F, 0xCD, 0xC1,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x8A, 0x65, 0xEB, 0x8C, 0x62, 0x14, 0xDD, 0x83,
+ 0x71, 0x0F, 0x1B, 0x21, 0xAD, 0x5F, 0xCD, 0xC1,
+ 0x72, 0x8A, 0x57, 0x94, 0x2D, 0x79, 0xBD, 0xAA,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x8A, 0x65, 0xEB, 0x8C, 0x62, 0x14, 0xDD, 0x83,
+ 0x71, 0x0F, 0x1B, 0x21, 0xAD, 0x5F, 0xCD, 0xC1,
+ 0xEB, 0x5E, 0x2E, 0xB9, 0x1A, 0x1E, 0x1B, 0xE4,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ // plaintext requires padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x14, 0x76, 0x59, 0x63, 0xBD, 0x9E, 0xF3, 0x2E,
+ 0xF6, 0xA1, 0x05, 0x03, 0x44, 0x59, 0xF5, 0x88,
+ 0xF2, 0x94, 0x11, 0xA3, 0xE8, 0xAD, 0xA7, 0xE6,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x14, 0x76, 0x59, 0x63, 0xBD, 0x9E, 0xF3, 0x2E,
+ 0xF6, 0xA1, 0x05, 0x03, 0x44, 0x59, 0xF5, 0x88,
+ 0xE3, 0xB2, 0x3D, 0xAA, 0x91, 0x6A, 0xD0, 0x06,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x14, 0x76, 0x59, 0x63, 0xBD, 0x9E, 0xF3, 0x2E,
+ 0xF6, 0xA1, 0x05, 0x03, 0x44, 0x59, 0xF5, 0x88,
+ 0x17, 0x97, 0x3A, 0x77, 0x69, 0x5E, 0x79, 0xE9,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x14, 0x76, 0x59, 0x63, 0xBD, 0x9E, 0xF3, 0x2E,
+ 0xF6, 0xA1, 0x05, 0x03, 0x44, 0x59, 0xF5, 0x88,
+ 0x22, 0xC0, 0x50, 0x52, 0x56, 0x5A, 0x15, 0xFD,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ new byte[]
+ {
+ 0x9D, 0x70, 0x70, 0x58, 0x47, 0x5A, 0xD0, 0xC8,
+ },
+
+ PaddingMode.PKCS7,
+ };
+ }
+ }
+ }
+}
[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
[ConditionalClass(typeof(RC2Factory), nameof(RC2Factory.IsSupported))]
- public static class RC2CipherTests
+ public static partial class RC2CipherTests
{
// These are the expected output of many decryptions. Changing these values requires re-generating test input.
private static readonly string s_multiBlockString = new ASCIIEncoding().GetBytes(
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Security.Cryptography;
+using Test.Cryptography;
+using Xunit;
+
+namespace System.Security.Cryptography.Encryption.TripleDes.Tests
+{
+ public partial class TripleDESCipherTests
+ {
+ private static byte[] s_tdes192OneShotKey = new byte[]
+ {
+ 0x00, 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08,
+ 0x0A, 0x0B, 0x0C, 0x0D, 0x0F, 0x10, 0x11, 0x12,
+ 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xA0,
+ };
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EcbRoundtrip(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ // Even though we have set the instance to use CFB, the Ecb one shots should
+ // always be done in ECB.
+ tdes.FeedbackSize = 8;
+ tdes.Mode = CipherMode.CFB;
+ tdes.Padding = padding == PaddingMode.None ? PaddingMode.PKCS7 : PaddingMode.None;
+
+ byte[] encrypted = tdes.EncryptEcb(plaintext, padding);
+ byte[] decrypted = tdes.DecryptEcb(encrypted, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted[..plaintext.Length]);
+ AssertFilledWith(0, plaintext.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+
+ decrypted = tdes.DecryptEcb(ciphertext, padding);
+ encrypted = tdes.EncryptEcb(decrypted, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (plaintext.Length == 0)
+ {
+ // Can't have a ciphertext length shorter than zero.
+ return;
+ }
+
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[plaintext.Length - 1];
+
+ bool result = tdes.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationTooSmall(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ if (ciphertext.Length == 0)
+ {
+ // Can't have a too small buffer for zero.
+ return;
+ }
+
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ Span<byte> destinationBuffer = new byte[ciphertext.Length - 1];
+
+ bool result = tdes.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.False(result, "TryDecryptEcb");
+ Assert.Equal(0, bytesWritten);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ Span<byte> destinationBuffer = new byte[expectedPlaintextSize];
+
+ bool result = tdes.TryDecryptEcb(ciphertext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationJustRight(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ int expectedCiphertextSize = tdes.GetCiphertextLengthEcb(plaintext.Length, padding);
+ Span<byte> destinationBuffer = new byte[expectedCiphertextSize];
+
+ bool result = tdes.TryEncryptEcb(plaintext, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(expectedCiphertextSize, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ // Padding is random so we can't validate the last block.
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+
+ Span<byte> largeBuffer = new byte[expectedPlaintextSize + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, expectedPlaintextSize);
+ largeBuffer.Fill(0xCC);
+
+ bool result = tdes.TryDecryptEcb(
+ ciphertext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ }
+
+ Span<byte> excess = largeBuffer.Slice(destinationBuffer.Length);
+ AssertFilledWith(0xCC, excess);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_DestinationLarger(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ Span<byte> largeBuffer = new byte[ciphertext.Length + 10];
+ Span<byte> destinationBuffer = largeBuffer.Slice(0, ciphertext.Length);
+ largeBuffer.Fill(0xCC);
+
+ bool result = tdes.TryEncryptEcb(
+ plaintext,
+ destinationBuffer,
+ padding,
+ out int bytesWritten);
+
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ }
+
+ AssertFilledWith(0xCC, largeBuffer.Slice(ciphertext.Length));
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryDecryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ int expectedPlaintextSize = padding == PaddingMode.Zeros ? ciphertext.Length : plaintext.Length;
+ int destinationSize = Math.Max(expectedPlaintextSize, ciphertext.Length) + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize);
+ Span<byte> ciphertextBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ ciphertext.AsSpan().CopyTo(ciphertextBuffer);
+
+ bool result = tdes.TryDecryptEcb(ciphertextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryDecryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, destinationBuffer.Slice(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, destinationBuffer.Slice(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(ciphertextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void TryEncryptEcb_Overlaps(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ (int plaintextOffset, int ciphertextOffset)[] offsets =
+ {
+ (0, 0), (8, 0), (0, 8), (8, 8),
+ };
+
+ foreach ((int plaintextOffset, int ciphertextOffset) in offsets)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+
+ int destinationSize = ciphertext.Length + Math.Max(plaintextOffset, ciphertextOffset);
+ Span<byte> buffer = new byte[destinationSize];
+ Span<byte> destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length);
+ Span<byte> plaintextBuffer = buffer.Slice(plaintextOffset, plaintext.Length);
+ plaintext.AsSpan().CopyTo(plaintextBuffer);
+
+ bool result = tdes.TryEncryptEcb(plaintextBuffer, destinationBuffer, padding, out int bytesWritten);
+ Assert.True(result, "TryEncryptEcb");
+ Assert.Equal(destinationBuffer.Length, bytesWritten);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], destinationBuffer[..^blockSizeBytes].ToArray());
+ }
+ else
+ {
+ Assert.Equal(ciphertext, destinationBuffer.ToArray());
+ Assert.True(destinationBuffer.Overlaps(plaintextBuffer) || plaintext.Length == 0 || ciphertext.Length == 0);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+ byte[] decrypted = tdes.DecryptEcb(ciphertext.AsSpan(), padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Span(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+ byte[] encrypted = tdes.EncryptEcb(plaintext.AsSpan(), padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void DecryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+ byte[] decrypted = tdes.DecryptEcb(ciphertext, padding);
+
+ if (padding == PaddingMode.Zeros)
+ {
+ Assert.Equal(plaintext, decrypted.AsSpan(0, plaintext.Length).ToArray());
+ AssertFilledWith(0, decrypted.AsSpan(plaintext.Length));
+ }
+ else
+ {
+ Assert.Equal(plaintext, decrypted);
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(EcbTestCases))]
+ public static void EncryptEcb_Array(byte[] plaintext, byte[] ciphertext, PaddingMode padding)
+ {
+ using (TripleDES tdes = TripleDESFactory.Create())
+ {
+ tdes.Key = s_tdes192OneShotKey;
+ byte[] encrypted = tdes.EncryptEcb(plaintext, padding);
+
+ if (padding == PaddingMode.ISO10126)
+ {
+ int blockSizeBytes = tdes.BlockSize / 8;
+ Assert.Equal(ciphertext[..^blockSizeBytes], encrypted[..^blockSizeBytes]);
+ }
+ else
+ {
+ Assert.Equal(ciphertext, encrypted);
+ }
+ }
+ }
+
+ private static void AssertFilledWith(byte value, ReadOnlySpan<byte> span)
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Assert.Equal(value, span[i]);
+ }
+ }
+
+ public static IEnumerable<object[]> EcbTestCases
+ {
+ get
+ {
+ // plaintext requires no padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x2C, 0xE7, 0xF1, 0x5C, 0x7B, 0xA8, 0x40, 0x0C,
+ 0x1A, 0x09, 0xDC, 0x63, 0x43, 0xC9, 0x1A, 0x63,
+ 0x65, 0xE4, 0x9C, 0xD3, 0xE6, 0xBE, 0xB8, 0x40,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x2C, 0xE7, 0xF1, 0x5C, 0x7B, 0xA8, 0x40, 0x0C,
+ 0x1A, 0x09, 0xDC, 0x63, 0x43, 0xC9, 0x1A, 0x63,
+ },
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x2C, 0xE7, 0xF1, 0x5C, 0x7B, 0xA8, 0x40, 0x0C,
+ 0x1A, 0x09, 0xDC, 0x63, 0x43, 0xC9, 0x1A, 0x63,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x2C, 0xE7, 0xF1, 0x5C, 0x7B, 0xA8, 0x40, 0x0C,
+ 0x1A, 0x09, 0xDC, 0x63, 0x43, 0xC9, 0x1A, 0x63,
+ 0x34, 0xE6, 0x86, 0x6D, 0x94, 0x2E, 0x98, 0x0F,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8, 0x89,
+ 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83, 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0x2C, 0xE7, 0xF1, 0x5C, 0x7B, 0xA8, 0x40, 0x0C,
+ 0x1A, 0x09, 0xDC, 0x63, 0x43, 0xC9, 0x1A, 0x63,
+ 0x5E, 0xEE, 0x73, 0xBB, 0x94, 0xED, 0x29, 0x7A,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ // plaintext requires padding
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD5, 0xD5, 0x80, 0x3F, 0xC3, 0x7E, 0x4A, 0xE4,
+ 0xF2, 0x93, 0x9B, 0xC3, 0xDC, 0x4F, 0xA0, 0x23,
+ 0xB1, 0x3D, 0x05, 0x93, 0x98, 0xE6, 0x2C, 0xDF,
+ },
+
+ PaddingMode.PKCS7,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD5, 0xD5, 0x80, 0x3F, 0xC3, 0x7E, 0x4A, 0xE4,
+ 0xF2, 0x93, 0x9B, 0xC3, 0xDC, 0x4F, 0xA0, 0x23,
+ 0xC9, 0x52, 0x8F, 0xC1, 0x30, 0xC0, 0x7C, 0x63,
+ },
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD5, 0xD5, 0x80, 0x3F, 0xC3, 0x7E, 0x4A, 0xE4,
+ 0xF2, 0x93, 0x9B, 0xC3, 0xDC, 0x4F, 0xA0, 0x23,
+ 0x6A, 0x97, 0x38, 0x85, 0x3B, 0x48, 0x81, 0x5E,
+ },
+
+ PaddingMode.ANSIX923,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ new byte[]
+ {
+ 0x99, 0x50, 0x68, 0x12, 0xA4, 0x5F, 0x08, 0xC8,
+ 0x89, 0xB9, 0x7F, 0x59, 0x80, 0x03, 0x8B, 0x83,
+ 0x59,
+ },
+
+ // ciphertext
+ new byte[]
+ {
+ 0xD5, 0xD5, 0x80, 0x3F, 0xC3, 0x7E, 0x4A, 0xE4,
+ 0xF2, 0x93, 0x9B, 0xC3, 0xDC, 0x4F, 0xA0, 0x23,
+ 0x33, 0x58, 0x09, 0x2C, 0xD8, 0xB5, 0x36, 0xAD,
+ },
+
+ PaddingMode.ISO10126,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.Zeros,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ Array.Empty<byte>(),
+
+ PaddingMode.None,
+ };
+
+ yield return new object[]
+ {
+ // plaintext
+ Array.Empty<byte>(),
+
+ // ciphertext
+ new byte[]
+ {
+ 0x65, 0xE4, 0x9C, 0xD3, 0xE6, 0xBE, 0xB8, 0x40,
+ },
+
+ PaddingMode.PKCS7,
+ };
+ }
+ }
+ }
+}
namespace System.Security.Cryptography.Encryption.TripleDes.Tests
{
[SkipOnPlatform(TestPlatforms.Browser, "Not supported on Browser")]
- public static class TripleDESCipherTests
+ public static partial class TripleDESCipherTests
{
[Fact]
public static void TripleDESDefaults()
{
internal sealed partial class AesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class AesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class AesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
base.Dispose(disposing);
}
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ paddingSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ encrypting: false);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ paddingSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ encrypting: true);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
+ }
+
private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting)
{
// note: rbgIV is guaranteed to be cloned before this method, so no need to clone it again
ValidateCFBFeedbackSize(FeedbackSize);
}
- return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, this.GetPaddingSize(), FeedbackSize / BitsPerByte, encrypting);
+ return CreateTransformCore(
+ Mode,
+ Padding,
+ rgbKey,
+ rgbIV,
+ BlockSize / BitsPerByte,
+ this.GetPaddingSize(Mode, FeedbackSize),
+ FeedbackSize / BitsPerByte,
+ encrypting);
}
private static void ValidateCFBFeedbackSize(int feedback)
Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
Debug.Assert(input.Length <= output.Length);
- int written = ProcessFinalBlock(input, output);
+ int written = 0;
+
+ if (input.Overlaps(output, out int offset) && offset != 0)
+ {
+ byte[] rented = CryptoPool.Rent(output.Length);
+
+ try
+ {
+ written = ProcessFinalBlock(input, rented);
+ rented.AsSpan(0, written).CopyTo(output);
+ }
+ finally
+ {
+ CryptoPool.Return(rented, clearSize: written);
+ }
+ }
+ else
+ {
+ written = ProcessFinalBlock(input, output);
+ }
+
Reset();
return written;
}
{
internal sealed partial class DesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class DesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class DesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class DesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
ValidateCFBFeedbackSize(FeedbackSize);
}
- return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, FeedbackSize / BitsPerByte, this.GetPaddingSize(), encrypting);
+ return CreateTransformCore(
+ Mode,
+ Padding,
+ rgbKey,
+ rgbIV,
+ BlockSize / BitsPerByte,
+ FeedbackSize / BitsPerByte,
+ this.GetPaddingSize(Mode, FeedbackSize),
+ encrypting);
+ }
+
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ paddingSize: BlockSize / BitsPerByte,
+ encrypting: false);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ paddingSize: BlockSize / BitsPerByte,
+ encrypting: true);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
}
private static void ValidateCFBFeedbackSize(int feedback)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA5350", Justification = "We are providing the implementation for RC2, not consuming it.")]
internal sealed partial class RC2Implementation : RC2
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class RC2Implementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class RC2Implementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class RC2Implementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
if (rgbKey == null)
throw new ArgumentNullException(nameof(rgbKey));
- long keySize = rgbKey.Length * (long)BitsPerByte;
- if (keySize > int.MaxValue || !((int)keySize).IsLegalSize(LegalKeySizes))
+ if (!ValidKeySize(rgbKey.Length, out int keySize))
throw new ArgumentException(SR.Cryptography_InvalidKeySize, nameof(rgbKey));
if (rgbIV != null)
ValidateCFBFeedbackSize(FeedbackSize);
}
- int effectiveKeySize = EffectiveKeySizeValue == 0 ? (int)keySize : EffectiveKeySize;
+ int effectiveKeySize = EffectiveKeySizeValue == 0 ? keySize : EffectiveKeySize;
return CreateTransformCore(Mode, Padding, rgbKey, effectiveKeySize, rgbIV, BlockSize / BitsPerByte, FeedbackSize / BitsPerByte, GetPaddingSize(), encrypting);
}
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ if (!ValidKeySize(Key.Length, out int keySize))
+ throw new InvalidOperationException(SR.Cryptography_InvalidKeySize);
+
+ int effectiveKeySize = EffectiveKeySizeValue == 0 ? keySize : EffectiveKeySize;
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ effectiveKeyLength: effectiveKeySize,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ paddingSize: BlockSize / BitsPerByte,
+ encrypting: false);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ if (!ValidKeySize(Key.Length, out int keySize))
+ throw new InvalidOperationException(SR.Cryptography_InvalidKeySize);
+
+ int effectiveKeySize = EffectiveKeySizeValue == 0 ? keySize : EffectiveKeySize;
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ effectiveKeyLength: effectiveKeySize,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ paddingSize: BlockSize / BitsPerByte,
+ encrypting: true);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
+ }
+
private static void ValidateCFBFeedbackSize(int feedback)
{
// CFB not supported at all
{
return BlockSize / BitsPerByte;
}
+
+ private bool ValidKeySize(int keySizeBytes, out int keySizeBits)
+ {
+ if (keySizeBytes > (int.MaxValue / BitsPerByte))
+ {
+ keySizeBits = 0;
+ return false;
+ }
+
+ keySizeBits = keySizeBytes << 3;
+ return keySizeBits.IsLegalSize(LegalKeySizes);
+ }
}
}
{
internal sealed partial class TripleDesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class TripleDesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
{
internal sealed partial class TripleDesImplementation
{
- private static ICryptoTransform CreateTransformCore(
+ private static UniversalCryptoTransform CreateTransformCore(
CipherMode cipherMode,
PaddingMode paddingMode,
byte[] key,
ValidateCFBFeedbackSize(FeedbackSize);
}
- return CreateTransformCore(Mode, Padding, rgbKey, rgbIV, BlockSize / BitsPerByte, this.GetPaddingSize(), FeedbackSize / BitsPerByte, encrypting);
+ return CreateTransformCore(
+ Mode,
+ Padding,
+ rgbKey,
+ rgbIV,
+ BlockSize / BitsPerByte,
+ this.GetPaddingSize(Mode, FeedbackSize),
+ FeedbackSize / BitsPerByte,
+ encrypting);
+ }
+
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ paddingSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ encrypting: false);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = CreateTransformCore(
+ CipherMode.ECB,
+ paddingMode,
+ Key,
+ iv: null,
+ blockSize: BlockSize / BitsPerByte,
+ paddingSize: BlockSize / BitsPerByte,
+ 0, /*feedback size */
+ encrypting: true);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
}
private static void ValidateCFBFeedbackSize(int feedback)
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.Data.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.OneShot.cs"
+ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.OneShot.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesContractTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\AesContractTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCornerTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RSA\TestData.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.OneShot.cs"
+ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.OneShot.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESContractTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESContractTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESFactory.cs"
Link="CommonTest\System\Security\Cryptography\AsymmetricSignatureFormatter.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.OneShot.cs"
+ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESCipherTests.OneShot.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESContractTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\DES\DESContractTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\DES\DESFactory.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDsa\ECDsaXml.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.OneShot.cs"
+ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2CipherTests.OneShot.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2ContractTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\RC2\RC2ContractTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\RC2\RC2Factory.cs"
//
// The delegate must instantiate a new CngKey, based on a new underlying NCryptKeyHandle, each time is called.
//
- public BasicSymmetricCipherNCrypt(Func<CngKey> cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[] iv, bool encrypting, int feedbackSizeInBytes, int paddingSize)
+ public BasicSymmetricCipherNCrypt(Func<CngKey> cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[]? iv, bool encrypting, int feedbackSizeInBytes, int paddingSize)
: base(iv, blockSizeInBytes, paddingSize)
{
_encrypting = encrypting;
public ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV)
{
- return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true);
+ return CreateCryptoTransform(rgbKey, rgbIV, encrypting: true, _outer.Padding, _outer.Mode);
}
public ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV)
{
- return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false);
+ return CreateCryptoTransform(rgbKey, rgbIV, encrypting: false, _outer.Padding, _outer.Mode);
}
private ICryptoTransform CreateCryptoTransform(bool encrypting)
{
if (KeyInPlainText)
{
- return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting);
+ return CreateCryptoTransform(_outer.BaseKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode);
}
- return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting);
+ return CreatePersistedCryptoTransformCore(ProduceCngKey, _outer.IV, encrypting, _outer.Padding, _outer.Mode);
}
- private ICryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting)
+ public UniversalCryptoTransform CreateCryptoTransform(byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode)
+ {
+ if (KeyInPlainText)
+ {
+ return CreateCryptoTransform(_outer.BaseKey, iv, encrypting, padding, mode);
+ }
+
+ return CreatePersistedCryptoTransformCore(ProduceCngKey, iv, encrypting, padding, mode);
+ }
+
+ private UniversalCryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting, PaddingMode padding, CipherMode mode)
{
if (rgbKey == null)
throw new ArgumentNullException(nameof(rgbKey));
// CloneByteArray is null-preserving. So even when GetCipherIv returns null the iv variable
// is correct, and detached from the input parameter.
- byte[]? iv = _outer.Mode.GetCipherIv(rgbIV).CloneByteArray();
+ byte[]? iv = mode.GetCipherIv(rgbIV).CloneByteArray();
key = _outer.PreprocessKey(key);
- return CreateEphemeralCryptoTransformCore(key, iv, encrypting);
+ return CreateEphemeralCryptoTransformCore(key, iv, encrypting, padding, mode);
}
- private ICryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting)
+ private UniversalCryptoTransform CreateEphemeralCryptoTransformCore(byte[] key, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode)
{
int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize();
- SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle();
+ SafeAlgorithmHandle algorithmModeHandle = _outer.GetEphemeralModeHandle(mode);
BasicSymmetricCipher cipher = new BasicSymmetricCipherBCrypt(
algorithmModeHandle,
- _outer.Mode,
+ mode,
blockSizeInBytes,
- _outer.GetPaddingSize(),
+ _outer.GetPaddingSize(mode, _outer.FeedbackSize),
key,
- false,
+ ownsParentHandle: false,
iv,
encrypting);
- return UniversalCryptoTransform.Create(_outer.Padding, cipher, encrypting);
+ return UniversalCryptoTransform.Create(padding, cipher, encrypting);
}
- private ICryptoTransform CreatePersistedCryptoTransformCore(Func<CngKey> cngKeyFactory, byte[] iv, bool encrypting)
+ private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func<CngKey> cngKeyFactory, byte[]? iv, bool encrypting, PaddingMode padding, CipherMode mode)
{
// note: iv is guaranteed to be cloned before this method, so no need to clone it again
int blockSizeInBytes = _outer.BlockSize.BitSizeToByteSize();
int feedbackSizeInBytes = _outer.FeedbackSize;
- BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(cngKeyFactory, _outer.Mode, blockSizeInBytes, iv, encrypting, feedbackSizeInBytes, _outer.GetPaddingSize());
- return UniversalCryptoTransform.Create(_outer.Padding, cipher, encrypting);
+ BasicSymmetricCipher cipher = new BasicSymmetricCipherNCrypt(
+ cngKeyFactory,
+ mode,
+ blockSizeInBytes,
+ iv,
+ encrypting,
+ feedbackSizeInBytes,
+ _outer.GetPaddingSize(mode, _outer.FeedbackSize));
+ return UniversalCryptoTransform.Create(padding, cipher, encrypting);
}
private CngKey ProduceCngKey()
// Other members.
bool IsWeakKey(byte[] key);
- SafeAlgorithmHandle GetEphemeralModeHandle();
+ SafeAlgorithmHandle GetEphemeralModeHandle(CipherMode mode);
string GetNCryptAlgorithmIdentifier();
byte[] PreprocessKey(byte[] key);
- int GetPaddingSize();
+ int GetPaddingSize(CipherMode mode, int feedbackSizeBits);
}
}
_core.GenerateIV();
}
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = _core.CreateCryptoTransform(
+ iv: null,
+ encrypting: false,
+ paddingMode,
+ CipherMode.ECB);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = _core.CreateCryptoTransform(
+ iv: null,
+ encrypting: true,
+ paddingMode,
+ CipherMode.ECB);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
+ }
+
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
return false;
}
- int ICngSymmetricAlgorithm.GetPaddingSize()
+ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits)
{
- return this.GetPaddingSize();
+ return this.GetPaddingSize(mode, feedbackSizeBits);
}
- SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle()
+ SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode)
{
try
{
- return AesBCryptModes.GetSharedHandle(Mode, FeedbackSize / 8);
+ return AesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8);
}
catch (NotSupportedException)
{
_core.GenerateIV();
}
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = _core.CreateCryptoTransform(
+ iv: null,
+ encrypting: false,
+ paddingMode,
+ CipherMode.ECB);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(ciphertext, destination, out bytesWritten);
+ }
+ }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ UniversalCryptoTransform transform = _core.CreateCryptoTransform(
+ iv: null,
+ encrypting: true,
+ paddingMode,
+ CipherMode.ECB);
+
+ using (transform)
+ {
+ return transform.TransformOneShot(plaintext, destination, out bytesWritten);
+ }
+ }
+
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
return TripleDES.IsWeakKey(key);
}
- int ICngSymmetricAlgorithm.GetPaddingSize()
+ int ICngSymmetricAlgorithm.GetPaddingSize(CipherMode mode, int feedbackSizeBits)
{
- return this.GetPaddingSize();
+ return this.GetPaddingSize(mode, feedbackSizeBits);
}
- SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle()
+ SafeAlgorithmHandle ICngSymmetricAlgorithm.GetEphemeralModeHandle(CipherMode mode)
{
- return TripleDesBCryptModes.GetSharedHandle(Mode, FeedbackSize / 8);
+ return TripleDesBCryptModes.GetSharedHandle(mode, FeedbackSize / 8);
}
string ICngSymmetricAlgorithm.GetNCryptAlgorithmIdentifier()
[ConditionalTheory(nameof(SupportsPersistedSymmetricKeys))]
// AES128-ECB-NoPadding 2 blocks.
[InlineData(128, 2 * BlockSizeBytes, CipherMode.ECB, PaddingMode.None)]
+ // AES128-ECB-Zeros 2 blocks.
+ [InlineData(128, 2 * BlockSizeBytes, CipherMode.ECB, PaddingMode.Zeros)]
+ // AES128-ECB-Zeros 1.5 blocks.
+ [InlineData(128, BlockSizeBytes + BlockSizeBytes / 2, CipherMode.ECB, PaddingMode.Zeros)]
// AES128-CBC-NoPadding at 2 blocks
[InlineData(128, 2 * BlockSizeBytes, CipherMode.CBC, PaddingMode.None)]
// AES256-CBC-Zeros at 1.5 blocks
Assert.Equal(expectedBytes, persistedDecrypted);
}
+
+ byte[] oneShotPersistedEncrypted = null;
+ byte[] oneShotEphemeralEncrypted = null;
+ byte[] oneShotPersistedDecrypted = null;
+
+ if (cipherMode == CipherMode.ECB)
+ {
+ oneShotPersistedEncrypted = persisted.EncryptEcb(plainBytes, paddingMode);
+ oneShotEphemeralEncrypted = ephemeral.EncryptEcb(plainBytes, paddingMode);
+ oneShotPersistedDecrypted = persisted.DecryptEcb(oneShotEphemeralEncrypted, paddingMode);
+ }
+
+ if (oneShotPersistedEncrypted is not null)
+ {
+ Assert.Equal(oneShotEphemeralEncrypted, oneShotPersistedEncrypted);
+
+ if (paddingMode == PaddingMode.Zeros)
+ {
+ byte[] plainPadded = new byte[oneShotPersistedDecrypted.Length];
+ plainBytes.AsSpan().CopyTo(plainPadded);
+ Assert.Equal(plainPadded, oneShotPersistedDecrypted);
+ }
+ else
+ {
+ Assert.Equal(plainBytes, oneShotPersistedDecrypted);
+ }
+ }
}
}
Link="CommonTest\AlgorithmImplementations\AES\AesContractTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.OneShot.cs"
+ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESCipherTests.OneShot.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesModeTests.cs"
Link="CommonTest\AlgorithmImplementations\AES\AesModeTests.cs" />
<Compile Condition="'$(TargetsWindows)' == 'true'" Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESReusabilityTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\TripleDES\TripleDESReusabilityTests.cs" />
<Compile Condition="'$(TargetsWindows)' == 'true'" Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.cs"
Link="CommonTest\AlgorithmImplementations\AES\AesCipherTests.cs" />
+ <Compile Condition="'$(TargetsWindows)' == 'true'" Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.OneShot.cs"
+ Link="CommonTest\AlgorithmImplementations\AES\AesCipherTests.OneShot" />
<Compile Include="ECDiffieHellmanCngTests.cs" />
<Compile Include="$(CommonTestPath)System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.cs"
Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\ECDiffieHellman\ECDiffieHellmanTests.cs" />
[ConditionalTheory(nameof(SupportsPersistedSymmetricKeys))]
// 3DES192-ECB-NoPadding 2 blocks.
[InlineData(2 * BlockSizeBytes, CipherMode.ECB, PaddingMode.None)]
+ // 3DES192-ECB-Zeros 2 blocks.
+ [InlineData(2 * BlockSizeBytes, CipherMode.ECB, PaddingMode.Zeros)]
+ // 3DES192-ECB-Zeros 1.5 blocks.
+ [InlineData(BlockSizeBytes + BlockSizeBytes / 2, CipherMode.ECB, PaddingMode.Zeros)]
// 3DES192-CBC-NoPadding at 2 blocks
[InlineData(2 * BlockSizeBytes, CipherMode.CBC, PaddingMode.None)]
// 3DES192-CBC-Zeros at 1.5 blocks
throw new CryptographicException(SR.Cryptography_InvalidIVSize);
}
- BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(CapiHelper.CALG_DES, Mode, BlockSize / BitsPerByte, rgbKey, 0, false, rgbIV, encrypting, FeedbackSize, this.GetPaddingSize());
+ BasicSymmetricCipher cipher = new BasicSymmetricCipherCsp(
+ CapiHelper.CALG_DES,
+ Mode,
+ BlockSize / BitsPerByte,
+ rgbKey,
+ effectiveKeyLength: 0,
+ addNoSaltFlag: false,
+ rgbIV,
+ encrypting,
+ FeedbackSize,
+ this.GetPaddingSize(Mode, FeedbackSize));
return UniversalCryptoTransform.Create(Padding, cipher, encrypting);
}
}
"TrySignDataCore",
"VerifyDataCore",
"VerifySignatureCore",
+ // CryptoServiceProviders will not get one-shot APIs as they are being deprecated
+ "TryEncryptEcbCore",
+ "TryDecryptEcbCore",
};
IEnumerable<MethodInfo> baseMethods = shimType.
public abstract System.Security.Cryptography.ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV);
public virtual System.Security.Cryptography.ICryptoTransform CreateEncryptor() { throw null; }
public abstract System.Security.Cryptography.ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV);
+ public byte[] DecryptEcb(byte[] ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
+ public byte[] DecryptEcb(System.ReadOnlySpan<byte> ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
+ public int DecryptEcb(System.ReadOnlySpan<byte> ciphertext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
public void Dispose() { }
protected virtual void Dispose(bool disposing) { }
+ public byte[] EncryptEcb(byte[] plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
+ public byte[] EncryptEcb(System.ReadOnlySpan<byte> plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
+ public int EncryptEcb(System.ReadOnlySpan<byte> plaintext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode) { throw null; }
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 TryDecryptEcb(System.ReadOnlySpan<byte> ciphertext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; }
+ protected virtual bool TryDecryptEcbCore(System.ReadOnlySpan<byte> ciphertext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; }
+ public bool TryEncryptEcb(System.ReadOnlySpan<byte> plaintext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; }
+ protected virtual bool TryEncryptEcbCore(System.ReadOnlySpan<byte> plaintext, System.Span<byte> destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; }
public bool ValidKeySize(int bitLength) { throw null; }
}
}
<data name="Arg_CryptographyException" xml:space="preserve">
<value>Error occurred during a cryptographic operation.</value>
</data>
+ <data name="Argument_DestinationTooShort" xml:space="preserve">
+ <value>Destination is too short.</value>
+ </data>
<data name="Argument_InvalidOffLen" xml:space="preserve">
<value>Offset and length were out of bounds for the array or count is greater than the number of elements from index to the end of the source collection.</value>
</data>
<data name="Cryptography_PlaintextTooLarge" xml:space="preserve">
<value>The specified plaintext size is too large.</value>
</data>
+ <data name="Cryptography_EncryptedIncorrectLength" xml:space="preserve">
+ <value>{0} unexpectedly produced a ciphertext with the incorrect length.</value>
+ </data>
<data name="NotSupported_SubclassOverride" xml:space="preserve">
<value>Method not supported. Derived class must override.</value>
</data>
}
}
+ /// <summary>
+ /// Decrypts data using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="ciphertext">The data to decrypt.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The decrypted plaintext data.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="ciphertext" /> is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The ciphertext could not be decrypted successfully.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
+ /// </remarks>
+ public byte[] DecryptEcb(byte[] ciphertext, PaddingMode paddingMode)
+ {
+ // Padding mode is validated by callee.
+ if (ciphertext is null)
+ throw new ArgumentNullException(nameof(ciphertext));
+
+ return DecryptEcb(new ReadOnlySpan<byte>(ciphertext), paddingMode);
+ }
+
+ /// <summary>
+ /// Decrypts data using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="ciphertext">The data to decrypt.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The decrypted plaintext data.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The ciphertext could not be decrypted successfully.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
+ /// </remarks>
+ public byte[] DecryptEcb(ReadOnlySpan<byte> ciphertext, PaddingMode paddingMode)
+ {
+ CheckPaddingMode(paddingMode);
+
+ // This could get returned directly to the caller if we there was no padding
+ // that needed to get removed, so don't rent from a pool.
+ byte[] decryptBuffer = GC.AllocateUninitializedArray<byte>(ciphertext.Length);
+
+ if (!TryDecryptEcbCore(ciphertext, decryptBuffer, paddingMode, out int written)
+ || (uint)written > decryptBuffer.Length)
+ {
+ // This means decrypting the ciphertext grew in to a larger plaintext or overflowed.
+ // A user-derived class could do this, but it is not expected in any of the
+ // implementations that we ship.
+
+ throw new CryptographicException(SR.Argument_DestinationTooShort);
+ }
+
+ // Array.Resize will no-op if the array does not need to be resized.
+ Array.Resize(ref decryptBuffer, written);
+ return decryptBuffer;
+ }
+
+ /// <summary>
+ /// Decrypts data into the specified buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="ciphertext">The data to decrypt.</param>
+ /// <param name="destination">The buffer to receive the plaintext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The total number of bytes written to <paramref name="destination" /></returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The ciphertext could not be decrypted successfully.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// The buffer in <paramref name="destination"/> is too small to hold the plaintext data.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
+ /// </remarks>
+ public int DecryptEcb(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode)
+ {
+ CheckPaddingMode(paddingMode);
+
+ if (!TryDecryptEcbCore(ciphertext, destination, paddingMode, out int written))
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
+
+ return written;
+ }
+
+ /// <summary>
+ /// Attempts to decrypt data into the specified buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="ciphertext">The data to decrypt.</param>
+ /// <param name="destination">The buffer to receive the plaintext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
+ /// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The ciphertext could not be decrypted successfully.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
+ /// </remarks>
+ public bool TryDecryptEcb(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ CheckPaddingMode(paddingMode);
+ return TryDecryptEcbCore(ciphertext, destination, paddingMode, out bytesWritten);
+ }
+
+ /// <summary>
+ /// Encrypts data using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="plaintext">The data to encrypt.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The encrypted ciphertext data.</returns>
+ /// <exception cref="ArgumentNullException">
+ /// <paramref name="plaintext" /> is <see langword="null" />.
+ /// </exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// <see cref="TryEncryptEcbCore" /> could not encrypt the plaintext.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
+ /// </remarks>
+ public byte[] EncryptEcb(byte[] plaintext, PaddingMode paddingMode)
+ {
+ // paddingMode is validated by callee
+ if (plaintext is null)
+ throw new ArgumentNullException(nameof(plaintext));
+
+ return EncryptEcb(new ReadOnlySpan<byte>(plaintext), paddingMode);
+ }
+
+ /// <summary>
+ /// Encrypts data using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="plaintext">The data to encrypt.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The encrypted ciphertext data.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The plaintext could not be encrypted successfully.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
+ /// </remarks>
+ public byte[] EncryptEcb(ReadOnlySpan<byte> plaintext, PaddingMode paddingMode)
+ {
+ CheckPaddingMode(paddingMode);
+
+ int ciphertextLength = GetCiphertextLengthEcb(plaintext.Length, paddingMode);
+
+ // We expect most if not all uses to encrypt to exactly the ciphertextLength
+ byte[] buffer = GC.AllocateUninitializedArray<byte>(ciphertextLength);
+
+ if (!TryEncryptEcbCore(plaintext, buffer, paddingMode, out int written) ||
+ written != ciphertextLength)
+ {
+ // This means a user-derived imiplementation added more padding than we expected or
+ // did something non-standard (encrypt to a partial block). This can't happen for
+ // multiple padding blocks since the buffer would have been too small in the first
+ // place. It doesn't make sense to try and support partial block encryption, likely
+ // something went very wrong. So throw.
+ throw new CryptographicException(SR.Format(SR.Cryptography_EncryptedIncorrectLength, nameof(TryEncryptEcbCore)));
+ }
+
+ return buffer;
+ }
+
+ /// <summary>
+ /// Encrypts data into the specified buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="plaintext">The data to encrypt.</param>
+ /// <param name="destination">The buffer to receive the ciphertext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The plaintext could not be encrypted successfully.
+ /// </exception>
+ /// <exception cref="ArgumentException">
+ /// The buffer in <paramref name="destination"/> is too small to hold the ciphertext data.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
+ /// </remarks>
+ public int EncryptEcb(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode)
+ {
+ CheckPaddingMode(paddingMode);
+
+ if (!TryEncryptEcbCore(plaintext, destination, paddingMode, out int written))
+ {
+ throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
+ }
+
+ return written;
+ }
+
+ /// <summary>
+ /// Attempts to encrypt data into the specified buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="plaintext">The data to encrypt.</param>
+ /// <param name="destination">The buffer to receive the ciphertext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
+ /// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="paddingMode" /> is not a valid padding mode.
+ /// </exception>
+ /// <exception cref="CryptographicException">
+ /// The plaintext could not be encrypted successfully.
+ /// </exception>
+ /// <remarks>
+ /// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
+ /// </remarks>
+ public bool TryEncryptEcb(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ CheckPaddingMode(paddingMode);
+ return TryEncryptEcbCore(plaintext, destination, paddingMode, out bytesWritten);
+ }
+
+ /// <summary>
+ /// When overridden in a derived class, attempts to encrypt data into the specified
+ /// buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="plaintext">The data to encrypt.</param>
+ /// <param name="destination">The buffer to receive the ciphertext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
+ /// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="NotSupportedException">
+ /// A derived class has not provided an implementation.
+ /// </exception>
+ /// <remarks>
+ /// <para>Derived classes must override this and provide an implementation.</para>
+ /// <para>
+ /// Implementations of this method must write precisely
+ /// <c>GetCiphertextLengthEcb(plaintext.Length, paddingMode)</c> bytes to <paramref name="destination"/>
+ /// and report that via <paramref name="bytesWritten"/>.
+ /// </para>
+ /// </remarks>
+ protected virtual bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ throw new NotSupportedException(SR.NotSupported_SubclassOverride);
+ }
+
+ /// <summary>
+ /// When overridden in a derived class, attempts to decrypt data
+ /// into the specified buffer, using ECB mode with the specified padding mode.
+ /// </summary>
+ /// <param name="ciphertext">The data to decrypt.</param>
+ /// <param name="destination">The buffer to receive the plaintext data.</param>
+ /// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
+ /// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
+ /// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
+ /// <exception cref="NotSupportedException">
+ /// A derived class has not provided an implementation.
+ /// </exception>
+ /// <remarks>
+ /// Derived classes must override this and provide an implementation.
+ /// </remarks>
+ protected virtual bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten)
+ {
+ throw new NotSupportedException(SR.NotSupported_SubclassOverride);
+ }
+
+ private static void CheckPaddingMode(PaddingMode paddingMode)
+ {
+ if (paddingMode < PaddingMode.None || paddingMode > PaddingMode.ISO10126)
+ throw new ArgumentOutOfRangeException(nameof(paddingMode), SR.Cryptography_InvalidPaddingMode);
+ }
+
protected CipherMode ModeValue;
protected PaddingMode PaddingValue;
protected byte[]? KeyValue;
alg.GetCiphertextLengthCfb(17, PaddingMode.None, feedbackSizeInBits: 128));
}
+ [Fact]
+ public static void EncryptEcb_NotSupportedInDerived()
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+
+ Assert.Throws<NotSupportedException>(() =>
+ alg.EncryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void DecryptEcb_NotSupportedInDerived()
+ {
+ AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 };
+
+ Assert.Throws<NotSupportedException>(() =>
+ alg.DecryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void EncryptEcb_EncryptProducesIncorrectlyPaddedValue()
+ {
+ static bool EncryptImpl(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = destination.Length + 1;
+ return true;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryEncryptEcbCoreImpl = EncryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.EncryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void DecryptEcb_DecryptBytesWrittenLies()
+ {
+ static bool DecryptImpl(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = destination.Length + 1;
+ return true;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryDecryptEcbCoreImpl = DecryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.DecryptEcb(new byte[128 / 8], PaddingMode.None));
+ }
+
+ [Fact]
+ public static void EncryptEcb_EncryptCoreFails()
+ {
+ static bool EncryptImpl(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryEncryptEcbCoreImpl = EncryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.EncryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void EncryptEcb_EncryptCoreOverflowWritten()
+ {
+ static bool EncryptImpl(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = -1;
+ return true;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryEncryptEcbCoreImpl = EncryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.EncryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void DecryptEcb_DecryptCoreFails()
+ {
+ static bool DecryptImpl(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = 0;
+ return false;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryDecryptEcbCoreImpl = DecryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.DecryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
+ [Fact]
+ public static void DecryptEcb_DecryptCoreOverflowWritten()
+ {
+ static bool DecryptImpl(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
+ {
+ bytesWritten = -1;
+ return true;
+ }
+
+ EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm
+ {
+ BlockSize = 128,
+ TryDecryptEcbCoreImpl = DecryptImpl,
+ };
+
+ Assert.Throws<CryptographicException>(() =>
+ alg.DecryptEcb(Array.Empty<byte>(), PaddingMode.None));
+ }
+
public static IEnumerable<object[]> CiphertextLengthTheories
{
get
public override void GenerateIV() => throw new NotImplementedException();
public override void GenerateKey() => throw new NotImplementedException();
}
+
+ private class EcbSymmetricAlgorithm : AnySizeAlgorithm
+ {
+ public delegate bool TryEncryptEcbCoreFunc(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten);
+
+ public delegate bool TryDecryptEcbCoreFunc(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten);
+
+ public TryEncryptEcbCoreFunc TryEncryptEcbCoreImpl { get; set; }
+ public TryDecryptEcbCoreFunc TryDecryptEcbCoreImpl { get; set; }
+
+ protected override bool TryEncryptEcbCore(
+ ReadOnlySpan<byte> plaintext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten) => TryEncryptEcbCoreImpl(plaintext, destination, paddingMode, out bytesWritten);
+
+ protected override bool TryDecryptEcbCore(
+ ReadOnlySpan<byte> ciphertext,
+ Span<byte> destination,
+ PaddingMode paddingMode,
+ out int bytesWritten) => TryDecryptEcbCoreImpl(ciphertext, destination, paddingMode, out bytesWritten);
+ }
}
}