diff --git a/src/Microsoft.AspNet.Identity/Crypto.cs b/src/Microsoft.AspNet.Identity/Crypto.cs
index 86e26060..520f2abb 100644
--- a/src/Microsoft.AspNet.Identity/Crypto.cs
+++ b/src/Microsoft.AspNet.Identity/Crypto.cs
@@ -1,10 +1,105 @@
-#if K10
+#if NET45
using System;
using System.Runtime.CompilerServices;
-//using System.Security.Cryptography;
+using System.Security.Cryptography;
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
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/PasswordHasher.cs b/src/Microsoft.AspNet.Identity/PasswordHasher.cs
new file mode 100644
index 00000000..252e8150
--- /dev/null
+++ b/src/Microsoft.AspNet.Identity/PasswordHasher.cs
@@ -0,0 +1,37 @@
+namespace Microsoft.AspNet.Identity
+{
+ ///
+ /// Implements password hashing methods
+ ///
+ public class PasswordHasher : IPasswordHasher
+ {
+ ///
+ /// Hash a password
+ ///
+ ///
+ ///
+ public virtual string HashPassword(string password)
+ {
+#if NET45
+ return Crypto.HashPassword(password);
+#else
+ return password;
+#endif
+ }
+
+ ///
+ /// Verify that a password matches the hashedPassword
+ ///
+ ///
+ ///
+ ///
+ 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Identity/UserManager.cs b/src/Microsoft.AspNet.Identity/UserManager.cs
index 7535483f..b8768dca 100644
--- a/src/Microsoft.AspNet.Identity/UserManager.cs
+++ b/src/Microsoft.AspNet.Identity/UserManager.cs
@@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Identity
}
Store = store;
UserValidator = new UserValidator(this);
- //PasswordHasher = new PasswordHasher();
+ PasswordHasher = new PasswordHasher();
//ClaimsIdentityFactory = new ClaimsIdentityFactory();
}
diff --git a/src/Microsoft.AspNet.Identity/project.json b/src/Microsoft.AspNet.Identity/project.json
index 1c1f0c09..de269b57 100644
--- a/src/Microsoft.AspNet.Identity/project.json
+++ b/src/Microsoft.AspNet.Identity/project.json
@@ -1,6 +1,7 @@
{
"version" : "0.1-alpha-*",
"dependencies": {
+ "System.Collections.Immutable" : "1.1.15.0",
"Microsoft.AspNet.DependencyInjection" : "0.1-alpha-*",
"System.Security.Claims" : "0.1-alpha-*"
},
diff --git a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStoreTest.cs b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStoreTest.cs
index 1e889bbc..d23ebd5e 100644
--- a/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStoreTest.cs
+++ b/test/Microsoft.AspNet.Identity.InMemory.Test/InMemoryUserStoreTest.cs
@@ -1,10 +1,6 @@
using System;
using System.Linq;
-#if NET45
using System.Security.Claims;
-#else
-using System.Security.ClaimsK;
-#endif
using System.Threading.Tasks;
using Xunit;
@@ -53,21 +49,21 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.Equal(providerKey, logins.First().ProviderKey);
}
- //[Fact]
- //public async Task CreateUserLoginAndAddPasswordTest()
- //{
- // var manager = CreateManager();
- // var login = new UserLoginInfo("Provider", "key");
- // var user = new InMemoryUser("CreateUserLoginAddPasswordTest");
- // UnitTestHelper.IsSuccess(await manager.Create(user));
- // UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
- // UnitTestHelper.IsSuccess(await manager.AddPassword(user.Id, "password"));
- // var logins = await manager.GetLogins(user.Id);
- // Assert.NotNull(logins);
- // Assert.Equal(1, logins.Count());
- // Assert.Equal(user, await manager.Find(login));
- // Assert.Equal(user, await manager.Find(user.UserName, "password"));
- //}
+ [Fact]
+ public async Task CreateUserLoginAndAddPasswordTest()
+ {
+ var manager = CreateManager();
+ var login = new UserLoginInfo("Provider", "key");
+ var user = new InMemoryUser("CreateUserLoginAddPasswordTest");
+ UnitTestHelper.IsSuccess(await manager.Create(user));
+ UnitTestHelper.IsSuccess(await manager.AddLogin(user.Id, login));
+ UnitTestHelper.IsSuccess(await manager.AddPassword(user.Id, "password"));
+ var logins = await manager.GetLogins(user.Id);
+ Assert.NotNull(logins);
+ Assert.Equal(1, logins.Count());
+ Assert.Equal(user, await manager.Find(login));
+ Assert.Equal(user, await manager.Find(user.UserName, "password"));
+ }
[Fact]
public async Task CreateUserAddRemoveLoginTest()
@@ -94,36 +90,36 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.NotEqual(stamp, user.SecurityStamp);
}
- //[Fact]
- //public async Task RemovePasswordTest()
- //{
- // var manager = CreateManager();
- // var user = new InMemoryUser("RemovePasswordTest");
- // const string password = "password";
- // UnitTestHelper.IsSuccess(await manager.Create(user, password));
- // var stamp = user.SecurityStamp;
- // UnitTestHelper.IsSuccess(await manager.RemovePassword(user.Id));
- // var u = await manager.FindByName(user.UserName);
- // Assert.NotNull(u);
- // Assert.Null(u.PasswordHash);
- // Assert.NotEqual(stamp, user.SecurityStamp);
- //}
+ [Fact]
+ public async Task RemovePasswordTest()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("RemovePasswordTest");
+ const string password = "password";
+ UnitTestHelper.IsSuccess(await manager.Create(user, password));
+ var stamp = user.SecurityStamp;
+ UnitTestHelper.IsSuccess(await manager.RemovePassword(user.Id));
+ var u = await manager.FindByName(user.UserName);
+ Assert.NotNull(u);
+ Assert.Null(u.PasswordHash);
+ Assert.NotEqual(stamp, user.SecurityStamp);
+ }
- //[Fact]
- //public async Task ChangePasswordTest()
- //{
- // var manager = CreateManager();
- // var user = new InMemoryUser("ChangePasswordTest");
- // const string password = "password";
- // const string newPassword = "newpassword";
- // UnitTestHelper.IsSuccess(await manager.Create(user, password));
- // var stamp = user.SecurityStamp;
- // Assert.NotNull(stamp);
- // UnitTestHelper.IsSuccess(await manager.ChangePassword(user.Id, password, newPassword));
- // Assert.Null(await manager.Find(user.UserName, password));
- // Assert.Equal(user, await manager.Find(user.UserName, newPassword));
- // Assert.NotEqual(stamp, user.SecurityStamp);
- //}
+ [Fact]
+ public async Task ChangePasswordTest()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("ChangePasswordTest");
+ const string password = "password";
+ const string newPassword = "newpassword";
+ UnitTestHelper.IsSuccess(await manager.Create(user, password));
+ var stamp = user.SecurityStamp;
+ Assert.NotNull(stamp);
+ UnitTestHelper.IsSuccess(await manager.ChangePassword(user.Id, password, newPassword));
+ Assert.Null(await manager.Find(user.UserName, password));
+ Assert.Equal(user, await manager.Find(user.UserName, newPassword));
+ Assert.NotEqual(stamp, user.SecurityStamp);
+ }
[Fact]
public async Task AddRemoveUserClaimTest()
@@ -149,26 +145,15 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.Equal(0, userClaims.Count);
}
- //[Fact]
- //public async Task ChangePasswordFallsIfPasswordTooShortTest()
- //{
- // var manager = CreateManager();
- // var user = new InMemoryUser("user");
- // const string password = "password";
- // UnitTestHelper.IsSuccess(await manager.Create(user, password));
- // var result = await manager.ChangePassword(user.Id, password, "n");
- // 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]
+ 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]
public async Task AddDupeUserFailsTest()
@@ -193,17 +178,17 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
Assert.NotEqual(stamp, user.SecurityStamp);
}
- //[Fact]
- //public async Task AddDupeLoginFailsTest()
- //{
- // var manager = CreateManager();
- // var user = new InMemoryUser("DupeLogin");
- // var login = new UserLoginInfo("provder", "key");
- // UnitTestHelper.IsSuccess(await manager.Create(user));
- // UnitTestHelper.IsSuccess(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.");
- //}
+ [Fact]
+ public async Task AddDupeLoginFailsTest()
+ {
+ var manager = CreateManager();
+ var user = new InMemoryUser("DupeLogin");
+ var login = new UserLoginInfo("provder", "key");
+ UnitTestHelper.IsSuccess(await manager.Create(user));
+ UnitTestHelper.IsSuccess(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.");
+ }
// Lockout tests
@@ -533,7 +518,7 @@ namespace Microsoft.AspNet.Identity.InMemory.Test
new InMemoryUser("1"), new InMemoryUser("2"), new InMemoryUser("3"),
new InMemoryUser("4")
};
- foreach (InMemoryUser u in users)
+ foreach (var u in users)
{
UnitTestHelper.IsSuccess(await manager.Create(u));
UnitTestHelper.IsSuccess(await manager.AddToRole(u.Id, role.Name));