Skip to content
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

Analyzer / fixer proposal: Prefer static HashData methods over ComputeHash #40579

Closed
Tracked by #57797
GrabYourPitchforks opened this issue Aug 8, 2020 · 20 comments · Fixed by dotnet/roslyn-analyzers#4797
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Security code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer
Milestone

Comments

@GrabYourPitchforks
Copy link
Member

Category: performance, improved linker trimming efficiency (is that even a category?)

We should consider an analyzer that can detect this pattern:

byte[] buffer = GetSomeBuffer();
using (var sha256 = SHA256.Create())
{
    byte[] digest = sha256.ComputeHash(buffer);
    /* use 'digest' here */
}

And suggest the code instead use this pattern:

byte[] buffer = GetSomeBuffer();
byte[] digest = SHA256.HashData(buffer);
/* use 'digest' here */

Using one-shot hashing APIs like this is a bit more foolproof than using the normal stateful instance members on these types.

The analyzer should detect the pattern where a HashAlgorithm instance is created (either via SHA256.Create, new SHA256Managed, or new SHA256CryptoServiceProvider; or via the MD5 / SHA* equivalents), there is a single call made to HashAlgorithm.ComputeHash(byte[]), then there is an optional call made to Dispose.

The analyzer should only trigger for projects targeting net5.0+, as that's when the new static APIs were introduced.

See also: #17590, dotnet/aspnetcore#24696, dotnet/wpf#3318

@GrabYourPitchforks GrabYourPitchforks added area-System.Security code-analyzer Marks an issue that suggests a Roslyn analyzer labels Aug 8, 2020
@GrabYourPitchforks GrabYourPitchforks added this to the Future milestone Aug 8, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Aug 8, 2020
@ghost
Copy link

ghost commented Aug 8, 2020

Tagging subscribers to this area: @bartonjs, @vcsjones, @krwq
See info in area-owners.md if you want to be subscribed.

@bartonjs bartonjs removed the untriaged New issue has not been triaged by the area owner label Aug 28, 2020
@stephentoub stephentoub added api-ready-for-review API is ready for review, it is NOT ready for implementation code-fixer Marks an issue that suggests a Roslyn code fixer labels Oct 12, 2020
@terrajobst terrajobst added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Oct 20, 2020
@terrajobst
Copy link
Member

terrajobst commented Oct 20, 2020

Video

This makes sense.

@wzchua
Copy link
Contributor

wzchua commented Feb 5, 2021

Which severity should this be?

@GrabYourPitchforks
Copy link
Member Author

Probably suggestion / informational / etc.? We tend to keep perf-focused analyzers low severity unless the caller is doing something egregiously wrong.

@wzchua
Copy link
Contributor

wzchua commented Feb 5, 2021

I see.
I would like to be assigned this issue.

@Ponant
Copy link
Contributor

Ponant commented Jul 22, 2021

Is the bottom line that the using can be skipped by using the static HashData?

@bartonjs
Copy link
Member

Is the bottom line that the using can be skipped by using the static HashData?

  • The using
  • The object creation
  • The pooling that people often write to avoid the object creation
  • The questions about thread safety that some of the people who write pooling often have

@stephentoub
Copy link
Member

What Jeremy said... it's simpler and faster.

Method Mean Error StdDev Ratio Gen 0 Gen 1 Gen 2 Allocated
WithInstance 1,185.3 ns 10.21 ns 9.55 ns 1.00 0.0381 - - 240 B
WithStatic 972.3 ns 12.58 ns 11.15 ns 0.82 0.0076 - - 56 B
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Security.Cryptography;

[MemoryDiagnoser]
public class Program
{
    public static void Main(string[] args) => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

    private readonly byte[] _data = RandomNumberGenerator.GetBytes(128);

    [Benchmark(Baseline = true)]
    public byte[] WithInstance()
    {
        using (var sha256 = SHA256.Create())
        {
            return sha256.ComputeHash(_data);
        }
    }

    [Benchmark]
    public byte[] WithStatic()
    {
        return SHA256.HashData(_data);
    }
}

@Ponant
Copy link
Contributor

Ponant commented Jul 22, 2021

Well that is pretty great guys. I came accross this in a pretty random way. I saw methods using using ReadOnlySpan, should we favor them, too?
Also, how disposal work now, I guess in the background but how?

@GrabYourPitchforks
Copy link
Member Author

I saw methods using using ReadOnlySpan, should we favor them, too?

It depends on your needs. If you already have a destination span you want the results written to, call the overload that takes a destination span. Otherwise call the overload that creates the result array on your behalf.

Also, how disposal work now, I guess in the background but how?

Depends on the OS. For example, we may keep a shared handle around and keep reusing it. Or we may call a specialty OS API under the covers. This is just an implementation detail and callers shouldn't need to worry about it.

@Ponant
Copy link
Contributor

Ponant commented Jul 22, 2021

That is great thank you. I think the docs should explain this somehow, I was updating a library I had from .Net Standard and somehow came to this thread by chance. The compute hash method is not labelled as obsolete.

@Ponant
Copy link
Contributor

Ponant commented Jul 22, 2021

@stephentoub , the allocation ratio is huge! (240/56)

@stephentoub
Copy link
Member

That's because it's only allocating the resulting byte[] now rather than also allocating the SHA256 instance and associated state.

@GrabYourPitchforks
Copy link
Member Author

I think the docs should explain this somehow, ... The compute hash method is not labelled as obsolete.

The docs should explain what, specifically? The existing ComputeHash instance method is not obsolete, nor are there current plans to make it obsolete.

@Ponant
Copy link
Contributor

Ponant commented Jul 22, 2021

Explain the difference in usage, and also provide examples as done above. If not to be obsoleted, then explain in what scenario the old method would be better, e.g. for projects targeting .Net5+ the static version seems better.
E.g. in these docs the using is used https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.sha256?view=net-5.0.

@Ponant
Copy link
Contributor

Ponant commented Jul 30, 2021

Does the skipping of the using statement also applies to RandomNumberGenerator?
Example

 var random= RandomNumberGenerator.GetBytes(16);
 var encode = WebEncoders.Base64UrlEncode(random);

Or should we use

using (var rng = RandomNumberGenerator.Create())
{
...
}

?

@vcsjones
Copy link
Member

Does the skipping of the using statement also applies to RandomNumberGenerator?

You should use the static members when possible on RandomNumberGenerator. (and in the case of GetInt32, it is only available as a static member). The benefits are similar, there is no object to allocate and no need to reason about the lifetime of the object.

@vcsjones
Copy link
Member

vcsjones commented Jul 30, 2021

Speaking more broadly, there has been a lot of "staticification" of the crypto stuff in .NET:

  1. RandomNumberGenerator had static members added back in the .NET Core 2 and 3 timeframe. In .NET 6, GetBytes(int) was added as a convenience of doing new byte[n] and calling Fill on it.

Similarly, GetInt32 was added as a static-only member, out of a desire to use statics going forward.

  1. .NET 5 added static HashData to the hash APIs (which is the topic of this issue)

  2. .NET 6 added static Pbkdf2 to Rfc2898DerivedBytes.

  3. .NET 6 added static HashData to the HMAC APIs (eg HMACSHA256.HashData).

@Ponant
Copy link
Contributor

Ponant commented Jul 30, 2021

Thank you for the detailed info @vcsjones and what you have done sounds great and surely welcomed.

@danmoseley
Copy link
Member

@jeffhandley should this be in 6.0 at this point?

@jeffhandley jeffhandley modified the milestones: 6.0.0, 7.0.0 Sep 3, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 21, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-System.Security code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer
Projects
None yet
Development

Successfully merging a pull request may close this issue.