Switch default implementation of Pbkdf2 on Linux/macOS to Rfc2898DeriveBytes (#301)
Requires adding .NET Core 2.0 target framework to the package because only SHA1 is supported in .NET Standard 2.0
This commit is contained in:
parent
0ec64af67c
commit
e552b5861a
|
|
@ -5,13 +5,30 @@ MinimumVisualStudioVersion = 15.0.26730.03
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5FCB2DA3-5395-47F5-BCEE-E0EA319448EA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{60336AB3-948D-4D15-A5FB-F32A2B91E814}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
test\CreateTestCert.ps1 = test\CreateTestCert.ps1
|
||||
test\Directory.Build.props = test\Directory.Build.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{5A3A5DE3-49AD-431C-971D-B01B62D94AE2}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E1D86B1B-41D8-43C9-97FD-C2BF65C414E2}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.appveyor.yml = .appveyor.yml
|
||||
.gitattributes = .gitattributes
|
||||
.gitignore = .gitignore
|
||||
.travis.yml = .travis.yml
|
||||
CONTRIBUTING.md = CONTRIBUTING.md
|
||||
build\dependencies.props = build\dependencies.props
|
||||
Directory.Build.props = Directory.Build.props
|
||||
Directory.Build.targets = Directory.Build.targets
|
||||
korebuild.json = korebuild.json
|
||||
LICENSE.txt = LICENSE.txt
|
||||
NuGet.config = NuGet.config
|
||||
NuGetPackageVerifier.json = NuGetPackageVerifier.json
|
||||
Provision-AutoGenKeys.ps1 = Provision-AutoGenKeys.ps1
|
||||
README.md = README.md
|
||||
version.props = version.props
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.DataProtection", "src\Microsoft.AspNetCore.DataProtection\Microsoft.AspNetCore.DataProtection.csproj", "{1E570CD4-6F12-44F4-961E-005EE2002BC2}"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core utilities for key derivation.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFrameworks>netstandard2.0;netcoreapp2.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;dataprotection</PackageTags>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if NETCOREAPP2_0
|
||||
// Rfc2898DeriveBytes in .NET Standard 2.0 only supports SHA1
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements Pbkdf2 using <see cref="Rfc2898DeriveBytes"/>.
|
||||
/// </summary>
|
||||
internal sealed class NetCorePbkdf2Provider : IPbkdf2Provider
|
||||
{
|
||||
private static readonly ManagedPbkdf2Provider _fallbackProvider = new ManagedPbkdf2Provider();
|
||||
|
||||
public byte[] DeriveKey(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
|
||||
{
|
||||
Debug.Assert(password != null);
|
||||
Debug.Assert(salt != null);
|
||||
Debug.Assert(iterationCount > 0);
|
||||
Debug.Assert(numBytesRequested > 0);
|
||||
|
||||
if (salt.Length < 8)
|
||||
{
|
||||
// Rfc2898DeriveBytes enforces the 8 byte recommendation.
|
||||
// To maintain compatibility, we call into ManagedPbkdf2Provider for salts shorter than 8 bytes
|
||||
// because we can't use Rfc2898DeriveBytes with this salt.
|
||||
return _fallbackProvider.DeriveKey(password, salt, prf, iterationCount, numBytesRequested);
|
||||
}
|
||||
else
|
||||
{
|
||||
return DeriveKeyImpl(password, salt, prf, iterationCount, numBytesRequested);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] DeriveKeyImpl(string password, byte[] salt, KeyDerivationPrf prf, int iterationCount, int numBytesRequested)
|
||||
{
|
||||
HashAlgorithmName algorithmName;
|
||||
switch (prf)
|
||||
{
|
||||
case KeyDerivationPrf.HMACSHA1:
|
||||
algorithmName = HashAlgorithmName.SHA1;
|
||||
break;
|
||||
case KeyDerivationPrf.HMACSHA256:
|
||||
algorithmName = HashAlgorithmName.SHA256;
|
||||
break;
|
||||
case KeyDerivationPrf.HMACSHA512:
|
||||
algorithmName = HashAlgorithmName.SHA512;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
var passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
using (var rfc = new Rfc2898DeriveBytes(passwordBytes, salt, iterationCount, algorithmName))
|
||||
{
|
||||
return rfc.GetBytes(numBytesRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#elif NETSTANDARD2_0
|
||||
#else
|
||||
#error Update target frameworks
|
||||
#endif
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Cryptography.Cng;
|
||||
|
||||
namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
|
||||
|
|
@ -20,15 +19,28 @@ namespace Microsoft.AspNetCore.Cryptography.KeyDerivation.PBKDF2
|
|||
{
|
||||
// fastest implementation
|
||||
return new Win8Pbkdf2Provider();
|
||||
} else if (OSVersionUtil.IsWindows())
|
||||
}
|
||||
else if (OSVersionUtil.IsWindows())
|
||||
{
|
||||
// acceptable implementation
|
||||
return new Win7Pbkdf2Provider();
|
||||
} else
|
||||
}
|
||||
#if NETCOREAPP2_0
|
||||
else
|
||||
{
|
||||
// fastest implementation on .NET Core for Linux/macOS.
|
||||
// Not supported on .NET Framework
|
||||
return new NetCorePbkdf2Provider();
|
||||
}
|
||||
#elif NETSTANDARD2_0
|
||||
else
|
||||
{
|
||||
// slowest implementation
|
||||
return new ManagedPbkdf2Provider();
|
||||
}
|
||||
#else
|
||||
#error Update target frameworks
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,58 @@ namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
|
|||
{
|
||||
public class Pbkdf2Tests
|
||||
{
|
||||
|
||||
#if NET461
|
||||
#elif NETCOREAPP2_0 || NETCOREAPP2_1
|
||||
// The 'numBytesRequested' parameters below are chosen to exercise code paths where
|
||||
// this value straddles the digest length of the PRF. We only use 5 iterations so
|
||||
// that our unit tests are fast.
|
||||
|
||||
// This provider is only available in .NET Core because .NET Standard only supports HMACSHA1
|
||||
[Theory]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA1, 5, 160 / 8 - 1, "efmxNcKD/U1urTEDGvsThlPnHA==")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA1, 5, 160 / 8 + 0, "efmxNcKD/U1urTEDGvsThlPnHDI=")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA1, 5, 160 / 8 + 1, "efmxNcKD/U1urTEDGvsThlPnHDLk")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA256, 5, 256 / 8 - 1, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRA==")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA256, 5, 256 / 8 + 0, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRLo=")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA256, 5, 256 / 8 + 1, "JRNz8bPKS02EG1vf7eWjA64IeeI+TI8gBEwb1oVvRLpk")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA512, 5, 512 / 8 - 1, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm9")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA512, 5, 512 / 8 + 0, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm90Q==")]
|
||||
[InlineData("my-password", KeyDerivationPrf.HMACSHA512, 5, 512 / 8 + 1, "ZTallQJrFn0279xIzaiA1XqatVTGei+ZjKngA7bIMtKMDUw6YJeGUQpFG8iGTgN+ri3LNDktNbzwfcSyZmm90Wk=")]
|
||||
public void RunTest_Normal_NetCore(string password, KeyDerivationPrf prf, int iterationCount, int numBytesRequested, string expectedValueAsBase64)
|
||||
{
|
||||
// Arrange
|
||||
byte[] salt = new byte[256];
|
||||
for (int i = 0; i < salt.Length; i++)
|
||||
{
|
||||
salt[i] = (byte)i;
|
||||
}
|
||||
|
||||
// Act & assert
|
||||
TestProvider<NetCorePbkdf2Provider>(password, salt, prf, iterationCount, numBytesRequested, expectedValueAsBase64);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunTest_WithLongPassword_NetCore_FallbackToManaged()
|
||||
{
|
||||
// salt is less than 8 bytes
|
||||
byte[] salt = Encoding.UTF8.GetBytes("salt");
|
||||
const string expectedDerivedKeyBase64 = "Sc+V/c3fiZq5Z5qH3iavAiojTsW97FAp2eBNmCQAwCNzA8hfhFFYyQLIMK65qPnBFHOHXQPwAxNQNhaEAH9hzfiaNBSRJpF9V4rpl02d5ZpI6cZbsQFF7TJW7XJzQVpYoPDgJlg0xVmYLhn1E9qMtUVUuXsBjOOdd7K1M+ZI00c=";
|
||||
|
||||
RunTest_WithLongPassword_Impl<NetCorePbkdf2Provider>(salt, expectedDerivedKeyBase64);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunTest_WithLongPassword_NetCore()
|
||||
{
|
||||
// salt longer than 8 bytes
|
||||
var salt = Encoding.UTF8.GetBytes("abcdefghijkl");
|
||||
RunTest_WithLongPassword_Impl<NetCorePbkdf2Provider>(salt, "NGJtFzYUaaSxu+3ZsMeZO5d/qPJDUYW4caLkFlaY0cLSYdh1PN4+nHUVp4pUUubJWu3UeXNMnHKNDfnn8GMfnDVrAGTv1lldszsvUJ0JQ6p4+daQEYBc//Tj/ejuB3luwW0IinyE7U/ViOQKbfi5pCZFMQ0FFx9I+eXRlyT+I74=");
|
||||
}
|
||||
#else
|
||||
#error Update target framework
|
||||
#endif
|
||||
|
||||
// The 'numBytesRequested' parameters below are chosen to exercise code paths where
|
||||
// this value straddles the digest length of the PRF. We only use 5 iterations so
|
||||
// that our unit tests are fast.
|
||||
|
|
@ -115,10 +167,16 @@ namespace Microsoft.AspNetCore.Cryptography.KeyDerivation
|
|||
private static void RunTest_WithLongPassword_Impl<TProvider>()
|
||||
where TProvider : IPbkdf2Provider, new()
|
||||
{
|
||||
// Arrange
|
||||
string password = new String('x', 50000); // 50,000 char password
|
||||
byte[] salt = Encoding.UTF8.GetBytes("salt");
|
||||
const string expectedDerivedKeyBase64 = "Sc+V/c3fiZq5Z5qH3iavAiojTsW97FAp2eBNmCQAwCNzA8hfhFFYyQLIMK65qPnBFHOHXQPwAxNQNhaEAH9hzfiaNBSRJpF9V4rpl02d5ZpI6cZbsQFF7TJW7XJzQVpYoPDgJlg0xVmYLhn1E9qMtUVUuXsBjOOdd7K1M+ZI00c=";
|
||||
RunTest_WithLongPassword_Impl<TProvider>(salt, expectedDerivedKeyBase64);
|
||||
}
|
||||
|
||||
private static void RunTest_WithLongPassword_Impl<TProvider>(byte[] salt, string expectedDerivedKeyBase64)
|
||||
where TProvider : IPbkdf2Provider, new()
|
||||
{
|
||||
// Arrange
|
||||
string password = new String('x', 50000); // 50,000 char password
|
||||
const KeyDerivationPrf prf = KeyDerivationPrf.HMACSHA256;
|
||||
const int iterationCount = 5;
|
||||
const int numBytesRequested = 128;
|
||||
|
|
|
|||
Loading…
Reference in New Issue