Description
.NET Core 3.0 has AesCcm
and AesGcm
type that are simple, one-shot APIs. I really like the API shape of these and would suggest that it might be worth adding AesCbc
and AesEcb
(sorry) types.
The one-shot (no streaming, no update / finalize) would give a few benefits.
-
I like that the mode is part of the type. The modes aren't really interchangeable, and having them as part of the type helps suggest that. That is, as far as using a primitive goes, I can't really "upgrade" from one mode to another like it's some kind of configuration. It requires some thought and consideration as to how each mode behaves.
-
Type-per-mode would allow the API shape to fit the mode better (no IV for ECB).
-
One-shot usage would be hard to "you're holding it wrong". The API surface would be simple
Decrypt
andEncrypt
methods. -
One-shot API would mean there is no state to carry between calls (like CNG updating the IV).
That might afford a few more opportunities to use CryptoPool or stack buffers instead ofnew byte[]
. -
Do not support some less common types of block padding (PKCS#7 and no padding only). This can help developers make better choices security-wise and push handling down to the underlying implementation.
/cc @GrabYourPitchforks @bartonjs
Updated API Proposal
Since the API review we added CFB support, so this adds the CFB members, and adds a mild reshaping to account for CFB being different than ECB and CBC:
Current approved additions, with no updates required:
namespace System.Security.Cryptography
{
public abstract class SymmetricAlgorithm
{
// ECB (Block Aligned, no default padding, no IV)
public byte[] DecryptEcb(byte[] ciphertext, PaddingMode paddingMode);
public byte[] DecryptEcb(ReadOnlySpan<byte> ciphertext, PaddingMode paddingMode);
public int DecryptEcb(ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode);
public byte[] EncryptEcb(byte[] plaintext, PaddingMode paddingMode);
public byte[] EncryptEcb(ReadOnlySpan<byte> plaintext, PaddingMode paddingMode);
public int EncryptEcb(ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode);
public bool TryDecryptEcb(
ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten);
protected virtual bool TryDecryptEcbCore(
ReadOnlySpan<byte> ciphertext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten);
public bool TryEncryptEcb(
ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten);
protected virtual bool TryEncryptEcbCore(
ReadOnlySpan<byte> plaintext, Span<byte> destination, PaddingMode paddingMode, out int bytesWritten);
// CBC (Block Aligned, default to PKCS#7 padding, IV required)
public byte[] DecryptCbc(byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.PKCS7);
public byte[] DecryptCbc(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.PKCS7);
public int DecryptCbc(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination,
PaddingMode paddingMode = PaddingMode.PKCS7);
public byte[] EncryptCbc(byte[] plaintext, byte[] iv, PaddingMode paddingMode = PaddingMode.PKCS7);
public byte[] EncryptCbc(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.PKCS7);
public int EncryptCbc(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination,
PaddingMode paddingMode = PaddingMode.PKCS7);
public bool TryDecryptCbc(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten,
PaddingMode paddingMode = PaddingMode.PKCS7);
protected virtual bool TryDecryptCbcCore(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode,
out int bytesWritten);
public bool TryEncryptCbc(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten,
PaddingMode paddingMode = PaddingMode.PKCS7);
protected virtual bool TryEncryptCbcCore(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode,
out int bytesWritten);
}
}
CFB additions
namespace System.Security.Cryptography
{
public abstract class SymmetricAlgorithm
{
// CFB (Feedback-size aligned, default to No padding, IV required, adds feedback size (default 8))
public byte[] DecryptCfb(
byte[] ciphertext, byte[] iv, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
public byte[] DecryptCfb(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeBits = 8);
public int DecryptCfb(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination,
PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
public byte[] EncryptCfb(
byte[] plaintext, byte[] iv, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
public byte[] EncryptCfb(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.None,
int feedbackSizeBits = 8);
public int EncryptCfb(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination,
PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
public bool TryDecryptCfb(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten,
PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
public bool TryEncryptCfb(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, out int bytesWritten,
PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
protected virtual bool TryDecryptCfbCore(
ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode,
int feedbackSizeBits, out int bytesWritten);
protected virtual bool TryEncryptCfbCore(
ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode,
int feedbackSizeBits, out int bytesWritten);
}
}
Modifications of existing approved members:
namespace System.Security.Cryptography
{
public abstract class SymmetricAlgorithm
{
- public int GetCiphertextLength(PaddingMode paddingMode, int plaintextLength);
+ public int GetCiphertextLengthEcb(int plaintextLength, PaddingMode paddingMode);
+ public int GetCiphertextLengthCbc(int plaintextLength, PaddingMode paddingMode = PaddingMode.PKCS7);
+ public int GetCiphertextLengthCfb(
+ int plaintextLength, PaddingMode paddingMode = PaddingMode.None, int feedbackSizeBits = 8);
}
}
Previously discussed, outdated, API Proposal
namespace System.Security.Cryptography {
public abstract class SymmetricAlgorithm {
public byte[] EncryptEcb(
byte[] plaintext,
PaddingMode paddingMode = default);
public byte[] EncryptEcb(
ReadOnlySpan<byte> plaintext,
PaddingMode paddingMode = default);
public int EncryptEcb(
ReadOnlySpan<byte> plaintext,
Span<byte> destination,
PaddingMode paddingMode = default);
public bool TryEncryptEcb(
ReadOnlySpan<byte> plaintext,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = default);
public byte[] EncryptCbc(
byte[] plaintext,
byte[] iv,
PaddingMode paddingMode = default);
public byte[] EncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
PaddingMode paddingMode = default);
public int EncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = default);
public bool TryEncryptCbc(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = default);
public byte[] DecryptEcb(
byte[] ciphertext,
PaddingMode paddingMode = default);
public byte[] DecryptEcb(
ReadOnlySpan<byte> ciphertext,
PaddingMode paddingMode = default);
public int DecryptEcb(
ReadOnlySpan<byte> ciphertext,
Span<byte> destination,
PaddingMode paddingMode = default);
public bool TryDecryptEcb(
ReadOnlySpan<byte> ciphertext,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = default);
public byte[] DecryptCbc(
byte[] ciphertext,
byte[] iv,
PaddingMode paddingMode = default);
public byte[] DecryptCbc(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
PaddingMode paddingMode = default);
public int DecryptCbc(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode = default);
public bool TryDecryptCbc(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
out int bytesWritten,
PaddingMode paddingMode = default);
protected virtual bool TryEncryptCbcCore(
ReadOnlySpan<byte> plaintext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten);
protected virtual bool TryDecryptCbcCore(
ReadOnlySpan<byte> ciphertext,
ReadOnlySpan<byte> iv,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten);
protected virtual bool TryEncryptEcbCore(
ReadOnlySpan<byte> plaintext,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten);
protected virtual bool TryDecryptEcbCore(
ReadOnlySpan<byte> ciphertext,
Span<byte> destination,
PaddingMode paddingMode,
out int bytesWritten);
public int GetCiphertextLength(PaddingMode paddingMode, int plaintextLength);
}
}