diff --git a/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs b/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs index 59d984f5fec49..348f1a3a2c9ba 100644 --- a/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs +++ b/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs @@ -64,15 +64,27 @@ public override int Transform(ReadOnlySpan input, Span output) 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) @@ -84,6 +96,13 @@ public override int Transform(ReadOnlySpan input, Span output) } return numBytesWritten; + + int BCryptTransform(ReadOnlySpan input, Span output) + { + return _encrypting ? + Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output) : + Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output); + } } public override int TransformFinal(ReadOnlySpan input, Span output) diff --git a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs index c2998444ea105..2ee5ffc39c4f7 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -30,11 +30,11 @@ internal static partial class Helpers 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; diff --git a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs index 3f050ff1eda72..0578bb3d5d063 100644 --- a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs +++ b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs @@ -177,6 +177,62 @@ protected sealed override void Dispose(bool disposing) base.Dispose(disposing); } + public override unsafe bool TransformOneShot(ReadOnlySpan input, Span 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 buffer = rentedBuffer.AsSpan(0, input.Length); + Span 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) diff --git a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs index af88c7e6c2771..b9139789f9721 100644 --- a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs +++ b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs @@ -56,6 +56,33 @@ protected override byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int i return buffer; } + public override bool TransformOneShot(ReadOnlySpan input, Span 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 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); diff --git a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs index 23966df296939..a3a22165c6ceb 100644 --- a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs +++ b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoTransform.cs @@ -17,7 +17,7 @@ namespace Internal.Cryptography // internal abstract class UniversalCryptoTransform : ICryptoTransform { - public static ICryptoTransform Create( + public static UniversalCryptoTransform Create( PaddingMode paddingMode, BasicSymmetricCipher cipher, bool encrypting) @@ -108,6 +108,8 @@ public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int input return output; } + public abstract bool TransformOneShot(ReadOnlySpan input, Span output, out int bytesWritten); + protected virtual void Dispose(bool disposing) { if (disposing) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.OneShot.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.OneShot.cs new file mode 100644 index 0000000000000..fd82160f1a72a --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.OneShot.cs @@ -0,0 +1,638 @@ +// 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 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 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 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 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 largeBuffer = new byte[expectedPlaintextSize + 10]; + Span 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 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 largeBuffer = new byte[ciphertext.Length + 10]; + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize); + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length); + Span 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 span) + { + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(value, span[i]); + } + } + + public static IEnumerable 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(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x6D, 0xE5, 0xF6, 0x07, 0xAB, 0x7E, 0xB8, 0x20, + 0x2F, 0x39, 0x57, 0x70, 0x3B, 0x04, 0xE8, 0xB5, + }, + + PaddingMode.PKCS7, + }; + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs index e1f5da5f40f9e..2d7e23749d499 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/AES/AesCipherTests.cs @@ -1066,6 +1066,7 @@ private static void TestAesDecrypt( int? feedbackSize = default) { byte[] decryptedBytes; + byte[] oneShotDecryptedBytes = null; using (Aes aes = AesFactory.Create()) { @@ -1089,10 +1090,20 @@ private static void TestAesDecrypt( 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( @@ -1106,6 +1117,8 @@ private static void TestAesTransformDirectKey( { byte[] liveEncryptBytes; byte[] liveDecryptBytes; + byte[] liveOneShotDecryptBytes = null; + byte[] liveOneShotEncryptBytes = null; using (Aes aes = AesFactory.Create()) { @@ -1119,10 +1132,27 @@ private static void TestAesTransformDirectKey( 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) diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.OneShot.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.OneShot.cs new file mode 100644 index 0000000000000..6f5106816bcf0 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.OneShot.cs @@ -0,0 +1,624 @@ +// 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 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 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 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 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 largeBuffer = new byte[expectedPlaintextSize + 10]; + Span 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 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 largeBuffer = new byte[ciphertext.Length + 10]; + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize); + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length); + Span 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 span) + { + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(value, span[i]); + } + } + + public static IEnumerable 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(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0xED, 0xD9, 0xE3, 0xFC, 0xC6, 0x55, 0xDC, 0x32, + }, + + PaddingMode.PKCS7, + }; + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs index d007a41167e18..dae50482fc04e 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/DES/DESCipherTests.cs @@ -10,7 +10,7 @@ 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( diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.OneShot.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.OneShot.cs new file mode 100644 index 0000000000000..4a22728224d66 --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.OneShot.cs @@ -0,0 +1,627 @@ +// 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 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 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 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 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 largeBuffer = new byte[expectedPlaintextSize + 10]; + Span 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 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 largeBuffer = new byte[ciphertext.Length + 10]; + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize); + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length); + Span 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 span) + { + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(value, span[i]); + } + } + + public static IEnumerable 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(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x9D, 0x70, 0x70, 0x58, 0x47, 0x5A, 0xD0, 0xC8, + }, + + PaddingMode.PKCS7, + }; + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs index 7823cc8b1c58c..7b065d22b47ae 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/RC2/RC2CipherTests.cs @@ -12,7 +12,7 @@ namespace System.Security.Cryptography.Encryption.RC2.Tests [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( diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.OneShot.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.OneShot.cs new file mode 100644 index 0000000000000..3469f4c6fec1d --- /dev/null +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.OneShot.cs @@ -0,0 +1,633 @@ +// 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 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 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 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 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 largeBuffer = new byte[expectedPlaintextSize + 10]; + Span 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 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 largeBuffer = new byte[ciphertext.Length + 10]; + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(plaintextOffset, expectedPlaintextSize); + Span 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 buffer = new byte[destinationSize]; + Span destinationBuffer = buffer.Slice(ciphertextOffset, ciphertext.Length); + Span 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 span) + { + for (int i = 0; i < span.Length; i++) + { + Assert.Equal(value, span[i]); + } + } + + public static IEnumerable 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(), + + // ciphertext + Array.Empty(), + + PaddingMode.Zeros, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + Array.Empty(), + + PaddingMode.None, + }; + + yield return new object[] + { + // plaintext + Array.Empty(), + + // ciphertext + new byte[] + { + 0x65, 0xE4, 0x9C, 0xD3, 0xE6, 0xBE, 0xB8, 0x40, + }, + + PaddingMode.PKCS7, + }; + } + } + } +} diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs index ce92a62f029b4..9bc2b80f14c80 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/TripleDES/TripleDESCipherTests.cs @@ -9,7 +9,7 @@ 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() diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.OSX.cs index 1c7fbb1ea71f9..9439209338718 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.OSX.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.OSX.cs @@ -7,7 +7,7 @@ namespace Internal.Cryptography { internal sealed partial class AesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Unix.cs index 378b4cf0c1121..1fd8e00cd3d40 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Unix.cs @@ -8,7 +8,7 @@ namespace Internal.Cryptography { internal sealed partial class AesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Windows.cs index 953bcde1cc132..9bb0a9efc797a 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.Windows.cs @@ -8,7 +8,7 @@ namespace Internal.Cryptography { internal sealed partial class AesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs index 9041682804d6d..f00ee5582d363 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AesImplementation.cs @@ -43,6 +43,50 @@ protected sealed override void Dispose(bool disposing) base.Dispose(disposing); } + protected override bool TryDecryptEcbCore( + ReadOnlySpan ciphertext, + Span 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 plaintext, + Span 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 @@ -66,7 +110,15 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs index 0400c5c5c1c3c..9c406f8f107a1 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/AppleCCCryptor.cs @@ -70,7 +70,27 @@ public override int TransformFinal(ReadOnlySpan input, Span output) 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; } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Android.cs index 7e8ebb82a2267..759665214ee51 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Android.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Android.cs @@ -9,7 +9,7 @@ namespace Internal.Cryptography { internal sealed partial class DesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.OSX.cs index 72714b4cf3eb7..015c3a023d49a 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.OSX.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.OSX.cs @@ -7,7 +7,7 @@ namespace Internal.Cryptography { internal sealed partial class DesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Unix.cs index f9f21194f7c94..d4f7d6d5858f7 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Unix.cs @@ -9,7 +9,7 @@ namespace Internal.Cryptography { internal sealed partial class DesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Windows.cs index dba5858591c3d..cdc8e28ac12a5 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.Windows.cs @@ -8,7 +8,7 @@ namespace Internal.Cryptography { internal sealed partial class DesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs index 9c1bf00bbf469..34f0fc6367fc9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/DesImplementation.cs @@ -76,7 +76,59 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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 ciphertext, + Span 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 plaintext, + Span 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) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Android.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Android.cs index fa5604a71262c..39448908dc2e3 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Android.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Android.cs @@ -9,7 +9,7 @@ namespace Internal.Cryptography [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, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.OSX.cs index 1473dc7ca3bd1..af2340f60fba3 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.OSX.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.OSX.cs @@ -7,7 +7,7 @@ namespace Internal.Cryptography { internal sealed partial class RC2Implementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Unix.cs index 4f85d080c6478..d33174d8f89ff 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Unix.cs @@ -9,7 +9,7 @@ namespace Internal.Cryptography { internal sealed partial class RC2Implementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Windows.cs index 5c038c325d503..1a934ec03a7b2 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.Windows.cs @@ -9,7 +9,7 @@ namespace Internal.Cryptography { internal sealed partial class RC2Implementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs index f4f1ed1231519..b4c8029f7a5ac 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/RC2Implementation.cs @@ -61,8 +61,7 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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) @@ -77,10 +76,64 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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 ciphertext, + Span 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 plaintext, + Span 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 @@ -91,5 +144,17 @@ private int GetPaddingSize() { 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); + } } } diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.OSX.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.OSX.cs index 34d3dcf4978fb..461a07a6550d9 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.OSX.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.OSX.cs @@ -7,7 +7,7 @@ namespace Internal.Cryptography { internal sealed partial class TripleDesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Unix.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Unix.cs index 2044b3f596351..b9335a1853740 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Unix.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Unix.cs @@ -8,7 +8,7 @@ namespace Internal.Cryptography { internal sealed partial class TripleDesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Windows.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Windows.cs index b6ea4fca90585..577c771b001d4 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.Windows.cs @@ -8,7 +8,7 @@ namespace Internal.Cryptography { internal sealed partial class TripleDesImplementation { - private static ICryptoTransform CreateTransformCore( + private static UniversalCryptoTransform CreateTransformCore( CipherMode cipherMode, PaddingMode paddingMode, byte[] key, diff --git a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs index 691c5736ea05d..8c0e2825a7380 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs +++ b/src/libraries/System.Security.Cryptography.Algorithms/src/Internal/Cryptography/TripleDesImplementation.cs @@ -81,7 +81,59 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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 ciphertext, + Span 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 plaintext, + Span 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) diff --git a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj index 5577ac649d22f..01c484e98d20f 100644 --- a/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Algorithms/tests/System.Security.Cryptography.Algorithms.Tests.csproj @@ -15,6 +15,8 @@ Link="CommonTest\System\Security\Cryptography\AlgorithmImplementations\AES\AesCipherTests.Data.cs" /> + + + + cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[] iv, bool encrypting, int feedbackSizeInBytes, int paddingSize) + public BasicSymmetricCipherNCrypt(Func cngKeyFactory, CipherMode cipherMode, int blockSizeInBytes, byte[]? iv, bool encrypting, int feedbackSizeInBytes, int paddingSize) : base(iv, blockSizeInBytes, paddingSize) { _encrypting = encrypting; diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs index 6aa654e1eeabc..6b323ccf4607a 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/CngSymmetricAlgorithmCore.cs @@ -111,25 +111,35 @@ public ICryptoTransform CreateDecryptor() 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)); @@ -148,39 +158,46 @@ private ICryptoTransform CreateCryptoTransform(byte[] rgbKey, byte[]? rgbIV, boo // 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 cngKeyFactory, byte[] iv, bool encrypting) + private UniversalCryptoTransform CreatePersistedCryptoTransformCore(Func 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() diff --git a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs index 8fdf656d87c73..1e1edf7c792b8 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/Internal/Cryptography/ICngSymmetricAlgorithm.cs @@ -28,9 +28,9 @@ internal interface ICngSymmetricAlgorithm // 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); } } diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs index 44553eaeeb72d..7a19ca06de97c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/AesCng.cs @@ -92,6 +92,42 @@ public override void GenerateIV() _core.GenerateIV(); } + protected override bool TryDecryptEcbCore( + ReadOnlySpan ciphertext, + Span 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 plaintext, + Span 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); @@ -105,16 +141,16 @@ bool ICngSymmetricAlgorithm.IsWeakKey(byte[] key) 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) { diff --git a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs index 708acee372ad6..3687df173b5eb 100644 --- a/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs +++ b/src/libraries/System.Security.Cryptography.Cng/src/System/Security/Cryptography/TripleDESCng.cs @@ -93,6 +93,42 @@ public override void GenerateIV() _core.GenerateIV(); } + protected override bool TryDecryptEcbCore( + ReadOnlySpan ciphertext, + Span 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 plaintext, + Span 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); @@ -106,14 +142,14 @@ bool ICngSymmetricAlgorithm.IsWeakKey(byte[] key) 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() diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs index 981242fd983a2..dc6d484263c0c 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/AesCngTests.cs @@ -15,6 +15,10 @@ public static class AesCngTests [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 diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs index 2b714471e6560..9f1a604a74a74 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/SymmetricCngTestHelpers.cs @@ -100,6 +100,33 @@ internal static void VerifyPersistedKey( 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); + } + } } } diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj index 1e494d236e18a..1761b8d4a2ea8 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj +++ b/src/libraries/System.Security.Cryptography.Cng/tests/System.Security.Cryptography.Cng.Tests.csproj @@ -146,12 +146,16 @@ Link="CommonTest\AlgorithmImplementations\AES\AesContractTests.cs" /> + + diff --git a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs index 078601871d01a..d68aa596c27d8 100644 --- a/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs +++ b/src/libraries/System.Security.Cryptography.Cng/tests/TripleDESCngTests.cs @@ -29,6 +29,10 @@ public static void VerifyDefaults() [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 diff --git a/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DESCryptoServiceProvider.Windows.cs b/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DESCryptoServiceProvider.Windows.cs index d2536fd679169..2bdd859a691e2 100644 --- a/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DESCryptoServiceProvider.Windows.cs +++ b/src/libraries/System.Security.Cryptography.Csp/src/System/Security/Cryptography/DESCryptoServiceProvider.Windows.cs @@ -95,7 +95,17 @@ private ICryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encr 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); } } diff --git a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs index b2019d7061aba..00a71bd16878d 100644 --- a/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs +++ b/src/libraries/System.Security.Cryptography.Csp/tests/ShimHelpers.cs @@ -77,6 +77,9 @@ public static void VerifyAllBaseMembersOverloaded(Type shimType) "TrySignDataCore", "VerifyDataCore", "VerifySignatureCore", + // CryptoServiceProviders will not get one-shot APIs as they are being deprecated + "TryEncryptEcbCore", + "TryDecryptEcbCore", }; IEnumerable baseMethods = shimType. diff --git a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs index d363d1df18358..49510d6484c64 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/ref/System.Security.Cryptography.Primitives.cs @@ -253,13 +253,23 @@ public void Clear() { } 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 ciphertext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } + public int DecryptEcb(System.ReadOnlySpan ciphertext, System.Span 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 plaintext, System.Security.Cryptography.PaddingMode paddingMode) { throw null; } + public int EncryptEcb(System.ReadOnlySpan plaintext, System.Span 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 ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + protected virtual bool TryDecryptEcbCore(System.ReadOnlySpan ciphertext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + public bool TryEncryptEcb(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } + protected virtual bool TryEncryptEcbCore(System.ReadOnlySpan plaintext, System.Span destination, System.Security.Cryptography.PaddingMode paddingMode, out int bytesWritten) { throw null; } public bool ValidKeySize(int bitLength) { throw null; } } } diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx index 75639b420001c..250a6eceed20e 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx +++ b/src/libraries/System.Security.Cryptography.Primitives/src/Resources/Strings.resx @@ -60,6 +60,9 @@ Error occurred during a cryptographic operation. + + Destination is too short. + 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. @@ -120,6 +123,9 @@ The specified plaintext size is too large. + + {0} unexpectedly produced a ciphertext with the incorrect length. + Method not supported. Derived class must override. diff --git a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs index c140062b697f6..a79a220a707cc 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/src/System/Security/Cryptography/SymmetricAlgorithm.cs @@ -428,6 +428,303 @@ public int GetCiphertextLengthCfb(int plaintextLength, PaddingMode paddingMode = } } + /// + /// Decrypts data using ECB mode with the specified padding mode. + /// + /// The data to decrypt. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The decrypted plaintext data. + /// + /// is . + /// + /// + /// is not a valid padding mode. + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// This method's behavior is defined by . + /// + 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(ciphertext), paddingMode); + } + + /// + /// Decrypts data using ECB mode with the specified padding mode. + /// + /// The data to decrypt. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The decrypted plaintext data. + /// + /// is not a valid padding mode. + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// This method's behavior is defined by . + /// + public byte[] DecryptEcb(ReadOnlySpan 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(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; + } + + /// + /// Decrypts data into the specified buffer, using ECB mode with the specified padding mode. + /// + /// The data to decrypt. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The total number of bytes written to + /// + /// is not a valid padding mode. + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// The buffer in is too small to hold the plaintext data. + /// + /// + /// This method's behavior is defined by . + /// + public int DecryptEcb(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode) + { + CheckPaddingMode(paddingMode); + + if (!TryDecryptEcbCore(ciphertext, destination, paddingMode, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to decrypt data into the specified buffer, using ECB mode with the specified padding mode. + /// + /// The data to decrypt. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// is not a valid padding mode. + /// + /// + /// The ciphertext could not be decrypted successfully. + /// + /// + /// This method's behavior is defined by . + /// + public bool TryDecryptEcb(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + CheckPaddingMode(paddingMode); + return TryDecryptEcbCore(ciphertext, destination, paddingMode, out bytesWritten); + } + + /// + /// Encrypts data using ECB mode with the specified padding mode. + /// + /// The data to encrypt. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The encrypted ciphertext data. + /// + /// is . + /// + /// + /// is not a valid padding mode. + /// + /// + /// could not encrypt the plaintext. + /// + /// + /// This method's behavior is defined by . + /// + 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(plaintext), paddingMode); + } + + /// + /// Encrypts data using ECB mode with the specified padding mode. + /// + /// The data to encrypt. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The encrypted ciphertext data. + /// + /// is not a valid padding mode. + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// This method's behavior is defined by . + /// + public byte[] EncryptEcb(ReadOnlySpan 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(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; + } + + /// + /// Encrypts data into the specified buffer, using ECB mode with the specified padding mode. + /// + /// The data to encrypt. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// The total number of bytes written to . + /// + /// is not a valid padding mode. + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// The buffer in is too small to hold the ciphertext data. + /// + /// + /// This method's behavior is defined by . + /// + public int EncryptEcb(ReadOnlySpan plaintext, Span destination, PaddingMode paddingMode) + { + CheckPaddingMode(paddingMode); + + if (!TryEncryptEcbCore(plaintext, destination, paddingMode, out int written)) + { + throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination)); + } + + return written; + } + + /// + /// Attempts to encrypt data into the specified buffer, using ECB mode with the specified padding mode. + /// + /// The data to encrypt. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// is not a valid padding mode. + /// + /// + /// The plaintext could not be encrypted successfully. + /// + /// + /// This method's behavior is defined by . + /// + public bool TryEncryptEcb(ReadOnlySpan plaintext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + CheckPaddingMode(paddingMode); + return TryEncryptEcbCore(plaintext, destination, paddingMode, out bytesWritten); + } + + /// + /// When overridden in a derived class, attempts to encrypt data into the specified + /// buffer, using ECB mode with the specified padding mode. + /// + /// The data to encrypt. + /// The buffer to receive the ciphertext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the encrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + /// Implementations of this method must write precisely + /// GetCiphertextLengthEcb(plaintext.Length, paddingMode) bytes to + /// and report that via . + /// + /// + protected virtual bool TryEncryptEcbCore( + ReadOnlySpan plaintext, + Span destination, + PaddingMode paddingMode, + out int bytesWritten) + { + throw new NotSupportedException(SR.NotSupported_SubclassOverride); + } + + /// + /// When overridden in a derived class, attempts to decrypt data + /// into the specified buffer, using ECB mode with the specified padding mode. + /// + /// The data to decrypt. + /// The buffer to receive the plaintext data. + /// The padding mode used to produce the ciphertext and remove during decryption. + /// When this method returns, the total number of bytes written to . + /// if was large enough to receive the decrypted data; otherwise, . + /// + /// A derived class has not provided an implementation. + /// + /// + /// Derived classes must override this and provide an implementation. + /// + protected virtual bool TryDecryptEcbCore( + ReadOnlySpan ciphertext, + Span 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; diff --git a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs index 8edfa2b0650aa..65fa6db569945 100644 --- a/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs +++ b/src/libraries/System.Security.Cryptography.Primitives/tests/SymmetricAlgorithmTests.cs @@ -133,6 +133,138 @@ public static void GetCiphertextLengthCfb_NoPaddingAndPlaintextSizeNotFeedbackAl alg.GetCiphertextLengthCfb(17, PaddingMode.None, feedbackSizeInBits: 128)); } + [Fact] + public static void EncryptEcb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.EncryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void DecryptEcb_NotSupportedInDerived() + { + AnySizeAlgorithm alg = new AnySizeAlgorithm { BlockSize = 128 }; + + Assert.Throws(() => + alg.DecryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void EncryptEcb_EncryptProducesIncorrectlyPaddedValue() + { + static bool EncryptImpl(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptEcbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void DecryptEcb_DecryptBytesWrittenLies() + { + static bool DecryptImpl(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = destination.Length + 1; + return true; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptEcbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptEcb(new byte[128 / 8], PaddingMode.None)); + } + + [Fact] + public static void EncryptEcb_EncryptCoreFails() + { + static bool EncryptImpl(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptEcbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void EncryptEcb_EncryptCoreOverflowWritten() + { + static bool EncryptImpl(ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryEncryptEcbCoreImpl = EncryptImpl, + }; + + Assert.Throws(() => + alg.EncryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void DecryptEcb_DecryptCoreFails() + { + static bool DecryptImpl(ReadOnlySpan plaintext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = 0; + return false; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptEcbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptEcb(Array.Empty(), PaddingMode.None)); + } + + [Fact] + public static void DecryptEcb_DecryptCoreOverflowWritten() + { + static bool DecryptImpl(ReadOnlySpan plaintext, Span destination, PaddingMode paddingMode, out int bytesWritten) + { + bytesWritten = -1; + return true; + } + + EcbSymmetricAlgorithm alg = new EcbSymmetricAlgorithm + { + BlockSize = 128, + TryDecryptEcbCoreImpl = DecryptImpl, + }; + + Assert.Throws(() => + alg.DecryptEcb(Array.Empty(), PaddingMode.None)); + } + public static IEnumerable CiphertextLengthTheories { get @@ -231,5 +363,35 @@ public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[] rgbIV) => public override void GenerateIV() => throw new NotImplementedException(); public override void GenerateKey() => throw new NotImplementedException(); } + + private class EcbSymmetricAlgorithm : AnySizeAlgorithm + { + public delegate bool TryEncryptEcbCoreFunc( + ReadOnlySpan plaintext, + Span destination, + PaddingMode paddingMode, + out int bytesWritten); + + public delegate bool TryDecryptEcbCoreFunc( + ReadOnlySpan ciphertext, + Span destination, + PaddingMode paddingMode, + out int bytesWritten); + + public TryEncryptEcbCoreFunc TryEncryptEcbCoreImpl { get; set; } + public TryDecryptEcbCoreFunc TryDecryptEcbCoreImpl { get; set; } + + protected override bool TryEncryptEcbCore( + ReadOnlySpan plaintext, + Span destination, + PaddingMode paddingMode, + out int bytesWritten) => TryEncryptEcbCoreImpl(plaintext, destination, paddingMode, out bytesWritten); + + protected override bool TryDecryptEcbCore( + ReadOnlySpan ciphertext, + Span destination, + PaddingMode paddingMode, + out int bytesWritten) => TryDecryptEcbCoreImpl(ciphertext, destination, paddingMode, out bytesWritten); + } } }