Port PasswordsHasher, crypto only in Net45
This commit is contained in:
parent
edb709ce9d
commit
5128a030da
|
|
@ -1,10 +1,105 @@
|
||||||
#if K10
|
#if NET45
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
//using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Identity
|
namespace Microsoft.AspNet.Identity
|
||||||
{
|
{
|
||||||
|
internal static class Crypto
|
||||||
|
{
|
||||||
|
private const int PBKDF2IterCount = 1000; // default for Rfc2898DeriveBytes
|
||||||
|
private const int PBKDF2SubkeyLength = 256/8; // 256 bits
|
||||||
|
private const int SaltSize = 128/8; // 128 bits
|
||||||
|
|
||||||
|
/* =======================
|
||||||
|
* HASHED PASSWORD FORMATS
|
||||||
|
* =======================
|
||||||
|
*
|
||||||
|
* Version 0:
|
||||||
|
* PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
|
||||||
|
* (See also: SDL crypto guidelines v5.1, Part III)
|
||||||
|
* Format: { 0x00, salt, subkey }
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static string HashPassword(string password)
|
||||||
|
{
|
||||||
|
if (password == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("password");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Produce a version 0 (see comment above) text hash.
|
||||||
|
byte[] salt;
|
||||||
|
byte[] subkey;
|
||||||
|
using (var deriveBytes = new Rfc2898DeriveBytes(password, SaltSize, PBKDF2IterCount))
|
||||||
|
{
|
||||||
|
salt = deriveBytes.Salt;
|
||||||
|
subkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputBytes = new byte[1 + SaltSize + PBKDF2SubkeyLength];
|
||||||
|
Buffer.BlockCopy(salt, 0, outputBytes, 1, SaltSize);
|
||||||
|
Buffer.BlockCopy(subkey, 0, outputBytes, 1 + SaltSize, PBKDF2SubkeyLength);
|
||||||
|
return Convert.ToBase64String(outputBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// hashedPassword must be of the format of HashWithPassword (salt + Hash(salt+input)
|
||||||
|
public static bool VerifyHashedPassword(string hashedPassword, string password)
|
||||||
|
{
|
||||||
|
if (hashedPassword == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (password == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("password");
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashedPasswordBytes = Convert.FromBase64String(hashedPassword);
|
||||||
|
|
||||||
|
// Verify a version 0 (see comment above) text hash.
|
||||||
|
|
||||||
|
if (hashedPasswordBytes.Length != (1 + SaltSize + PBKDF2SubkeyLength) || hashedPasswordBytes[0] != 0x00)
|
||||||
|
{
|
||||||
|
// Wrong length or version header.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var salt = new byte[SaltSize];
|
||||||
|
Buffer.BlockCopy(hashedPasswordBytes, 1, salt, 0, SaltSize);
|
||||||
|
var storedSubkey = new byte[PBKDF2SubkeyLength];
|
||||||
|
Buffer.BlockCopy(hashedPasswordBytes, 1 + SaltSize, storedSubkey, 0, PBKDF2SubkeyLength);
|
||||||
|
|
||||||
|
byte[] generatedSubkey;
|
||||||
|
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt, PBKDF2IterCount))
|
||||||
|
{
|
||||||
|
generatedSubkey = deriveBytes.GetBytes(PBKDF2SubkeyLength);
|
||||||
|
}
|
||||||
|
return ByteArraysEqual(storedSubkey, generatedSubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares two byte arrays for equality. The method is specifically written so that the loop is not optimized.
|
||||||
|
[MethodImpl(MethodImplOptions.NoOptimization)]
|
||||||
|
private static bool ByteArraysEqual(byte[] a, byte[] b)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(a, b))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a == null || b == null || a.Length != b.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var areSame = true;
|
||||||
|
for (var i = 0; i < a.Length; i++)
|
||||||
|
{
|
||||||
|
areSame &= (a[i] == b[i]);
|
||||||
|
}
|
||||||
|
return areSame;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
namespace Microsoft.AspNet.Identity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Implements password hashing methods
|
||||||
|
/// </summary>
|
||||||
|
public class PasswordHasher : IPasswordHasher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Hash a password
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="password"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual string HashPassword(string password)
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
return Crypto.HashPassword(password);
|
||||||
|
#else
|
||||||
|
return password;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that a password matches the hashedPassword
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hashedPassword"></param>
|
||||||
|
/// <param name="providedPassword"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual PasswordVerificationResult VerifyHashedPassword(string hashedPassword, string providedPassword)
|
||||||
|
{
|
||||||
|
#if NET45
|
||||||
|
return Crypto.VerifyHashedPassword(hashedPassword, providedPassword) ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
|
||||||
|
#else
|
||||||
|
return hashedPassword == providedPassword ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Identity
|
||||||
}
|
}
|
||||||
Store = store;
|
Store = store;
|
||||||
UserValidator = new UserValidator<TUser, TKey>(this);
|
UserValidator = new UserValidator<TUser, TKey>(this);
|
||||||
//PasswordHasher = new PasswordHasher();
|
PasswordHasher = new PasswordHasher();
|
||||||
//ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser, TKey>();
|
//ClaimsIdentityFactory = new ClaimsIdentityFactory<TUser, TKey>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"version" : "0.1-alpha-*",
|
"version" : "0.1-alpha-*",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"System.Collections.Immutable" : "1.1.15.0",
|
||||||
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
|
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
|
||||||
"System.Security.Claims" : "0.1-alpha-*"
|
"System.Security.Claims" : "0.1-alpha-*"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if NET45
|
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
#else
|
|
||||||
using System.Security.ClaimsK;
|
|
||||||
#endif
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
@ -53,21 +49,21 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
|
||||||
Assert.Equal(providerKey, logins.First().ProviderKey);
|
Assert.Equal(providerKey, logins.First().ProviderKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public async Task CreateUserLoginAndAddPasswordTest()
|
public async Task CreateUserLoginAndAddPasswordTest()
|
||||||
//{
|
{
|
||||||
// var manager = CreateManager();
|
var manager = CreateManager();
|
||||||
// var login = new UserLoginInfo("Provider", "key");
|
var login = new UserLoginInfo("Provider", "key");
|
||||||
// var user = new InMemoryUser("CreateUserLoginAddPasswordTest");
|
var user = new InMemoryUser("CreateUserLoginAddPasswordTest");
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user));
|
UnitTestHelper.IsSuccess(await manager.Create(user));
|
||||||
// UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
|
UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
|
||||||
// UnitTestHelper.IsSuccess(await manager.AddPassword(user.Id, "password"));
|
UnitTestHelper.IsSuccess(await manager.AddPassword(user.Id, "password"));
|
||||||
// var logins = await manager.GetLogins(user.Id);
|
var logins = await manager.GetLogins(user.Id);
|
||||||
// Assert.NotNull(logins);
|
Assert.NotNull(logins);
|
||||||
// Assert.Equal(1, logins.Count());
|
Assert.Equal(1, logins.Count());
|
||||||
// Assert.Equal(user, await manager.Find(login));
|
Assert.Equal(user, await manager.Find(login));
|
||||||
// Assert.Equal(user, await manager.Find(user.UserName, "password"));
|
Assert.Equal(user, await manager.Find(user.UserName, "password"));
|
||||||
//}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CreateUserAddRemoveLoginTest()
|
public async Task CreateUserAddRemoveLoginTest()
|
||||||
|
|
@ -94,36 +90,36 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
|
||||||
Assert.NotEqual(stamp, user.SecurityStamp);
|
Assert.NotEqual(stamp, user.SecurityStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public async Task RemovePasswordTest()
|
public async Task RemovePasswordTest()
|
||||||
//{
|
{
|
||||||
// var manager = CreateManager();
|
var manager = CreateManager();
|
||||||
// var user = new InMemoryUser("RemovePasswordTest");
|
var user = new InMemoryUser("RemovePasswordTest");
|
||||||
// const string password = "password";
|
const string password = "password";
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user, password));
|
UnitTestHelper.IsSuccess(await manager.Create(user, password));
|
||||||
// var stamp = user.SecurityStamp;
|
var stamp = user.SecurityStamp;
|
||||||
// UnitTestHelper.IsSuccess(await manager.RemovePassword(user.Id));
|
UnitTestHelper.IsSuccess(await manager.RemovePassword(user.Id));
|
||||||
// var u = await manager.FindByName(user.UserName);
|
var u = await manager.FindByName(user.UserName);
|
||||||
// Assert.NotNull(u);
|
Assert.NotNull(u);
|
||||||
// Assert.Null(u.PasswordHash);
|
Assert.Null(u.PasswordHash);
|
||||||
// Assert.NotEqual(stamp, user.SecurityStamp);
|
Assert.NotEqual(stamp, user.SecurityStamp);
|
||||||
//}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public async Task ChangePasswordTest()
|
public async Task ChangePasswordTest()
|
||||||
//{
|
{
|
||||||
// var manager = CreateManager();
|
var manager = CreateManager();
|
||||||
// var user = new InMemoryUser("ChangePasswordTest");
|
var user = new InMemoryUser("ChangePasswordTest");
|
||||||
// const string password = "password";
|
const string password = "password";
|
||||||
// const string newPassword = "newpassword";
|
const string newPassword = "newpassword";
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user, password));
|
UnitTestHelper.IsSuccess(await manager.Create(user, password));
|
||||||
// var stamp = user.SecurityStamp;
|
var stamp = user.SecurityStamp;
|
||||||
// Assert.NotNull(stamp);
|
Assert.NotNull(stamp);
|
||||||
// UnitTestHelper.IsSuccess(await manager.ChangePassword(user.Id, password, newPassword));
|
UnitTestHelper.IsSuccess(await manager.ChangePassword(user.Id, password, newPassword));
|
||||||
// Assert.Null(await manager.Find(user.UserName, password));
|
Assert.Null(await manager.Find(user.UserName, password));
|
||||||
// Assert.Equal(user, await manager.Find(user.UserName, newPassword));
|
Assert.Equal(user, await manager.Find(user.UserName, newPassword));
|
||||||
// Assert.NotEqual(stamp, user.SecurityStamp);
|
Assert.NotEqual(stamp, user.SecurityStamp);
|
||||||
//}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AddRemoveUserClaimTest()
|
public async Task AddRemoveUserClaimTest()
|
||||||
|
|
@ -149,26 +145,15 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
|
||||||
Assert.Equal(0, userClaims.Count);
|
Assert.Equal(0, userClaims.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public async Task ChangePasswordFallsIfPasswordTooShortTest()
|
public async Task ChangePasswordFallsIfPasswordWrongTest()
|
||||||
//{
|
{
|
||||||
// var manager = CreateManager();
|
var manager = CreateManager();
|
||||||
// var user = new InMemoryUser("user");
|
var user = new InMemoryUser("user");
|
||||||
// const string password = "password";
|
UnitTestHelper.IsSuccess(await manager.Create(user, "password"));
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user, password));
|
var result = await manager.ChangePassword(user.Id, "bogus", "newpassword");
|
||||||
// var result = await manager.ChangePassword(user.Id, password, "n");
|
UnitTestHelper.IsFailure(result, "Incorrect password.");
|
||||||
// UnitTestHelper.IsFailure(result, "Passwords must be at least 6 characters.");
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
//[Fact]
|
|
||||||
//public async Task ChangePasswordFallsIfPasswordWrongTest()
|
|
||||||
//{
|
|
||||||
// var manager = CreateManager();
|
|
||||||
// var user = new InMemoryUser("user");
|
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user, "password"));
|
|
||||||
// var result = await manager.ChangePassword(user.Id, "bogus", "newpassword");
|
|
||||||
// UnitTestHelper.IsFailure(result, "Incorrect password.");
|
|
||||||
//}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AddDupeUserFailsTest()
|
public async Task AddDupeUserFailsTest()
|
||||||
|
|
@ -193,17 +178,17 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
|
||||||
Assert.NotEqual(stamp, user.SecurityStamp);
|
Assert.NotEqual(stamp, user.SecurityStamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Fact]
|
[Fact]
|
||||||
//public async Task AddDupeLoginFailsTest()
|
public async Task AddDupeLoginFailsTest()
|
||||||
//{
|
{
|
||||||
// var manager = CreateManager();
|
var manager = CreateManager();
|
||||||
// var user = new InMemoryUser("DupeLogin");
|
var user = new InMemoryUser("DupeLogin");
|
||||||
// var login = new UserLoginInfo("provder", "key");
|
var login = new UserLoginInfo("provder", "key");
|
||||||
// UnitTestHelper.IsSuccess(await manager.Create(user));
|
UnitTestHelper.IsSuccess(await manager.Create(user));
|
||||||
// UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
|
UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
|
||||||
// var result = await manager.AddLogin(user.Id, login);
|
var result = await manager.AddLogin(user.Id, login);
|
||||||
// UnitTestHelper.IsFailure(result, "A user with that external login already exists.");
|
UnitTestHelper.IsFailure(result, "A user with that external login already exists.");
|
||||||
//}
|
}
|
||||||
|
|
||||||
// Lockout tests
|
// Lockout tests
|
||||||
|
|
||||||
|
|
@ -533,7 +518,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
|
||||||
new InMemoryUser("1"), new InMemoryUser("2"), new InMemoryUser("3"),
|
new InMemoryUser("1"), new InMemoryUser("2"), new InMemoryUser("3"),
|
||||||
new InMemoryUser("4")
|
new InMemoryUser("4")
|
||||||
};
|
};
|
||||||
foreach (InMemoryUser u in users)
|
foreach (var u in users)
|
||||||
{
|
{
|
||||||
UnitTestHelper.IsSuccess(await manager.Create(u));
|
UnitTestHelper.IsSuccess(await manager.Create(u));
|
||||||
UnitTestHelper.IsSuccess(await manager.AddToRole(u.Id, role.Name));
|
UnitTestHelper.IsSuccess(await manager.AddToRole(u.Id, role.Name));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue