Port PasswordsHasher, crypto only in Net45

This commit is contained in:
Hao Kung 2014-03-06 11:54:33 -08:00
parent edb709ce9d
commit 5128a030da
5 changed files with 201 additions and 83 deletions

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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>();
} }

View File

@ -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-*"
}, },

View File

@ -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));