Repository navigation
Closed
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Security
Milestone
Description
Edit by @GrabYourPitchforks on 20 Jan 2020: Formal API proposal written at https://github.com/dotnet/corefx/issues/9369#issuecomment-576445733.
Computing a hash code currently requires this code:
byte[] hashBytes;
using (var hashAlgo = SHA256.Create())
hashBytes = hashAlgo.ComputeHash(bytes);
Or the more awkward HashAlgorithm.Create("SHA256").
This code is alright. It's not the end of the world. But I think it should be slimmer than that:
var hashBytes = SHA256.ComputeHash(bytes);
Benefits:
- This is more terse.
- It's an expression as opposed to a statement. That makes it more composable. It can be a subexpression for example in a LINQ query.
- No way to forget resource cleanup or mess up code quality otherwise.
SHA256.ComputeHashcan look at it's input size and dynamically pick the fastest implementation. I found the following to be optimal through testing on Windows x64:estimatedDataLength <= 512 ? new SHA1Managed() : HashAlgorithm.Create("SHA1"). Apparently, using the Windows crypto API has quite some per-hash cost.
I request that static helper method be added to the framework. This seems like an attractive case for a community contribution.
Proposal:
Using this pattern on all of the HashAlgorithm-derived types that are not KeyedHashAlgorithm-derived types (MD5, SHA1, SHA256, SHA384, SHA512):
public partial class SHA256
{
public static byte[] Hash(byte[] source) => throw null;
public static byte[] Hash(ReadOnlySpan<byte> source) => throw null;
public static int Hash(ReadOnlySpan<byte> source, ReadOnlySpan<byte> destination) => throw null;
public static bool TryHash(ReadOnlySpan<byte> source, ReadOnlySpan<byte> destination, out int bytesWritten) => throw null;
}Reacted by OmariO, GSPP, Centurys, Martin Andreas Ullrich, Joseph Musser, Stephen Reindl, Aron Parker, Levi Broderick, Gérald Barré, strangeman375 and 2 more
Metadata
Metadata
Assignees
Labels
api-approvedAPI was approved in API review, it can be implementedAPI was approved in API review, it can be implementedarea-System.Security
Activity
joshfree commentedon Jun 12, 2016
Thanks @GSPP! @bartonjs take a look and tag this as appropriate.
bartonjs commentedon Jun 13, 2016
There are both good and bad sides to this idea.
The succinctness, composability, and reduced strain on the finalizer are all good. But "mess up code quality" is hard to gauge... if you're hashing a lot then it's better to hold the hash object across calls to avoid the setup/teardown overhead.
If we were to make it a static method then we need to make it thread safe (by the general rules of the framework), which means one of
None of those are hard, or necessarily bad, but they all have different tradeoffs; and I'm not sure we'd be able to determine which one is best for the framework; other than guessing arbitrarily.
And, of course, there's an instance v static naming problem. The good name (ComputeHash) is already taken as a public instance method on a base class. We could redefine it with
new, but I'm not sure it can really be done in a way that helps more than it confuses. (Or maybe we do this all the time and I've just never noticed).(And as for the switching between the managed and native implementations, that could be done on the .NET Framework implementation, but in .NET Core we don't have a managed implementation).
So... given that we've broken IncrementalHash out into its own class, I think this definitely makes sense to have been the original implementation model. I'm just not sure that it can really be worked in to the existing API without creating more confusion than benefit.
(Maybe a new class which has each algorithm as a method?
)
GSPP commentedon Jun 13, 2016
I like the new class idea.
There could be an overload for
Streamand, more questionably, an overload forstring, Encoding. Hashing strings is common and often done wrong because the encoding aspect is not understood. This overload would gently inform the user that the encoding is an important choice.I did not think of sharing the object. I like that a lot! That would increase efficiency to nearly optimal levels.
I consider it bad practice to reuse crypto objects because that's error potential in very sensitive code. That's even more reason to add the static functions. If the framework does it correctly callers can rely on it and forget about playing caching tricks.
Now the advice can be: "If you want to hash anything, just use the
Hashclass." That's super simple guidance with almost no potential of misuse. I like that a lot.morganbr commentedon Jun 16, 2016
I've always been a fan of adding convenience methods for the very common situation of having all the data you intend to hash (or do other crypto operations with). I'd rather not add more classes since we already have classes people know to use; adding static methods should generally be fine (maybe something like Sha256.Hash(byte[])?).
As @GSPP pointed out, as the framework, we can experiment with the policies you floated and find something that generally works reasonably well. If somebody finds a situation that isn't good for them, they're free to manage their own instances. Among your suggestions, I like attempting to do some kind of sharing for general efficiency. As long as we're not holding on to a ton of hashers, I think it's fine to have them open for a while... they don't use that many resources.
karelz commentedon Nov 16, 2016
We need API proposal.
GSPP commentedon Aug 12, 2018
API proposal:
Let's add static methods to the individual hash classes, e.g.
I'd not add a new class
Hash(or similar name) with usage likeHash.SHA256(...). Each hash type is logically isolated. User code might add more hash algorithms that would not be represented on thisHashclass leading to confusion.Libraries providing more hash algorithms can simply implement the static method pattern set forth by the framework.
We could cache hash algorithm instances. One issue here is that permanent resource usage might result from performing just a single or a few hash operations.
The cache could either be a
ThreadStaticfield or a global pool. I suspect that the overhead of any global pool would destroy too much performance.Here is a sample implementation for a thread static solution:
The main advantage is simplicity and performance. The main downside is potentially permanent resource usage from those cached algorithms. There are two instances per thread which can result in hundreds of objects.
An alternative here is to simply not cache anything and create a fresh instance each time.
There should be overloads for accepting
Span<byte>,Streamand possibly pipelines if that is possible from a dependency standpoint.GrabYourPitchforks commentedon Feb 28, 2019
On Win10+, the OS exposes a one-shot p/invoke API
BCryptHash. You don't even need to open or cache an algorithm handle; you can pass the well-known constantBCRYPT_SHA256_ALG_HANDLE(for SHA256) in as the first parameter and let the OS take care of everything on your behalf. Pseudohandles for other hash routines are defined in bcrypt.h.On other OSes, we could have a thread-static
WeakReference<HashAlgorithm>if we're concerned about holding on to these objects for too long. That would allow the GC to reclaim them.GrabYourPitchforks commentedon Jan 20, 2020
API proposal from the thread, more formally stated:
bartonjs commentedon Jan 20, 2020
As ComputeHash is an instance method, making a static method with the same name seems overly confusing (removed ready-for-review)
bartonjs commentedon Jan 20, 2020
It's actually worse than confusing, it's a recompile-breaking change. Anyone with a strongly typed
SHA256that currently invokes the instance method will get a compile error:GrabYourPitchforks commentedon Jan 20, 2020
@bartonjs You have an alternative proposal that puts these as static members on a new static type, which would work around the issue. Even with the issues you point out it still seems like something we'd allow to come through API review since it's an actionable, concrete proposal.
bartonjs commentedon Jan 20, 2020
The proposal for a new type named Hash? Since we've spanified things since then, I think it would be
Morgan's suggestion was to use Hash as the method name for the one-shot, which would be
The latter generalizes better (anyone who adds a hash algorithm in a NuGet package, or whatever, can follow that pattern, but they can't add static methods on our Hash accelerator type).
Having done a lot of work lately on patterns and practices, I think I prefer it as methods on the existing classes, as long as
Hash(or some other non-breaking-change verb) seems discoverable enough.23 remaining items
GSPP commentedon Jul 10, 2020
Maybe
HashBuffer? That would signal that a single buffer is being processed. This is in contrast to the other more general APIs (TransformBlock, ...).Alternatives:
HashBytes,GetHash.bartonjs commentedon Jul 10, 2020
Video
We discussed this again, and accepted HashData.
vcsjones commentedon Jul 10, 2020
I think I can squeeze this one in over the weekend.
bartonjs commentedon Jul 10, 2020
Regarding the suggestion of "HashBytes": there are existing methods named (Try)HashData... admittedly, they're all protected; but there aren't any named "HashBytes", so "HashData" is more platform-consistent.
bartonjs commentedon Jul 10, 2020
If you want to PAL through to native one-shots, that'd be best. If you want to go quicker with
[ThreadStatic], that's fine too.vcsjones commentedon Jul 10, 2020
That's the intention.
We don't really have a native PAL for CNG (do we?) and CNG's one-shot
BCryptHashis Windows 10+, so maybe both?bartonjs commentedon Jul 10, 2020
Well, we have the PAL (split build for the OS), we just don't carry a native shim.
vcsjones commentedon Jul 15, 2020
New APIs are in. Work left to be done:
EVP_sha1,EVP_sha256, etc inHashAlgorithmToEvpto avoid a P/Invoke call.