diff --git a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs index 7b56a7017c4bab..e43b37a77ec9f7 100644 --- a/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs +++ b/src/libraries/Common/tests/System/Security/Cryptography/AlgorithmImplementations/Symmetric/SymmetricOneShotBase.cs @@ -5,6 +5,7 @@ using System.IO; using System.Reflection; using System.Text; +using System.Threading; using System.Security.Cryptography; using Microsoft.DotNet.XUnitExtensions; using Test.Cryptography; @@ -572,6 +573,226 @@ public void DecryptOneShot_Cfb8_TooShortDoesNotContainPlaintext(PaddingMode padd } } + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(CipherMode.ECB)] + [InlineData(CipherMode.CBC)] + [InlineData(CipherMode.CFB)] + public void TryEncryptOneShot_KeyChange(CipherMode mode) + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + if (alg is RC2 && mode == CipherMode.CFB) + { + throw new SkipTestException("RC2 does not support CFB."); + } + + alg.Key = Key; + + ReadOnlySpan plaintext = "hello world"u8; + Span destinationBuffer = stackalloc byte[32]; + int bytesWritten; + + bool result = mode switch + { + CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, PaddingMode.PKCS7, out bytesWritten), + CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, PaddingMode.PKCS7), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, PaddingMode.PKCS7, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryEncrypt"); + byte[] ciphertext1 = destinationBuffer.Slice(0, bytesWritten).ToArray(); + + alg.GenerateKey(); + + result = mode switch + { + CipherMode.ECB => alg.TryEncryptEcb(plaintext, destinationBuffer, PaddingMode.PKCS7, out bytesWritten), + CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, destinationBuffer, out bytesWritten, PaddingMode.PKCS7), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, destinationBuffer, out bytesWritten, PaddingMode.PKCS7, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryEncrypt"); + byte[] ciphertext2 = destinationBuffer.Slice(0, bytesWritten).ToArray(); + Assert.NotEqual(ciphertext1, ciphertext2); + + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(CipherMode.ECB)] + [InlineData(CipherMode.CBC)] + [InlineData(CipherMode.CFB)] + public void TryDecryptOneShot_KeyChange(CipherMode mode) + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + if (alg is RC2 && mode == CipherMode.CFB) + { + throw new SkipTestException("RC2 does not support CFB."); + } + + alg.Key = Key; + + ReadOnlySpan ciphertext = stackalloc byte[alg.BlockSize / 8]; + Span destinationBuffer = stackalloc byte[32]; + int bytesWritten; + + bool result = mode switch + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryDecrypt"); + byte[] plaintext1 = destinationBuffer.Slice(0, bytesWritten).ToArray(); + + alg.GenerateKey(); + + result = mode switch + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, destinationBuffer, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, destinationBuffer, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, destinationBuffer, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryDecrypt"); + byte[] plaintext2 = destinationBuffer.Slice(0, bytesWritten).ToArray(); + Assert.NotEqual(plaintext1, plaintext2); + + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(CipherMode.ECB)] + [InlineData(CipherMode.CBC)] + [InlineData(CipherMode.CFB)] + public void TryDecryptOneShot_Multithreaded(CipherMode mode) + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + if (alg is RC2 && mode == CipherMode.CFB) + { + throw new SkipTestException("RC2 does not support CFB."); + } + + alg.Key = Key; + + // The more blocks the operation uses the more likely a race conditional will occur. 10k bytes results + // in the race occuring almost immediately if there is an actual race condition. + ReadOnlySpan plaintext = ByteArrayFilledWith(0xCC, 1024 * 10); + byte[] ciphertext = new byte[plaintext.Length]; + int bytesWritten; + + bool result = mode switch + { + CipherMode.ECB => alg.TryEncryptEcb(plaintext, ciphertext, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryEncryptCbc(plaintext, IV, ciphertext, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, IV, ciphertext, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryEncrypt"); + Assert.Equal(ciphertext.Length, bytesWritten); + + Thread t1 = new Thread(ThreadCallback); + Thread t2 = new Thread(ThreadCallback); + t1.Start((alg, IV, ciphertext, mode)); + t2.Start((alg, IV, ciphertext, mode)); + t1.Join(); + t2.Join(); + + static void ThreadCallback(object state) + { + (SymmetricAlgorithm alg, byte[] iv, byte[] ciphertext, CipherMode mode) = ((SymmetricAlgorithm, byte[], byte[], CipherMode))state; + Span plaintext = new byte[ciphertext.Length]; + + for (int i = 0; i < 100; i++) + { + int bytesWritten; + bool result = mode switch + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, plaintext, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, iv, plaintext, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, iv, plaintext, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryDecrypt"); + Assert.Equal(plaintext.Length, bytesWritten); + AssertExtensions.FilledWith(0xCC, plaintext); + } + } + } + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindows7))] + [InlineData(CipherMode.ECB)] + [InlineData(CipherMode.CBC)] + [InlineData(CipherMode.CFB)] + public void TryEncryptOneShot_Multithreaded(CipherMode mode) + { + using (SymmetricAlgorithm alg = CreateAlgorithm()) + { + if (alg is RC2 && mode == CipherMode.CFB) + { + throw new SkipTestException("RC2 does not support CFB."); + } + + alg.Key = Key; + + // The more blocks the operation uses the more likely a race conditional will occur. 10k bytes results + // in the race occuring almost immediately if there is an actual race condition. + ReadOnlySpan ciphertext = ByteArrayFilledWith(0xCC, 1024 * 10); + byte[] plaintext = new byte[ciphertext.Length]; + int bytesWritten; + + bool result = mode switch + { + CipherMode.ECB => alg.TryDecryptEcb(ciphertext, plaintext, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryDecryptCbc(ciphertext, IV, plaintext, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryDecryptCfb(ciphertext, IV, plaintext, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryDecrypt"); + Assert.Equal(ciphertext.Length, bytesWritten); + + Thread t1 = new Thread(ThreadCallback); + Thread t2 = new Thread(ThreadCallback); + t1.Start((alg, IV, plaintext, mode)); + t2.Start((alg, IV, plaintext, mode)); + t1.Join(); + t2.Join(); + + static void ThreadCallback(object state) + { + (SymmetricAlgorithm alg, byte[] iv, byte[] plaintext, CipherMode mode) = ((SymmetricAlgorithm, byte[], byte[], CipherMode))state; + Span ciphertext = new byte[plaintext.Length]; + + for (int i = 0; i < 100; i++) + { + int bytesWritten; + bool result = mode switch + { + CipherMode.ECB => alg.TryEncryptEcb(plaintext, ciphertext, PaddingMode.None, out bytesWritten), + CipherMode.CBC => alg.TryEncryptCbc(plaintext, iv, ciphertext, out bytesWritten, PaddingMode.None), + CipherMode.CFB => alg.TryEncryptCfb(plaintext, iv, ciphertext, out bytesWritten, PaddingMode.None, feedbackSizeInBits: 8), + _ => throw new NotImplementedException(), + }; + + Assert.True(result, "TryEncrypt"); + Assert.Equal(ciphertext.Length, bytesWritten); + AssertExtensions.FilledWith(0xCC, ciphertext); + } + } + } + } + private static void AssertPlaintexts(ReadOnlySpan expected, ReadOnlySpan actual, PaddingMode padding) { if (padding == PaddingMode.Zeros) diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.cs index c3ea366cc2b35c..b4189fb8b43c91 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/AesImplementation.cs @@ -1,12 +1,21 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Threading; using Internal.Cryptography; namespace System.Security.Cryptography { internal sealed partial class AesImplementation : Aes { + private const int BitsPerByte = 8; + + private ILiteSymmetricCipher? _encryptCbcLiteHash; + private ILiteSymmetricCipher? _decryptCbcLiteHash; + private ILiteSymmetricCipher? _encryptEcbLiteHash; + private ILiteSymmetricCipher? _decryptEcbLiteHash; + public sealed override ICryptoTransform CreateDecryptor() { return CreateTransform(Key, IV, encrypting: false); @@ -39,28 +48,35 @@ public sealed override void GenerateKey() protected sealed override void Dispose(bool disposing) { + InvalidateOneShotAlgorithms(); base.Dispose(disposing); } + public override byte[] Key + { + set + { + base.Key = value; + InvalidateOneShotAlgorithms(); + } + } + protected override bool TryDecryptEcbCore( ReadOnlySpan ciphertext, Span destination, PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptEcbLiteHash, CipherMode.ECB, - Key, iv: default, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryEncryptEcbCore( @@ -69,19 +85,16 @@ protected override bool TryEncryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptEcbLiteHash, CipherMode.ECB, - Key, iv: default, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryEncryptCbcCore( @@ -91,19 +104,16 @@ protected override bool TryEncryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryDecryptCbcCore( @@ -113,19 +123,16 @@ protected override bool TryDecryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryDecryptCfbCore( @@ -220,6 +227,57 @@ private static void ValidateCFBFeedbackSize(int feedback) } } - private const int BitsPerByte = 8; + private bool OneShotTransformation( + ref ILiteSymmetricCipher? cipherCache, + CipherMode cipherMode, + ReadOnlySpan iv, + bool encrypting, + PaddingMode paddingMode, + ReadOnlySpan source, + Span destination, + UniversalCryptoOneShot.UniversalOneShotCallback callback, + out int bytesWritten) + { + // Ensures we have a zero feedback size. + Debug.Assert(cipherMode is CipherMode.CBC or CipherMode.ECB); + + // Try grabbing a cached instance. + ILiteSymmetricCipher? cipher = Interlocked.Exchange(ref cipherCache, null); + + if (cipher is null) + { + // If there is no cached instance available, create one. This also sets the initialization vector during creation. + cipher = CreateLiteCipher( + cipherMode, + Key, + iv, + blockSize: BlockSize / BitsPerByte, + paddingSize: BlockSize / BitsPerByte, + 0, /*feedback size */ + encrypting); + } + else + { + // If we did grab a cached instance, put it back in to a working state. This needs to happen even for ECB + // since lite ciphers do not reset after the final transformation automatically. + cipher.Reset(iv); + } + + bool result = callback(cipher, paddingMode, source, destination, out bytesWritten); + + // Try making this instance available to use again later. If another thread put one there, dispose of it. + cipher = Interlocked.Exchange(ref cipherCache, cipher); + cipher?.Dispose(); + + return result; + } + + private void InvalidateOneShotAlgorithms() + { + Interlocked.Exchange(ref _encryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _encryptEcbLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptEcbLiteHash, null)?.Dispose(); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/DesImplementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/DesImplementation.cs index 4fb16cdf77055e..aa9a0ee0e1efc5 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/DesImplementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/DesImplementation.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Threading; using Internal.Cryptography; namespace System.Security.Cryptography @@ -10,6 +12,11 @@ internal sealed partial class DesImplementation : DES { private const int BitsPerByte = 8; + private ILiteSymmetricCipher? _encryptCbcLiteHash; + private ILiteSymmetricCipher? _decryptCbcLiteHash; + private ILiteSymmetricCipher? _encryptEcbLiteHash; + private ILiteSymmetricCipher? _decryptEcbLiteHash; + public DesImplementation() { // Default CFB to CFB8. .NET Framework uses 8 as the default for DESCryptoServiceProvider which @@ -52,7 +59,23 @@ public sealed override void GenerateKey() { RandomNumberGenerator.Fill(key); } - KeyValue = key; + + Key = key; + } + + protected sealed override void Dispose(bool disposing) + { + InvalidateOneShotAlgorithms(); + base.Dispose(disposing); + } + + public override byte[] Key + { + set + { + base.Key = value; + InvalidateOneShotAlgorithms(); + } } private UniversalCryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting) @@ -99,19 +122,16 @@ protected override bool TryDecryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptEcbLiteHash, CipherMode.ECB, - Key, - iv: null, - blockSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - paddingSize: BlockSize / BitsPerByte, - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + iv: default, + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryEncryptEcbCore( @@ -120,19 +140,16 @@ protected override bool TryEncryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptEcbLiteHash, CipherMode.ECB, - Key, - iv: null, - blockSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - paddingSize: BlockSize / BitsPerByte, - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + iv: default, + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryEncryptCbcCore( @@ -142,19 +159,16 @@ protected override bool TryEncryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - paddingSize: BlockSize / BitsPerByte, - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryDecryptCbcCore( @@ -164,19 +178,16 @@ protected override bool TryDecryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - paddingSize: BlockSize / BitsPerByte, - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryDecryptCfbCore( @@ -237,5 +248,58 @@ private static void ValidateCFBFeedbackSize(int feedback) throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); } } + + private bool OneShotTransformation( + ref ILiteSymmetricCipher? cipherCache, + CipherMode cipherMode, + ReadOnlySpan iv, + bool encrypting, + PaddingMode paddingMode, + ReadOnlySpan source, + Span destination, + UniversalCryptoOneShot.UniversalOneShotCallback callback, + out int bytesWritten) + { + // Ensures we have a zero feedback size. + Debug.Assert(cipherMode is CipherMode.CBC or CipherMode.ECB); + + // Try grabbing a cached instance. + ILiteSymmetricCipher? cipher = Interlocked.Exchange(ref cipherCache, null); + + if (cipher is null) + { + // If there is no cached instance available, create one. This also sets the initialization vector during creation. + cipher = CreateLiteCipher( + cipherMode, + Key, + iv, + blockSize: BlockSize / BitsPerByte, + 0, /*feedback size */ + paddingSize: BlockSize / BitsPerByte, + encrypting); + } + else + { + // If we did grab a cached instance, put it back in to a working state. This needs to happen even for ECB + // since lite ciphers do not reset after the final transformation automatically. + cipher.Reset(iv); + } + + bool result = callback(cipher, paddingMode, source, destination, out bytesWritten); + + // Try making this instance available to use again later. If another thread put one there, dispose of it. + cipher = Interlocked.Exchange(ref cipherCache, cipher); + cipher?.Dispose(); + + return result; + } + + private void InvalidateOneShotAlgorithms() + { + Interlocked.Exchange(ref _encryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _encryptEcbLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptEcbLiteHash, null)?.Dispose(); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RC2Implementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RC2Implementation.cs index f785edeedb03fd..fa3985fed1656a 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RC2Implementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/RC2Implementation.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; +using System.Threading; using Internal.Cryptography; namespace System.Security.Cryptography @@ -11,6 +12,11 @@ internal sealed partial class RC2Implementation : RC2 { private const int BitsPerByte = 8; + private ILiteSymmetricCipher? _encryptCbcLiteHash; + private ILiteSymmetricCipher? _decryptCbcLiteHash; + private ILiteSymmetricCipher? _encryptEcbLiteHash; + private ILiteSymmetricCipher? _decryptEcbLiteHash; + public override int EffectiveKeySize { get @@ -54,6 +60,21 @@ public sealed override void GenerateKey() Key = RandomNumberGenerator.GetBytes(KeySize / BitsPerByte); } + protected sealed override void Dispose(bool disposing) + { + InvalidateOneShotAlgorithms(); + base.Dispose(disposing); + } + + public override byte[] Key + { + set + { + base.Key = value; + InvalidateOneShotAlgorithms(); + } + } + private UniversalCryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting) { ArgumentNullException.ThrowIfNull(rgbKey); @@ -85,22 +106,16 @@ protected override bool TryDecryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - if (!ValidKeySize(Key.Length)) - throw new InvalidOperationException(SR.Cryptography_InvalidKeySize); - - Debug.Assert(EffectiveKeySize == KeySize); - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptEcbLiteHash, CipherMode.ECB, - Key, - iv: null, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + iv: default, + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryEncryptEcbCore( @@ -109,22 +124,16 @@ protected override bool TryEncryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - if (!ValidKeySize(Key.Length)) - throw new InvalidOperationException(SR.Cryptography_InvalidKeySize); - - Debug.Assert(EffectiveKeySize == KeySize); - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptEcbLiteHash, CipherMode.ECB, - Key, iv: default, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryEncryptCbcCore( @@ -134,22 +143,16 @@ protected override bool TryEncryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - if (!ValidKeySize(Key.Length)) - throw new InvalidOperationException(SR.Cryptography_InvalidKeySize); - - Debug.Assert(EffectiveKeySize == KeySize); - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryDecryptCbcCore( @@ -159,22 +162,16 @@ protected override bool TryDecryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - if (!ValidKeySize(Key.Length)) - throw new InvalidOperationException(SR.Cryptography_InvalidKeySize); - - Debug.Assert(EffectiveKeySize == KeySize); - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryDecryptCfbCore( @@ -220,5 +217,60 @@ private int GetPaddingSize() int keySizeBits = keySizeBytes << 3; return keySizeBits.IsLegalSize(LegalKeySizes); } + + private bool OneShotTransformation( + ref ILiteSymmetricCipher? cipherCache, + CipherMode cipherMode, + ReadOnlySpan iv, + bool encrypting, + PaddingMode paddingMode, + ReadOnlySpan source, + Span destination, + UniversalCryptoOneShot.UniversalOneShotCallback callback, + out int bytesWritten) + { + // Ensures we have a zero feedback size. + Debug.Assert(cipherMode is CipherMode.CBC or CipherMode.ECB); + + if (!ValidKeySize(Key.Length)) + throw new InvalidOperationException(SR.Cryptography_InvalidKeySize); + + // Try grabbing a cached instance. + ILiteSymmetricCipher? cipher = Interlocked.Exchange(ref cipherCache, null); + + if (cipher is null) + { + // If there is no cached instance available, create one. This also sets the initialization vector during creation. + cipher = CreateLiteCipher( + cipherMode, + Key, + iv, + blockSize: BlockSize / BitsPerByte, + paddingSize: BlockSize / BitsPerByte, + encrypting); + } + else + { + // If we did grab a cached instance, put it back in to a working state. This needs to happen even for ECB + // since lite ciphers do not reset after the final transformation automatically. + cipher.Reset(iv); + } + + bool result = callback(cipher, paddingMode, source, destination, out bytesWritten); + + // Try making this instance available to use again later. If another thread put one there, dispose of it. + cipher = Interlocked.Exchange(ref cipherCache, cipher); + cipher?.Dispose(); + + return result; + } + + private void InvalidateOneShotAlgorithms() + { + Interlocked.Exchange(ref _encryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _encryptEcbLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptEcbLiteHash, null)?.Dispose(); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/TripleDesImplementation.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/TripleDesImplementation.cs index 7708ff71482a6f..a489bd34d46965 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/TripleDesImplementation.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/TripleDesImplementation.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Diagnostics; +using System.Threading; using Internal.Cryptography; namespace System.Security.Cryptography @@ -10,6 +12,11 @@ internal sealed partial class TripleDesImplementation : TripleDES { private const int BitsPerByte = 8; + private ILiteSymmetricCipher? _encryptCbcLiteHash; + private ILiteSymmetricCipher? _decryptCbcLiteHash; + private ILiteSymmetricCipher? _encryptEcbLiteHash; + private ILiteSymmetricCipher? _decryptEcbLiteHash; + public TripleDesImplementation() { // Default CFB to CFB8 to match .NET Framework's default for TripleDES.Create() @@ -47,6 +54,21 @@ public sealed override void GenerateKey() Key = RandomNumberGenerator.GetBytes(KeySize / BitsPerByte); } + protected sealed override void Dispose(bool disposing) + { + InvalidateOneShotAlgorithms(); + base.Dispose(disposing); + } + + public override byte[] Key + { + set + { + base.Key = value; + InvalidateOneShotAlgorithms(); + } + } + private UniversalCryptoTransform CreateTransform(byte[] rgbKey, byte[]? rgbIV, bool encrypting) { ArgumentNullException.ThrowIfNull(rgbKey); @@ -96,19 +118,16 @@ protected override bool TryDecryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptEcbLiteHash, CipherMode.ECB, - Key, iv: default, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryEncryptEcbCore( @@ -117,19 +136,16 @@ protected override bool TryEncryptEcbCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptEcbLiteHash, CipherMode.ECB, - Key, iv: default, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryEncryptCbcCore( @@ -139,19 +155,16 @@ protected override bool TryEncryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _encryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: true); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotEncrypt(cipher, paddingMode, plaintext, destination, out bytesWritten); - } + encrypting: true, + paddingMode, + plaintext, + destination, + UniversalCryptoOneShot.OneShotEncrypt, + out bytesWritten); } protected override bool TryDecryptCbcCore( @@ -161,19 +174,16 @@ protected override bool TryDecryptCbcCore( PaddingMode paddingMode, out int bytesWritten) { - ILiteSymmetricCipher cipher = CreateLiteCipher( + return OneShotTransformation( + ref _decryptCbcLiteHash, CipherMode.CBC, - Key, iv, - blockSize: BlockSize / BitsPerByte, - paddingSize: BlockSize / BitsPerByte, - 0, /*feedback size */ - encrypting: false); - - using (cipher) - { - return UniversalCryptoOneShot.OneShotDecrypt(cipher, paddingMode, ciphertext, destination, out bytesWritten); - } + encrypting: false, + paddingMode, + ciphertext, + destination, + UniversalCryptoOneShot.OneShotDecrypt, + out bytesWritten); } protected override bool TryDecryptCfbCore( @@ -234,5 +244,58 @@ private static void ValidateCFBFeedbackSize(int feedback) throw new CryptographicException(SR.Format(SR.Cryptography_CipherModeFeedbackNotSupported, feedback, CipherMode.CFB)); } } + + private bool OneShotTransformation( + ref ILiteSymmetricCipher? cipherCache, + CipherMode cipherMode, + ReadOnlySpan iv, + bool encrypting, + PaddingMode paddingMode, + ReadOnlySpan source, + Span destination, + UniversalCryptoOneShot.UniversalOneShotCallback callback, + out int bytesWritten) + { + // Ensures we have a zero feedback size. + Debug.Assert(cipherMode is CipherMode.CBC or CipherMode.ECB); + + // Try grabbing a cached instance. + ILiteSymmetricCipher? cipher = Interlocked.Exchange(ref cipherCache, null); + + if (cipher is null) + { + // If there is no cached instance available, create one. This also sets the initialization vector during creation. + cipher = CreateLiteCipher( + cipherMode, + Key, + iv, + blockSize: BlockSize / BitsPerByte, + paddingSize: BlockSize / BitsPerByte, + 0, /*feedback size */ + encrypting); + } + else + { + // If we did grab a cached instance, put it back in to a working state. This needs to happen even for ECB + // since lite ciphers do not reset after the final transformation automatically. + cipher.Reset(iv); + } + + bool result = callback(cipher, paddingMode, source, destination, out bytesWritten); + + // Try making this instance available to use again later. If another thread put one there, dispose of it. + cipher = Interlocked.Exchange(ref cipherCache, cipher); + cipher?.Dispose(); + + return result; + } + + private void InvalidateOneShotAlgorithms() + { + Interlocked.Exchange(ref _encryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptCbcLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _encryptEcbLiteHash, null)?.Dispose(); + Interlocked.Exchange(ref _decryptEcbLiteHash, null)?.Dispose(); + } } } diff --git a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/UniversalCryptoOneShot.cs b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/UniversalCryptoOneShot.cs index 50ced69fa656ab..eaccdb11f13701 100644 --- a/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/UniversalCryptoOneShot.cs +++ b/src/libraries/System.Security.Cryptography/src/System/Security/Cryptography/UniversalCryptoOneShot.cs @@ -223,5 +223,12 @@ public static bool OneShotEncrypt( Debug.Assert(padWritten == bytesWritten); return true; } + + internal delegate bool UniversalOneShotCallback( + ILiteSymmetricCipher cipher, + PaddingMode paddingMode, + ReadOnlySpan source, + Span destination, + out int bytesWritten); } }