From: Kevin Jones Date: Mon, 28 Jun 2021 15:55:27 +0000 (-0400) Subject: Add one-shot ECB methods X-Git-Tag: submit/tizen/20210909.063632~539 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=6b5dbf6bdb95aef88262b1eb949f1028c59dc5e2;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Add one-shot ECB methods This change adds SymmetricAlgorithm.EncryptEcb, SymmetricAlgorithm.DecryptEcb, their respective Try- and -Core methods, derived type implementations thereof, and tests. There's an open question of should these members on on the base class throw or "succeed if the Mode property is in agreement with the algorithm". While the latter is "nicer", just throwing is easier to reason about, and that's the current behavior. Co-authored-by: Jeremy Barton --- diff --git a/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs b/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs index 59d984f..348f1a3 100644 --- a/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs +++ b/src/libraries/Common/src/Internal/Cryptography/BasicSymmetricCipherBCrypt.cs @@ -64,15 +64,27 @@ namespace Internal.Cryptography 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 @@ namespace Internal.Cryptography } 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 c299844..2ee5ffc 100644 --- a/src/libraries/Common/src/Internal/Cryptography/Helpers.cs +++ b/src/libraries/Common/src/Internal/Cryptography/Helpers.cs @@ -30,11 +30,11 @@ namespace Internal.Cryptography 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 3f050ff..0578bb3 100644 --- a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs +++ b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoDecryptor.cs @@ -177,6 +177,62 @@ namespace Internal.Cryptography 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 af88c7e..b913978 100644 --- a/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs +++ b/src/libraries/Common/src/Internal/Cryptography/UniversalCryptoEncryptor.cs @@ -56,6 +56,33 @@ namespace Internal.Cryptography 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 23966df..a3a2216 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 @@ namespace Internal.Cryptography 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 0000000..fd82160 --- /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 e1f5da5..2d7e237 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 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests int? feedbackSize = default) { byte[] decryptedBytes; + byte[] oneShotDecryptedBytes = null; using (Aes aes = AesFactory.Create()) { @@ -1089,10 +1090,20 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests 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 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests { byte[] liveEncryptBytes; byte[] liveDecryptBytes; + byte[] liveOneShotDecryptBytes = null; + byte[] liveOneShotEncryptBytes = null; using (Aes aes = AesFactory.Create()) { @@ -1119,10 +1132,27 @@ namespace System.Security.Cryptography.Encryption.Aes.Tests 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 0000000..6f51068 --- /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 d007a41..dae5048 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 @@ using Xunit; 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 0000000..4a22728 --- /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 7823cc8..7b065d2 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 0000000..3469f4c --- /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 ce92a62..9bc2b80 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 @@ using Xunit; 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 1c7fbb1..9439209 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 378b4cf..1fd8e00 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 953bcde..9bb0a9e 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 9041682..f00ee55 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 @@ namespace Internal.Cryptography 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 @@ namespace Internal.Cryptography 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 0400c5c..9c406f8f 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 @@ namespace Internal.Cryptography 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 7e8ebb8..7596652 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 72714b4..015c3a0 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 f9f2119..d4f7d6d 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 dba5858..cdc8e28 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 9c1bf00..34f0fc6 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 @@ namespace Internal.Cryptography 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 fa5604a..3944890 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 1473dc7..af2340f 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 4f85d08..d33174d 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 5c038c3..1a934ec 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 f4f1ed1..b4c8029 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 @@ namespace Internal.Cryptography 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 @@ namespace Internal.Cryptography 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 @@ namespace Internal.Cryptography { 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 34d3dcf..461a07a 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 2044b3f..b9335a1 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 b6ea4fc..577c771 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 691c573..8c0e282 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 @@ namespace Internal.Cryptography 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 5577ac6..01c484e 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 6aa654e..6b323cc 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 @@ namespace Internal.Cryptography 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 @@ namespace Internal.Cryptography // 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 8fdf656..1e1edf7 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 @@ namespace Internal.Cryptography // 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 44553ea..7a19ca0 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 @@ namespace System.Security.Cryptography _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 @@ namespace System.Security.Cryptography 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 708acee..3687df1 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 @@ namespace System.Security.Cryptography _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 @@ namespace System.Security.Cryptography 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 981242f..dc6d484 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 @@ namespace System.Security.Cryptography.Cng.Tests [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 2b71447..9f1a604 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 @@ namespace System.Security.Cryptography.Cng.Tests 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 1e494d2..1761b8d 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 0786018..d68aa59 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 @@ namespace System.Security.Cryptography.Cng.Tests [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 d2536fd..2bdd859 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 @@ namespace System.Security.Cryptography 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 b2019d7..00a71bd 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 @@ namespace System.Security.Cryptography.Csp.Tests "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 d363d1d..49510d6 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 @@ namespace System.Security.Cryptography 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 75639b4..250a6ec 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 c140062..a79a220 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 @@ namespace System.Security.Cryptography } } + /// + /// 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 8edfa2b..65fa6db 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 @@ namespace System.Security.Cryptography.Primitives.Tests 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 @@ namespace System.Security.Cryptography.Primitives.Tests 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); + } } }