|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Internal.Cryptography;
namespace System.Security.Cryptography
{
public abstract class SymmetricAlgorithm : IDisposable
{
protected SymmetricAlgorithm()
{
ModeValue = CipherMode.CBC;
PaddingValue = PaddingMode.PKCS7;
}
[Obsolete(Obsoletions.DefaultCryptoAlgorithmsMessage, DiagnosticId = Obsoletions.DefaultCryptoAlgorithmsDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
public static SymmetricAlgorithm Create() =>
throw new PlatformNotSupportedException(SR.Cryptography_DefaultAlgorithm_NotSupported);
[Obsolete(Obsoletions.CryptoStringFactoryMessage, DiagnosticId = Obsoletions.CryptoStringFactoryDiagId, UrlFormat = Obsoletions.SharedUrlFormat)]
[RequiresUnreferencedCode(CryptoConfigForwarder.CreateFromNameUnreferencedCodeMessage)]
public static SymmetricAlgorithm? Create(string algName) =>
CryptoConfigForwarder.CreateFromName<SymmetricAlgorithm>(algName);
public virtual int FeedbackSize
{
get
{
return FeedbackSizeValue;
}
set
{
if (value <= 0 || value > BlockSizeValue || (value % 8) != 0)
throw new CryptographicException(SR.Cryptography_InvalidFeedbackSize);
FeedbackSizeValue = value;
}
}
public virtual int BlockSize
{
get
{
return BlockSizeValue;
}
set
{
bool validatedByZeroSkipSizeKeySizes;
if (!value.IsLegalSize(this.LegalBlockSizes, out validatedByZeroSkipSizeKeySizes))
throw new CryptographicException(SR.Cryptography_InvalidBlockSize);
if (BlockSizeValue == value && !validatedByZeroSkipSizeKeySizes) // The !validatedByZeroSkipSizeKeySizes check preserves a very obscure back-compat behavior.
return;
BlockSizeValue = value;
IVValue = null;
return;
}
}
public virtual byte[] IV
{
get
{
if (IVValue == null)
GenerateIV();
return IVValue.CloneByteArray()!;
}
set
{
ArgumentNullException.ThrowIfNull(value);
if (value.Length != this.BlockSize / 8)
throw new CryptographicException(SR.Cryptography_InvalidIVSize);
IVValue = value.CloneByteArray();
}
}
public virtual byte[] Key
{
get
{
if (KeyValue == null)
GenerateKey();
return KeyValue.CloneByteArray()!;
}
set
{
ArgumentNullException.ThrowIfNull(value);
long bitLength = value.Length * 8L;
if (bitLength > int.MaxValue || !ValidKeySize((int)bitLength))
throw new CryptographicException(SR.Cryptography_InvalidKeySize);
// must convert bytes to bits
this.KeySize = (int)bitLength;
KeyValue = value.CloneByteArray();
}
}
public virtual int KeySize
{
get
{
return KeySizeValue;
}
set
{
if (!ValidKeySize(value))
throw new CryptographicException(SR.Cryptography_InvalidKeySize);
KeySizeValue = value;
KeyValue = null;
}
}
public virtual KeySizes[] LegalBlockSizes
{
get
{
// .NET Framework compat: No null check is performed.
return (KeySizes[])LegalBlockSizesValue!.Clone();
}
}
public virtual KeySizes[] LegalKeySizes
{
get
{
// .NET Framework compat: No null check is performed.
return (KeySizes[])LegalKeySizesValue!.Clone();
}
}
public virtual CipherMode Mode
{
get
{
return ModeValue;
}
set
{
if (!(value == CipherMode.CBC || value == CipherMode.ECB || value == CipherMode.CFB))
throw new CryptographicException(SR.Cryptography_InvalidCipherMode);
ModeValue = value;
}
}
public virtual PaddingMode Padding
{
get
{
return PaddingValue;
}
set
{
if ((value < PaddingMode.None) || (value > PaddingMode.ISO10126))
throw new CryptographicException(SR.Cryptography_InvalidPaddingMode);
PaddingValue = value;
}
}
public virtual ICryptoTransform CreateDecryptor()
{
return CreateDecryptor(Key, IV);
}
public abstract ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV);
public virtual ICryptoTransform CreateEncryptor()
{
return CreateEncryptor(Key, IV);
}
public abstract ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Clear()
{
(this as IDisposable).Dispose();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (KeyValue != null)
{
Array.Clear(KeyValue);
KeyValue = null;
}
if (IVValue != null)
{
Array.Clear(IVValue);
IVValue = null;
}
}
}
public abstract void GenerateIV();
public abstract void GenerateKey();
public bool ValidKeySize(int bitLength)
{
KeySizes[] validSizes = this.LegalKeySizes;
if (validSizes == null)
return false;
return bitLength.IsLegalSize(validSizes);
}
/// <summary>
/// Gets the length of a ciphertext with a given padding mode and plaintext length in ECB mode.
/// </summary>
/// <param name="paddingMode">The padding mode used to pad the plaintext to the algorithm's block size.</param>
/// <param name="plaintextLength">The plaintext length, in bytes.</param>
/// <returns>The length, in bytes, of the ciphertext with padding.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="plaintextLength" /> is a negative number.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="plaintextLength" /> when padded is too large to represent as
/// a signed 32-bit integer.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// <see cref="BlockSize" /> is not a positive integer.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <see cref="BlockSize" /> is not a whole number of bytes. It must be divisible by 8.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
/// is not a whole number of blocks.
/// </para>
/// </exception>
public int GetCiphertextLengthEcb(int plaintextLength, PaddingMode paddingMode) =>
GetCiphertextLengthBlockAligned(plaintextLength, paddingMode);
/// <summary>
/// Gets the length of a ciphertext with a given padding mode and plaintext length in CBC mode.
/// </summary>
/// <param name="paddingMode">The padding mode used to pad the plaintext to the algorithm's block size.</param>
/// <param name="plaintextLength">The plaintext length, in bytes.</param>
/// <returns>The length, in bytes, of the ciphertext with padding.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="plaintextLength" /> is a negative number.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="plaintextLength" /> when padded is too large to represent as
/// a signed 32-bit integer.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// <see cref="BlockSize" /> is not a positive integer.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <see cref="BlockSize" /> is not a whole number of bytes. It must be divisible by 8.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
/// is not a whole number of blocks.
/// </para>
/// </exception>
public int GetCiphertextLengthCbc(int plaintextLength, PaddingMode paddingMode = PaddingMode.PKCS7) =>
GetCiphertextLengthBlockAligned(plaintextLength, paddingMode);
private int GetCiphertextLengthBlockAligned(int plaintextLength, PaddingMode paddingMode)
{
ArgumentOutOfRangeException.ThrowIfNegative(plaintextLength);
int blockSizeBits = BlockSize; // The BlockSize property is in bits.
if (blockSizeBits <= 0 || (blockSizeBits & 0b111) != 0)
throw new InvalidOperationException(SR.InvalidOperation_UnsupportedBlockSize);
int blockSizeBytes = blockSizeBits >> 3;
int wholeBlocks = Math.DivRem(plaintextLength, blockSizeBytes, out int remainder) * blockSizeBytes;
switch (paddingMode)
{
case PaddingMode.None when remainder != 0:
throw new ArgumentException(SR.Cryptography_MatchBlockSize, nameof(plaintextLength));
case PaddingMode.None:
case PaddingMode.Zeros when remainder == 0:
return plaintextLength;
case PaddingMode.Zeros:
case PaddingMode.PKCS7:
case PaddingMode.ANSIX923:
case PaddingMode.ISO10126:
if (int.MaxValue - wholeBlocks < blockSizeBytes)
{
throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.Cryptography_PlaintextTooLarge);
}
return wholeBlocks + blockSizeBytes;
default:
throw new ArgumentOutOfRangeException(nameof(paddingMode), SR.Cryptography_InvalidPaddingMode);
}
}
/// <summary>
/// Gets the length of a ciphertext with a given padding mode and plaintext length in CFB mode.
/// </summary>
/// <param name="paddingMode">The padding mode used to pad the plaintext to the feedback size.</param>
/// <param name="plaintextLength">The plaintext length, in bytes.</param>
/// <param name="feedbackSizeInBits">The feedback size, in bits.</param>
/// <returns>The length, in bytes, of the ciphertext with padding.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not a positive number.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="plaintextLength" /> is a negative number.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="plaintextLength" /> when padded is too large to represent as
/// a signed 32-bit integer.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// The padding mode <see cref="PaddingMode.None" /> was used, but <paramref name="plaintextLength" />
/// is not a whole number of blocks.
/// </para>
/// <para>
/// - or -
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not a whole number of bytes. It must be divisible by 8.
/// </para>
/// </exception>
/// <remarks>
/// <paramref name="feedbackSizeInBits" /> accepts any value that is a valid feedback size, regardless if the algorithm
/// supports the specified feedback size.
/// </remarks>
public int GetCiphertextLengthCfb(int plaintextLength, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8)
{
ArgumentOutOfRangeException.ThrowIfNegative(plaintextLength);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(feedbackSizeInBits);
if ((feedbackSizeInBits & 0b111) != 0)
throw new ArgumentException(SR.Argument_BitsMustBeWholeBytes, nameof(feedbackSizeInBits));
int feedbackSizeInBytes = feedbackSizeInBits >> 3;
int feedbackAligned = Math.DivRem(plaintextLength, feedbackSizeInBytes, out int remainder) * feedbackSizeInBytes;
switch (paddingMode)
{
case PaddingMode.None when remainder != 0:
throw new ArgumentException(SR.Cryptography_MatchFeedbackSize, nameof(plaintextLength));
case PaddingMode.None:
case PaddingMode.Zeros when remainder == 0:
return plaintextLength;
case PaddingMode.Zeros:
case PaddingMode.PKCS7:
case PaddingMode.ANSIX923:
case PaddingMode.ISO10126:
if (int.MaxValue - feedbackAligned < feedbackSizeInBytes)
{
throw new ArgumentOutOfRangeException(nameof(plaintextLength), SR.Cryptography_PlaintextTooLarge);
}
return feedbackAligned + feedbackSizeInBytes;
default:
throw new ArgumentOutOfRangeException(nameof(paddingMode), SR.Cryptography_InvalidPaddingMode);
}
}
/// <summary>
/// Decrypts data using ECB mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="ciphertext" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
/// </remarks>
public byte[] DecryptEcb(byte[] ciphertext, PaddingMode paddingMode)
{
ArgumentNullException.ThrowIfNull(ciphertext);
// Padding mode is validated by callee.
return DecryptEcb(new ReadOnlySpan<byte>(ciphertext), paddingMode);
}
/// <summary>
/// Decrypts data using ECB mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
/// </remarks>
public byte[] DecryptEcb(ReadOnlySpan<byte> ciphertext, PaddingMode paddingMode)
{
CheckPaddingMode(paddingMode);
// This could get returned directly to the caller if we there was no padding
// that needed to get removed, so don't rent from a pool.
byte[] decryptBuffer = GC.AllocateUninitializedArray<byte>(ciphertext.Length);
if (!TryDecryptEcbCore(ciphertext, decryptBuffer, paddingMode, out int written)
|| (uint)written > decryptBuffer.Length)
{
// This means decrypting the ciphertext grew in to a larger plaintext or overflowed.
// A user-derived class could do this, but it is not expected in any of the
// implementations that we ship.
throw new CryptographicException(SR.Argument_DestinationTooShort);
}
// Array.Resize will no-op if the array does not need to be resized.
Array.Resize(ref decryptBuffer, written);
return decryptBuffer;
}
/// <summary>
/// Decrypts data into the specified buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The total number of bytes written to <paramref name="destination" /></returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <exception cref="ArgumentException">
/// The buffer in <paramref name="destination"/> is too small to hold the plaintext data.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
/// </remarks>
public int DecryptEcb(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode)
{
CheckPaddingMode(paddingMode);
if (!TryDecryptEcbCore(ciphertext, destination, paddingMode, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to decrypt data into the specified buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptEcbCore" />.
/// </remarks>
public bool TryDecryptEcb(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
{
CheckPaddingMode(paddingMode);
return TryDecryptEcbCore(ciphertext, destination, paddingMode, out bytesWritten);
}
/// <summary>
/// Encrypts data using ECB mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="plaintext" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
/// </remarks>
public byte[] EncryptEcb(byte[] plaintext, PaddingMode paddingMode)
{
ArgumentNullException.ThrowIfNull(plaintext);
// paddingMode is validated by callee
return EncryptEcb(new ReadOnlySpan<byte>(plaintext), paddingMode);
}
/// <summary>
/// Encrypts data using ECB mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
/// </remarks>
public byte[] EncryptEcb(ReadOnlySpan<byte> plaintext, PaddingMode paddingMode)
{
CheckPaddingMode(paddingMode);
int ciphertextLength = GetCiphertextLengthEcb(plaintext.Length, paddingMode);
// We expect most if not all uses to encrypt to exactly the ciphertextLength
byte[] buffer = GC.AllocateUninitializedArray<byte>(ciphertextLength);
if (!TryEncryptEcbCore(plaintext, buffer, paddingMode, out int written) ||
written != ciphertextLength)
{
// This means a user-derived implementation added more padding than we expected or
// did something non-standard (encrypt to a partial block). This can't happen for
// multiple padding blocks since the buffer would have been too small in the first
// place. It doesn't make sense to try and support partial block encryption, likely
// something went very wrong. So throw.
throw new CryptographicException(SR.Format(SR.Cryptography_EncryptedIncorrectLength, nameof(TryEncryptEcbCore)));
}
return buffer;
}
/// <summary>
/// Encrypts data into the specified buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <exception cref="ArgumentException">
/// The buffer in <paramref name="destination"/> is too small to hold the ciphertext data.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
/// </remarks>
public int EncryptEcb(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode)
{
CheckPaddingMode(paddingMode);
if (!TryEncryptEcbCore(plaintext, destination, paddingMode, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to encrypt data into the specified buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptEcbCore" />.
/// </remarks>
public bool TryEncryptEcb(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten)
{
CheckPaddingMode(paddingMode);
return TryEncryptEcbCore(plaintext, destination, paddingMode, out bytesWritten);
}
/// <summary>
/// Decrypts data using CBC mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="ciphertext" /> or <paramref name="iv" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCbcCore" />.
/// </remarks>
public byte[] DecryptCbc(byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.PKCS7)
{
ArgumentNullException.ThrowIfNull(ciphertext);
ArgumentNullException.ThrowIfNull(iv);
return DecryptCbc(new ReadOnlySpan<byte>(ciphertext), new ReadOnlySpan<byte>(iv), paddingMode);
}
/// <summary>
/// Decrypts data using CBC mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCbcCore" />.
/// </remarks>
public byte[] DecryptCbc(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
// CBC is more typically used with padding so the resulting plaintext is
// unlikely to be the same size as the ciphertext. So we rent since we are
// likely to going to need to resize anyway.
// This will pass the rented buffer to user code (the virtual Core) so
// don't use CryptoPool.
byte[] decryptRent = ArrayPool<byte>.Shared.Rent(ciphertext.Length);
Span<byte> decryptBuffer = decryptRent.AsSpan(0, ciphertext.Length);
if (!TryDecryptCbcCore(ciphertext, iv, 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);
}
byte[] plaintext = decryptBuffer.Slice(0, written).ToArray();
CryptographicOperations.ZeroMemory(decryptBuffer.Slice(0, written));
ArrayPool<byte>.Shared.Return(decryptRent);
return plaintext;
}
/// <summary>
/// Decrypts data into the specified buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The total number of bytes written to <paramref name="destination" /></returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <exception cref="ArgumentException">
/// The buffer in <paramref name="destination"/> is too small to hold the plaintext data.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCbcCore" />.
/// </remarks>
public int DecryptCbc(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
if (!TryDecryptCbcCore(ciphertext, iv, destination, paddingMode, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to decrypt data into the specified buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The ciphertext could not be decrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCbcCore" />.
/// </remarks>
public bool TryDecryptCbc(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
return TryDecryptCbcCore(ciphertext, iv, destination, paddingMode, out bytesWritten);
}
/// <summary>
/// Encrypts data using CBC mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="plaintext" /> or <paramref name="iv" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCbcCore" />.
/// </remarks>
public byte[] EncryptCbc(byte[] plaintext, byte[] iv, PaddingMode paddingMode = PaddingMode.PKCS7)
{
ArgumentNullException.ThrowIfNull(plaintext);
ArgumentNullException.ThrowIfNull(iv);
return EncryptCbc(new ReadOnlySpan<byte>(plaintext), new ReadOnlySpan<byte>(iv), paddingMode);
}
/// <summary>
/// Encrypts data using CBC mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCbcCore" />.
/// </remarks>
public byte[] EncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
int ciphertextLength = GetCiphertextLengthCbc(plaintext.Length, paddingMode);
// We expect most if not all uses to encrypt to exactly the ciphertextLength
byte[] buffer = GC.AllocateUninitializedArray<byte>(ciphertextLength);
if (!TryEncryptCbcCore(plaintext, iv, buffer, paddingMode, out int written) ||
written != ciphertextLength)
{
// This means a user-derived implementation 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(TryEncryptCbcCore)));
}
return buffer;
}
/// <summary>
/// Encrypts data into the specified buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <exception cref="ArgumentException">
/// The buffer in <paramref name="destination"/> is too small to hold the ciphertext data.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCbcCore" />.
/// </remarks>
public int EncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
if (!TryEncryptCbcCore(plaintext, iv, destination, paddingMode, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to encrypt data into the specified buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// The plaintext could not be encrypted successfully.
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCbcCore" />.
/// </remarks>
public bool TryEncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = PaddingMode.PKCS7)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
return TryEncryptCbcCore(plaintext, iv, destination, paddingMode, out bytesWritten);
}
/// <summary>
/// Decrypts data using CFB mode with the specified padding mode and
/// feedback size.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="ciphertext" /> or <paramref name="iv" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The ciphertext could not be decrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCfbCore" />.
/// </remarks>
public byte[] DecryptCfb(byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeInBits = 8)
{
ArgumentNullException.ThrowIfNull(ciphertext);
ArgumentNullException.ThrowIfNull(iv);
return DecryptCfb(
new ReadOnlySpan<byte>(ciphertext),
new ReadOnlySpan<byte>(iv),
paddingMode,
feedbackSizeInBits);
}
/// <summary>
/// Decrypts data using CFB mode with the specified padding mode and
/// feedback size.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The decrypted plaintext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The ciphertext could not be decrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCfbCore" />.
/// </remarks>
public byte[] DecryptCfb(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
// The default is CFB8 with no padding, so allocate a buffer
// that is not from the pool since we can return this directly if
// padding does not need to be removed.
byte[] decryptBuffer = GC.AllocateUninitializedArray<byte>(ciphertext.Length);
if (!TryDecryptCfbCore(ciphertext, iv, decryptBuffer, paddingMode, feedbackSizeInBits, out int written)
|| (uint)written > decryptBuffer.Length)
{
// This means decrypting the ciphertext grew in to a larger plaintext or overflowed.
// A user-derived class could do this, but it is not expected in any of the
// implementations that we ship.
throw new CryptographicException(SR.Argument_DestinationTooShort);
}
// Array.Resize will no-op if the array does not need to be resized.
Array.Resize(ref decryptBuffer, written);
return decryptBuffer;
}
/// <summary>
/// Decrypts data into the specified buffer, using CFB mode with the specified padding mode and
/// feedback size.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <para>
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The buffer in <paramref name="destination"/> is too small to hold the plaintext data.
/// </para>
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The ciphertext could not be decrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCfbCore" />.
/// </remarks>
public int DecryptCfb(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
if (!TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to decrypt data into the specified buffer, using CFB mode
/// with the specified padding mode and feedback size.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The ciphertext could not be decrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryDecryptCfbCore" />.
/// </remarks>
public bool TryDecryptCfb(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
return TryDecryptCfbCore(ciphertext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten);
}
/// <summary>
/// Encrypts data using CFB mode with the specified padding mode and feedback size.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="plaintext" /> or <paramref name="iv" /> is <see langword="null" />.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The plaintext could not be encrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCfbCore" />.
/// </remarks>
public byte[] EncryptCfb(
byte[] plaintext,
byte[] iv,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
return EncryptCfb(
new ReadOnlySpan<byte>(plaintext),
new ReadOnlySpan<byte>(iv),
paddingMode,
feedbackSizeInBits);
}
/// <summary>
/// Encrypts data using CFB mode with the specified padding mode and feedback size.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The encrypted ciphertext data.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The plaintext could not be encrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCfbCore" />.
/// </remarks>
public byte[] EncryptCfb(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
int ciphertextLength = GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits);
// We expect most if not all uses to encrypt to exactly the ciphertextLength
byte[] buffer = GC.AllocateUninitializedArray<byte>(ciphertextLength);
if (!TryEncryptCfbCore(plaintext, iv, buffer, paddingMode, feedbackSizeInBits, out int written) ||
written != ciphertextLength)
{
// This means a user-derived implementation 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(TryEncryptCfbCore)));
}
return buffer;
}
/// <summary>
/// Encrypts data into the specified buffer, using CFB mode with the specified padding mode
/// and feedback size.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns>The total number of bytes written to <paramref name="destination" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The plaintext could not be encrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCfbCore" />.
/// </remarks>
public int EncryptCfb(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
if (!TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out int written))
{
throw new ArgumentException(SR.Argument_DestinationTooShort, nameof(destination));
}
return written;
}
/// <summary>
/// Attempts to encrypt data into the specified buffer, using CFB mode with the specified padding mode
/// and feedback size.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// <para>
/// <paramref name="paddingMode" /> is not a valid padding mode.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// <paramref name="feedbackSizeInBits" /> is not positive or represent a whole number of bytes.
/// </para>
/// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="iv" /> is the incorrect length. Callers are expected to pass an initialization vector
/// that is exactly <see cref="BlockSize" /> in length, converted to bytes (<c>BlockSize / 8</c>).
/// </exception>
/// <exception cref="CryptographicException">
/// <para>
/// The plaintext could not be encrypted successfully.
/// </para>
/// <para>
/// -or-
/// </para>
/// <para>
/// The feedback size is not valid for the algorithm.
/// </para>
/// </exception>
/// <remarks>
/// This method's behavior is defined by <see cref="TryEncryptCfbCore" />.
/// </remarks>
public bool TryEncryptCfb(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeInBits = 8)
{
CheckPaddingMode(paddingMode);
CheckInitializationVectorSize(iv);
CheckFeedbackSize(feedbackSizeInBits);
return TryEncryptCfbCore(plaintext, iv, destination, paddingMode, feedbackSizeInBits, out bytesWritten);
}
/// <summary>
/// When overridden in a derived class, attempts to encrypt data into the specified
/// buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// <para>Derived classes must override this and provide an implementation.</para>
/// <para>
/// Implementations of this method must write precisely
/// <c>GetCiphertextLengthEcb(plaintext.Length, paddingMode)</c> bytes to <paramref name="destination"/>
/// and report that via <paramref name="bytesWritten"/>.
/// </para>
/// </remarks>
protected virtual bool TryEncryptEcbCore(
ReadOnlySpan<byte> plaintext,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten)
{
throw new NotSupportedException(SR.NotSupported_SubclassOverride);
}
/// <summary>
/// When overridden in a derived class, attempts to decrypt data
/// into the specified buffer, using ECB mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// Derived classes must override this and provide an implementation.
/// </remarks>
protected virtual bool TryDecryptEcbCore(
ReadOnlySpan<byte> ciphertext,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten)
{
throw new NotSupportedException(SR.NotSupported_SubclassOverride);
}
/// <summary>
/// When overridden in a derived class, attempts to encrypt data into the specified
/// buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// <para>Derived classes must override this and provide an implementation.</para>
/// <para>
/// Implementations of this method must write precisely
/// <c>GetCiphertextLengthCbc(plaintext.Length, paddingMode)</c> bytes to <paramref name="destination"/>
/// and report that via <paramref name="bytesWritten"/>.
/// </para>
/// </remarks>
protected virtual bool TryEncryptCbcCore(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten)
{
throw new NotSupportedException(SR.NotSupported_SubclassOverride);
}
/// <summary>
/// When overridden in a derived class, attempts to decrypt data
/// into the specified buffer, using CBC mode with the specified padding mode.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// Derived classes must override this and provide an implementation.
/// </remarks>
protected virtual bool TryDecryptCbcCore(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten)
{
throw new NotSupportedException(SR.NotSupported_SubclassOverride);
}
/// <summary>
/// When overridden in a derived class, attempts to decrypt data
/// into the specified buffer, using CFB mode with the specified padding mode
/// and feedback size.
/// </summary>
/// <param name="ciphertext">The data to decrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the plaintext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the decrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// Derived classes must override this and provide an implementation.
/// </remarks>
protected virtual bool TryDecryptCfbCore(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
int feedbackSizeInBits,
out int bytesWritten)
{
throw new NotSupportedException(SR.NotSupported_SubclassOverride);
}
/// <summary>
/// When overridden in a derived class, attempts to encrypt data into the specified
/// buffer, using CFB mode with the specified padding mode and feedback size.
/// </summary>
/// <param name="plaintext">The data to encrypt.</param>
/// <param name="iv">The initialization vector.</param>
/// <param name="destination">The buffer to receive the ciphertext data.</param>
/// <param name="paddingMode">The padding mode used to produce the ciphertext and remove during decryption.</param>
/// <param name="feedbackSizeInBits">The feedback size, specified in bits.</param>
/// <param name="bytesWritten">When this method returns, the total number of bytes written to <paramref name="destination" />.</param>
/// <returns><see langword="true"/> if <paramref name="destination"/> was large enough to receive the encrypted data; otherwise, <see langword="false" />.</returns>
/// <exception cref="NotSupportedException">
/// A derived class has not provided an implementation.
/// </exception>
/// <remarks>
/// <para>Derived classes must override this and provide an implementation.</para>
/// <para>
/// Implementations of this method must write precisely
/// <c>GetCiphertextLengthCfb(plaintext.Length, paddingMode, feedbackSizeInBits)</c>
/// bytes to <paramref name="destination"/> and report that via <paramref name="bytesWritten"/>.
/// </para>
/// </remarks>
protected virtual bool TryEncryptCfbCore(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
int feedbackSizeInBits,
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);
}
private void CheckInitializationVectorSize(ReadOnlySpan<byte> iv)
{
if (iv.Length != BlockSize >> 3)
throw new ArgumentException(SR.Cryptography_InvalidIVSize, nameof(iv));
}
private void CheckFeedbackSize(int feedbackSizeInBits)
{
if (feedbackSizeInBits < 8 || (feedbackSizeInBits & 0b111) != 0 || feedbackSizeInBits > BlockSize)
{
throw new ArgumentException(SR.Cryptography_InvalidFeedbackSize, nameof(feedbackSizeInBits));
}
}
protected CipherMode ModeValue;
protected PaddingMode PaddingValue;
protected byte[]? KeyValue;
protected byte[]? IVValue;
protected int BlockSizeValue;
protected int FeedbackSizeValue;
protected int KeySizeValue;
[MaybeNull] protected KeySizes[] LegalBlockSizesValue = null!;
[MaybeNull] protected KeySizes[] LegalKeySizesValue = null!;
}
}
|