Move KeyDerivation to use the built in Rfc2989 SHA2* support in Core 2.0
blowdart opened this issue · 7 comments
https://docs.microsoft.com/dotnet/api/system.security.cryptography.rfc2898derivebytes now supports SHA256, SHA384 and SHA512. We could remove the code from data protection and just use the framework support.
Looks like a good opportunity to delete code (a favorite pastime). Before we do, however, we should ensure we don't regress behavior by checking our test coverage.
Use caution that you don't cause a perf regression when you do this. The code as currently written determines which platform it is running on and calls into the fastest implementation for the platform. This is especially important when we're talking about password hashing, which defaults to 10,000 iterations. You probably don't want to take the CPU cost of 10,000 p/invoke calls in a tight loop if you can get away with just one. (Of course, this all depends on how the Rfc2898DeriveBytes class is written.)
@GrabYourPitchforks do you think we should do this the other way around, and move the data protection version to core?
@blowdart that would be ideal since you could bring the perf gains to the core class. It looks like Rfc2898DeriveBytes
has some custom caching logic for results so you'd have to make sure that you don't break the scenario where somebody calls GetBytes
multiple times.
Atleast on my Windows machine, the Dataprotection version is still ~25% faster than the pbkdf2 version from .NET Core:
BenchmarkDotNet=v0.10.9, OS=Windows 10 Redstone 1 (10.0.14393)
Processor=Intel Xeon CPU E5-1620 0 3.60GHz, ProcessorCount=8
Frequency=3507177 Hz, Resolution=285.1296 ns, Timer=TSC
.NET Core SDK=2.0.2
[Host] : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT
DefaultJob : .NET Core 2.0.0 (Framework 4.6.00001.0), 64bit RyuJIT
Method | Iterations | Mean | Error | StdDev |
---|---|---|---|---|
Rfc2898DeriveBytesSHA512 | 1000 | 1.273 ms | 0.0034 ms | 0.0032 ms |
KeyDerivationPBKDF2SHA512 | 1000 | 1.019 ms | 0.0025 ms | 0.0024 ms |
Rfc2898DeriveBytesSHA512 | 10000 | 12.652 ms | 0.0160 ms | 0.0142 ms |
KeyDerivationPBKDF2SHA512 | 10000 | 10.143 ms | 0.0361 ms | 0.0301 ms |
Rfc2898DeriveBytesSHA512 | 100000 | 128.595 ms | 0.8824 ms | 0.8254 ms |
KeyDerivationPBKDF2SHA512 | 100000 | 102.741 ms | 0.9762 ms | 0.9131 ms |
Apparently there are quite a few performance improvements in there, last time I checked against the vanilla implementation (modified to support SHA512) on netfx, the dataprotection version was pretty much twice as fast.
As for source:
using System.Security.Cryptography;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
namespace PBKDF2Benchmark
{
public class HashBenchmark
{
[Params(1000, 10000, 100000)]
public int Iterations { get; set; }
private static readonly byte[] _password = Encoding.UTF8.GetBytes("Hello World");
private static readonly byte[] _salt = GenerateSalt();
private static byte[] GenerateSalt()
{
using (var random = new RNGCryptoServiceProvider())
{
var result = new byte[64];
random.GetBytes(result);
return result;
}
}
[Benchmark]
public byte[] Rfc2898DeriveBytesSHA512()
{
var rfc = new Rfc2898DeriveBytes(_password, _salt, Iterations, HashAlgorithmName.SHA512);
return rfc.GetBytes(64);
}
[Benchmark]
public byte[] KeyDerivationPBKDF2SHA512()
{
var key = KeyDerivation.Pbkdf2("Hello World", _salt, KeyDerivationPrf.HMACSHA512, Iterations, 512 / 8);
return key;
}
}
}
Thanks for the numbers. I'll take a look at these more closely before we change implementations.
This issue was moved to dotnet/aspnetcore#2508