Allow the application developer to specify the iteration count for membership passwords.
This commit is contained in:
parent
8672bd7797
commit
bf8728bec9
|
|
@ -30,6 +30,7 @@ namespace Microsoft.AspNet.Identity
|
|||
*/
|
||||
|
||||
private readonly PasswordHasherCompatibilityMode _compatibilityMode;
|
||||
private readonly int _iterCount;
|
||||
private readonly RandomNumberGenerator _rng;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -44,11 +45,24 @@ namespace Microsoft.AspNet.Identity
|
|||
}
|
||||
|
||||
_compatibilityMode = options.Options.CompatibilityMode;
|
||||
if (!IsValidCompatibilityMode(_compatibilityMode))
|
||||
switch (_compatibilityMode)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode);
|
||||
}
|
||||
case PasswordHasherCompatibilityMode.IdentityV2:
|
||||
// nothing else to do
|
||||
break;
|
||||
|
||||
case PasswordHasherCompatibilityMode.IdentityV3:
|
||||
_iterCount = options.Options.IterationCount;
|
||||
if (_iterCount < 1)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.InvalidPasswordHasherIterationCount);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(Resources.InvalidPasswordHasherCompatibilityMode);
|
||||
}
|
||||
|
||||
_rng = options.Options.Rng;
|
||||
}
|
||||
|
||||
|
|
@ -114,11 +128,11 @@ namespace Microsoft.AspNet.Identity
|
|||
return outputBytes;
|
||||
}
|
||||
|
||||
private static byte[] HashPasswordV3(string password, RandomNumberGenerator rng)
|
||||
private byte[] HashPasswordV3(string password, RandomNumberGenerator rng)
|
||||
{
|
||||
return HashPasswordV3(password, rng,
|
||||
prf: KeyDerivationPrf.Sha256,
|
||||
iterCount: 10000,
|
||||
iterCount: _iterCount,
|
||||
saltSize: 128 / 8,
|
||||
numBytesRequested: 256 / 8);
|
||||
}
|
||||
|
|
@ -140,11 +154,6 @@ namespace Microsoft.AspNet.Identity
|
|||
return outputBytes;
|
||||
}
|
||||
|
||||
private static bool IsValidCompatibilityMode(PasswordHasherCompatibilityMode compatibilityMode)
|
||||
{
|
||||
return (compatibilityMode == PasswordHasherCompatibilityMode.IdentityV2 || compatibilityMode == PasswordHasherCompatibilityMode.IdentityV3);
|
||||
}
|
||||
|
||||
private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
|
||||
{
|
||||
return ((uint)(buffer[offset + 0]) << 24)
|
||||
|
|
@ -194,9 +203,18 @@ namespace Microsoft.AspNet.Identity
|
|||
}
|
||||
|
||||
case 0x01:
|
||||
return VerifyHashedPasswordV3(decodedHashedPassword, providedPassword)
|
||||
? PasswordVerificationResult.Success
|
||||
: PasswordVerificationResult.Failed;
|
||||
int embeddedIterCount;
|
||||
if (VerifyHashedPasswordV3(decodedHashedPassword, providedPassword, out embeddedIterCount))
|
||||
{
|
||||
// If this hasher was configured with a higher iteration count, change the entry now.
|
||||
return (embeddedIterCount < _iterCount)
|
||||
? PasswordVerificationResult.SuccessRehashNeeded
|
||||
: PasswordVerificationResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PasswordVerificationResult.Failed;
|
||||
}
|
||||
|
||||
default:
|
||||
return PasswordVerificationResult.Failed; // unknown format marker
|
||||
|
|
@ -227,13 +245,15 @@ namespace Microsoft.AspNet.Identity
|
|||
return ByteArraysEqual(actualSubkey, expectedSubkey);
|
||||
}
|
||||
|
||||
private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password)
|
||||
private static bool VerifyHashedPasswordV3(byte[] hashedPassword, string password, out int iterCount)
|
||||
{
|
||||
iterCount = default(int);
|
||||
|
||||
try
|
||||
{
|
||||
// Read header information
|
||||
KeyDerivationPrf prf = (KeyDerivationPrf)ReadNetworkByteOrder(hashedPassword, 1);
|
||||
int iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5);
|
||||
iterCount = (int)ReadNetworkByteOrder(hashedPassword, 5);
|
||||
int saltLength = (int)ReadNetworkByteOrder(hashedPassword, 9);
|
||||
|
||||
// Read the salt: must be >= 128 bits
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNet.Security.DataProtection;
|
||||
|
||||
namespace Microsoft.AspNet.Identity
|
||||
{
|
||||
|
|
@ -20,7 +21,16 @@ namespace Microsoft.AspNet.Identity
|
|||
/// </remarks>
|
||||
public PasswordHasherCompatibilityMode CompatibilityMode { get; set; } = PasswordHasherCompatibilityMode.IdentityV3;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the number of iterations to use when hashing passwords using PBKDF2.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This value is only used when the compatibiliy mode is set to 'V3'.
|
||||
/// The value must be a positive integer. The default value is 10,000.
|
||||
/// </remarks>
|
||||
public int IterationCount { get; set; } = 10000;
|
||||
|
||||
// for unit testing
|
||||
internal RandomNumberGenerator Rng { get; set; } = _defaultRng;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,22 @@ namespace Microsoft.AspNet.Identity
|
|||
return GetString("InvalidPasswordHasherCompatibilityMode");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The iteration count must be a positive integer.
|
||||
/// </summary>
|
||||
internal static string InvalidPasswordHasherIterationCount
|
||||
{
|
||||
get { return GetString("InvalidPasswordHasherIterationCount"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The iteration count must be a positive integer.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidPasswordHasherIterationCount()
|
||||
{
|
||||
return GetString("InvalidPasswordHasherIterationCount");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid token.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -165,6 +165,10 @@
|
|||
<value>The provided PasswordHasherCompatibilityMode is invalid.</value>
|
||||
<comment>Error when the password hasher doesn't understand the format it's being asked to produce.</comment>
|
||||
</data>
|
||||
<data name="InvalidPasswordHasherIterationCount" xml:space="preserve">
|
||||
<value>The iteration count must be a positive integer.</value>
|
||||
<comment>Error when the iteration count is < 1.</comment>
|
||||
</data>
|
||||
<data name="InvalidToken" xml:space="preserve">
|
||||
<value>Invalid token.</value>
|
||||
<comment>Error when a token is not recognized</comment>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,19 @@ namespace Microsoft.AspNet.Identity.Test
|
|||
Assert.Equal("The provided PasswordHasherCompatibilityMode is invalid.", ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
public void Ctor_InvalidIterCount_Throws(int iterCount)
|
||||
{
|
||||
// Act & assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
new PasswordHasher(iterCount: iterCount);
|
||||
});
|
||||
Assert.Equal("The iteration count must be a positive integer.", ex.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PasswordHasherCompatibilityMode.IdentityV2)]
|
||||
[InlineData(PasswordHasherCompatibilityMode.IdentityV3)]
|
||||
|
|
@ -123,9 +136,9 @@ namespace Microsoft.AspNet.Identity.Test
|
|||
// Version 2 payloads
|
||||
[InlineData("ANXrDknc7fGPpigibZXXZFMX4aoqz44JveK6jQuwY3eH/UyPhvr5xTPeGYEckLxz9A==", PasswordVerificationResult.SuccessRehashNeeded)] // SHA1, 1000 iterations, 128-bit salt, 256-bit subkey
|
||||
// Version 3 payloads
|
||||
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4B6pZWND6zgESBuWiHw", PasswordVerificationResult.Success)] // SHA512, 50 iterations, 128-bit salt, 128-bit subkey
|
||||
[InlineData("AQAAAAIAAAD6AAAAIJbVi5wbMR+htSfFp8fTw8N8GOS/Sje+S/4YZcgBfU7EQuqv4OkVYmc4VJl9AGZzmRTxSkP7LtVi9IWyUxX8IAAfZ8v+ZfhjCcudtC1YERSqE1OEdXLW9VukPuJWBBjLuw==", PasswordVerificationResult.Success)] // SHA512, 250 iterations, 256-bit salt, 512-bit subkey
|
||||
[InlineData("AQAAAAAAAAD6AAAAEAhftMyfTJylOlZT+eEotFXd1elee8ih5WsjXaR3PA9M", PasswordVerificationResult.Success)] // SHA1, 250 iterations, 128-bit salt, 128-bit subkey
|
||||
[InlineData("AQAAAAIAAAAyAAAAEOMwvh3+FZxqkdMBz2ekgGhwQ4B6pZWND6zgESBuWiHw", PasswordVerificationResult.SuccessRehashNeeded)] // SHA512, 50 iterations, 128-bit salt, 128-bit subkey
|
||||
[InlineData("AQAAAAIAAAD6AAAAIJbVi5wbMR+htSfFp8fTw8N8GOS/Sje+S/4YZcgBfU7EQuqv4OkVYmc4VJl9AGZzmRTxSkP7LtVi9IWyUxX8IAAfZ8v+ZfhjCcudtC1YERSqE1OEdXLW9VukPuJWBBjLuw==", PasswordVerificationResult.SuccessRehashNeeded)] // SHA512, 250 iterations, 256-bit salt, 512-bit subkey
|
||||
[InlineData("AQAAAAAAAAD6AAAAEAhftMyfTJylOlZT+eEotFXd1elee8ih5WsjXaR3PA9M", PasswordVerificationResult.SuccessRehashNeeded)] // SHA1, 250 iterations, 128-bit salt, 128-bit subkey
|
||||
[InlineData("AQAAAAEAA9CQAAAAIESkQuj2Du8Y+kbc5lcN/W/3NiAZFEm11P27nrSN5/tId+bR1SwV8CO1Jd72r4C08OLvplNlCDc3oQZ8efcW+jQ=", PasswordVerificationResult.Success)] // SHA256, 250000 iterations, 256-bit salt, 256-bit subkey
|
||||
public void VerifyHashedPassword_Version3CompatMode_SuccessCases(string hashedPassword, PasswordVerificationResult expectedResult)
|
||||
{
|
||||
|
|
@ -141,18 +154,22 @@ namespace Microsoft.AspNet.Identity.Test
|
|||
|
||||
private sealed class PasswordHasher : PasswordHasher<object>
|
||||
{
|
||||
public PasswordHasher(PasswordHasherCompatibilityMode? compatMode = null)
|
||||
: base(BuildOptions(compatMode))
|
||||
public PasswordHasher(PasswordHasherCompatibilityMode? compatMode = null, int? iterCount = null)
|
||||
: base(BuildOptions(compatMode, iterCount))
|
||||
{
|
||||
}
|
||||
|
||||
private static IOptions<PasswordHasherOptions> BuildOptions(PasswordHasherCompatibilityMode? compatMode)
|
||||
private static IOptions<PasswordHasherOptions> BuildOptions(PasswordHasherCompatibilityMode? compatMode, int? iterCount)
|
||||
{
|
||||
var options = new PasswordHasherOptionsAccessor();
|
||||
if (compatMode != null)
|
||||
{
|
||||
options.Options.CompatibilityMode = (PasswordHasherCompatibilityMode)compatMode;
|
||||
}
|
||||
if (iterCount != null)
|
||||
{
|
||||
options.Options.IterationCount = (int)iterCount;
|
||||
}
|
||||
Assert.NotNull(options.Options.Rng); // should have a default value
|
||||
options.Options.Rng = new SequentialRandomNumberGenerator();
|
||||
return options;
|
||||
|
|
|
|||
Loading…
Reference in New Issue