-
Notifications
You must be signed in to change notification settings - Fork 4.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: Hash and HMAC one-shots for HashAlgorithName #91407
Comments
Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones Issue DetailsBackground and motivationOver the past few releases of .NET, we've added a number of "one shots" for cryptographic hashing and HMACs (#62489, #40012, #17590). All of these one-shots live off of their respective type. For a SHA1 one-shot, use There is one other mechanism for producing HMACs and hashes, which is If you want to use this in conjunction with the hashing one-shots, you currently need to switch over the byte[] hash = myHashAlgorithmName.Name switch {
"SHA1" => SHA1.HashData(data),
"SHA256" => SHA256.HashData(data),
// etc
_ => throw new ArgumentException("helpful message"),
} This has a number of undesirable properties:
We have our own internal implementation of this called API ProposalThis proposal is identical to all of the hashing one-shots on SHA{n} and HMACSHA{n}, with a namespace System.Security.Cryptography;
// Existing class
public static partial class CryptographicOperations {
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source);
public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source, Span<byte> destination);
public static ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default);
public static ValueTask<int> HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, CancellationToken cancellationToken = default);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source);
public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination);
public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source);
public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span<byte> destination);
public static ValueTask<int> HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default);
} API Usagepublic static void SomeLibraryMethod(HashAlgorithmName hashAlgorithm) {
byte[] digest = CryptographicOperations.HashData(hashAlgorithm, "hash this"u8);
} Alternative DesignsNo response RisksNo response
|
There's a "why CryptographicOperations?" that goes with this, so I'll summarize an offline discussion: What's the most obvious place for these? Why are those bad? OK, those are out. So is there anywhere that accepts a HashAlgorithmName and does a hash already? Well, sure, RSA/ECDSA/DSA/ECDH... and... Since everything for the function is in its name and its state (the type is only required because of language/runtime rules), it felt sort of in the camp of ReadBigEndian or a math operation. Those end up in types like -Primitives or -Operations... and, guess what, we already have a CryptographicOperations. Huzzah. So, does that mean CryptographicOperations would become a dumping ground for other "typeless functions" in the crypto space? Well, in one sense that's what it's for (like the fixed time equality check). In a closer context, the problem here is that the expected names were taken. Our new algorithms/concepts haven't tried to introduce type hierarchies, and generally have thought about both static/one-shot and instance/accumulator patterns at design time; which just leaves existing modelled concepts. Core crypto boils down to hashing, HMAC, symmetric encryption, and asymmetric algorithms. Hashing and HMAC are this proposal, asymmetric has cumbersome keys and needs some sort of instance to track externalized keys (e.g. HSM), so that leaves symmetric. Symmetric cryptography sometimes has externalized keys (CNG supports persisted 3DES/AES keys), and the platform underlying instances maintain state that is easier to reset than it is to build (so two operations with the same key are cheaper if they reused the instance)... so static one-shots (vs the instance one-shots we already added) are of very marginal value. So, basically, hashing and HMAC are the only things that would "pollute" CryptographicOperations, and there's not an obvious good name for a type to hold these statics that isn't already taken by a type hierarchy... so it seems reasonable. |
Looks good as proposed. namespace System.Security.Cryptography;
// Existing class
public static partial class CryptographicOperations {
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, byte[] source);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source);
public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination);
public static bool TryHmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, byte[] key, Stream source);
public static byte[] HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source);
public static int HmacData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> key, Stream source, Span<byte> destination);
public static ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, byte[] key, Stream source, CancellationToken cancellationToken = default);
public static ValueTask<int> HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HmacDataAsync(HashAlgorithmName hashAlgorithm, ReadOnlyMemory<byte> key, Stream source, CancellationToken cancellationToken = default);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, byte[] source);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source);
public static int HashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination);
public static bool TryHashData(HashAlgorithmName hashAlgorithm, ReadOnlySpan<byte> source, Span<byte> destination, out int bytesWritten);
public static byte[] HashData(HashAlgorithmName hashAlgorithm, Stream source);
public static int HashData(HashAlgorithmName hashAlgorithm, Stream source, Span<byte> destination);
public static ValueTask<int> HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, Memory<byte> destination, CancellationToken cancellationToken = default);
public static ValueTask<byte[]> HashDataAsync(HashAlgorithmName hashAlgorithm, Stream source, CancellationToken cancellationToken = default);
} |
Background and motivation
Over the past few releases of .NET, we've added a number of "one shots" for cryptographic hashing and HMACs (#62489, #40012, #17590).
All of these one-shots live off of their respective type. For a SHA1 one-shot, use
SHA1
, for HMAC-SHA256, useHMACSHA256.HashData
, etc.There is one other mechanism for producing HMACs and hashes, which is
HashAlgorithmName
. This type is an identifier for the hash, but does not implement the hash itself. This type is fed in to places likeIncrementalHash
,AsymmetricAlgorithm.{SignData,VerifyData}
, etc. This is useful when libraries want to allow the caller to specify the hash algorithm used.If you want to use this in conjunction with the hashing one-shots, you currently need to switch over the
HashAlgorithmName
.This has a number of undesirable properties:
HashProviderDispenser
, actually prefers working with names. So this switch logic is inefficient, since we start out with a name, convert it to a static type, which then just changes it to the name. So the runtime can actually provide a better implementation by going directly to the internalHashProviderDispenser
.HashAlgorithmName.SHA256
, they end up rooting all of the static hash APIs, regardless of if they are used. This was identified in Rfc2898DeriveBytes.Pbkdf2 is trimming unfriendly #91181We have our own internal implementation of this called
HashOneShotHelpers
. It's used dozens of times, so there is a clear need for it ourselves, others will benefit from it.API Proposal
This proposal is identical to all of the hashing one-shots on SHA{n} and HMACSHA{n}, with a
HashAlgorithmName
as the first parameter.API Usage
Alternative Designs
No response
Risks
No response
The text was updated successfully, but these errors were encountered: