-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
Nowdays, aspnetcore DataProtection layer uses SymmetricAlgorithm.CreateDecryptor() and SymmetricAlgorithm.CreateEncryptor() for decryption and encryption operations.
While attempting to improve performance in this PR, I came across a limiting API in SymmetricAlgorithm
class (and implementations): there is no way to pass in key
of algorithm as Span<byte>
or ReadOnlySpan<byte>
(only byte[]
is available) to Encrypt
or Decrypt
methods.
The only possibilities now are (below only showing encryption flow, but the same applies to decryption):
- allocating encryptor and
CryptoStream
to write a plain text
using (var symmetricAlgorithm = CreateSymmetricAlgorithm())
using (var cryptoTransform = symmetricAlgorithm.CreateEncryptor(encryptionSubkey, iv))
using (var cryptoStream = new CryptoStream(outputStream, cryptoTransform, CryptoStreamMode.Write))
{
cryptoStream.Write(plaintext.Array!, plaintext.Offset, plaintext.Count);
the problem with this API is that implementation was using MemoryStream
as outputStream
, which is not that performant.
- set value for
SymmetricAlgorithm.Key
property
using var symmetricAlgorithm = CreateSymmetricAlgorithm(key: encryptionSubkey);
...
symmetricAlgorithm.EncryptCbc(plainText, iv, outputSpan.Slice(start: keyModifierLength + ivLength, length: cipherTextLength));
the problem with this API is that SymmetricAlgorithm.Key {set} is actually cloning a byte array. However, for aspnetcore DataProtection flow there is no need to do it - we could try to rent or stackalloc
memory, fill it with key data, pass it in and after it is used clear it out.
Same thing happens for KeyedHashAlgorithm
. Ask is to think to support this API as well:
Line 31 in 10f0bbf
KeyValue = value.CloneByteArray(); |
API Proposal
namespace System.Security.Cryptography;
public partial class SymmetricAlgorithm
{
public void SetKey(ReadOnlySpan<byte> key);
protected virtual void SetKey(ReadOnlySpan<byte> key);
}
public partial class KeyedHashAlgorithm
{
public void SetKey(ReadOnlySpan<byte> key);
protected virtual void SetKey(ReadOnlySpan<byte> key);
}
Alternatives
Basically the same kind of API exists for validationAlgorithms. In example HMACSHA256.HashData or HMACSHA512.HashData: HMACSHAxxx
are implementations of KeyedHashAlgorithm
, and I am able to determine the type and then call static method HashData
, which accepts validation algorithm key as ReadOnlySpan<byte>
:
if (validationAlgorithm is HMACSHA256)
{
HMACSHA256.HashData(key: validationSubkey, source: hashSource, destination: correctHash);
}
else if (validationAlgorithm is HMACSHA512)
{
HMACSHA512.HashData(key: validationSubkey, source: hashSource, destination: correctHash);
}
else // not known type, so we only can call `TryComputeHash()` here
{
// fill in the key for hash compute operation
validationAlgorithm.Key = validationSubkeyArray;
validationAlgorithm.TryComputeHash(hashSource, correctHash, out _);
}
however, for the unknown type (last else
block) there is no way to set the key except cloning the byte array under the hood.
I think we could be able to have something similar on the SymmetricAlgorithm type (or on implementations):
note: I am proposing both Encrypt and Decrypt API here, and probably we should add it for all modes (cbc, cfb, ecb)
public abstract class SymmetricAlgorithm : IDisposable
{
public int EncryptCbc(ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode = PaddingMode.PKCS7);
+ public static int EncryptCbc(ReadOnlySpan<byte> key, ReadOnlySpan<byte> plaintext, ReadOnlySpan<byte> iv, Span<byte> destination, PaddingMode paddingMode = PaddingMode.PKCS7);
public byte[] DecryptCbc(ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.PKCS7);
+ public static byte[] DecryptCbc(ReadOnlySpan<byte> key, ReadOnlySpan<byte> ciphertext, ReadOnlySpan<byte> iv, PaddingMode paddingMode = PaddingMode.PKCS7);
}
Usage
As proposed in current issue discussion, there could be a separate method on the types to set the temporary key as Span<byte>
, which later will be used by other methods.
SymmetricAlgorithm symmetricAlgorithm = Factory(state);
Span<byte> key = stackalloc byte[32];
key = DeriveKey(state, key);
-byte[] keyArray = key.ToArray();
-symmetricAlgorithm.Key = keyArray;
+symmetricAlgorithm.SetKey(key);
-CryptographicOperations.ZeroMemory(keyArray);
CryptographicOperations.ZeroMemory(key);
Risks
No response