Skip to content

[API Proposal]: add possibility to pass key as Span<byte> in SymmetricAlgorithm and KeyedHashAlgorithm #111154

@DeagleGross

Description

@DeagleGross

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):

  1. 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.

  1. 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:

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

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Securityin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions