#21036: User password hashes are now created with the PBKDF2 algorithm.

Work Item: 21036
This commit is contained in:
Lombiq
2015-01-17 22:42:40 +01:00
committed by Zoltán Lehóczky
parent af40271734
commit 5b74c20553
3 changed files with 58 additions and 23 deletions

View File

@@ -41,6 +41,7 @@ using Orchard.Core.Settings.Handlers;
using System.Collections.Specialized;
using Orchard.Mvc;
using Orchard.Tests.Modules.Stubs;
using Orchard.Environment.Configuration;
namespace Orchard.Tests.Modules.Users.Controllers {
[TestFixture]
@@ -63,6 +64,7 @@ namespace Orchard.Tests.Modules.Users.Controllers {
builder.RegisterInstance(new Mock<IAuthenticationService>().Object);
builder.RegisterInstance(new Mock<IUserEventHandler>().Object);
builder.RegisterInstance(new Mock<IAppConfigurationAccessor>().Object);
builder.RegisterType<MembershipService>().As<IMembershipService>();
builder.RegisterType<DefaultMessageService>().As<IMessageService>();
builder.RegisterInstance(new MessageChannelSelectorStub(_channel)).As<IMessageChannelSelector>();

View File

@@ -59,6 +59,10 @@
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Entity" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Helpers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Helpers.dll</HintPath>
</Reference>
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>

View File

@@ -14,16 +14,21 @@ using Orchard.Users.Models;
using Orchard.Messaging.Services;
using System.Collections.Generic;
using Orchard.Services;
using System.Web.Helpers;
using Orchard.Environment.Configuration;
namespace Orchard.Users.Services {
[UsedImplicitly]
public class MembershipService : IMembershipService {
private const string Pkdf2 = "PBKDF2"; // To avoid typos.
private readonly IOrchardServices _orchardServices;
private readonly IMessageService _messageService;
private readonly IUserEventHandler _userEventHandlers;
private readonly IEncryptionService _encryptionService;
private readonly IShapeFactory _shapeFactory;
private readonly IShapeDisplay _shapeDisplay;
private readonly IAppConfigurationAccessor _appConfigurationAccessor;
public MembershipService(
IOrchardServices orchardServices,
@@ -32,13 +37,15 @@ namespace Orchard.Users.Services {
IClock clock,
IEncryptionService encryptionService,
IShapeFactory shapeFactory,
IShapeDisplay shapeDisplay) {
IShapeDisplay shapeDisplay,
IAppConfigurationAccessor appConfigurationAccessor) {
_orchardServices = orchardServices;
_messageService = messageService;
_userEventHandlers = userEventHandlers;
_encryptionService = encryptionService;
_shapeFactory = shapeFactory;
_shapeDisplay = shapeDisplay;
_appConfigurationAccessor = appConfigurationAccessor;
Logger = NullLogger.Instance;
T = NullLocalizer.Instance;
}
@@ -62,7 +69,7 @@ namespace Orchard.Users.Services {
user.UserName = createUserParams.Username;
user.Email = createUserParams.Email;
user.NormalizedUserName = createUserParams.Username.ToLowerInvariant();
user.HashAlgorithm = "SHA1";
user.HashAlgorithm = Pkdf2;
SetPassword(user, createUserParams.Password);
if ( registrationSettings != null ) {
@@ -194,40 +201,62 @@ namespace Orchard.Users.Services {
}
private static void SetPasswordHashed(UserPart userPart, string password) {
var saltBytes = new byte[0x10];
using (var random = new RNGCryptoServiceProvider()) {
random.GetBytes(saltBytes);
}
var passwordBytes = Encoding.Unicode.GetBytes(password);
var combinedBytes = saltBytes.Concat(passwordBytes).ToArray();
byte[] hashBytes;
using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) {
hashBytes = hashAlgorithm.ComputeHash(combinedBytes);
}
userPart.PasswordFormat = MembershipPasswordFormat.Hashed;
userPart.Password = Convert.ToBase64String(hashBytes);
userPart.Password = ComputeHashBase64(userPart.HashAlgorithm, saltBytes, password);
userPart.PasswordSalt = Convert.ToBase64String(saltBytes);
}
private static bool ValidatePasswordHashed(UserPart userPart, string password) {
private bool ValidatePasswordHashed(UserPart userPart, string password) {
var saltBytes = Convert.FromBase64String(userPart.PasswordSalt);
var passwordBytes = Encoding.Unicode.GetBytes(password);
var combinedBytes = saltBytes.Concat(passwordBytes).ToArray();
byte[] hashBytes;
using (var hashAlgorithm = HashAlgorithm.Create(userPart.HashAlgorithm)) {
hashBytes = hashAlgorithm.ComputeHash(combinedBytes);
bool isValid;
if (IsPbkdf2(userPart.HashAlgorithm)) {
// Due to the internally generated salt repeated calls to Crypto.HashPassword() return different results.
isValid = Crypto.VerifyHashedPassword(userPart.Password, Encoding.Unicode.GetString(CombineSaltAndPassword(saltBytes, password)));
}
else {
isValid = userPart.Password == ComputeHashBase64(userPart.HashAlgorithm, saltBytes, password);
}
return userPart.Password == Convert.ToBase64String(hashBytes);
// Migrating older password hashes to PBKDF2 if necessary and enabled.
if (isValid && !IsPbkdf2(userPart.HashAlgorithm)) {
var keepSha1Configuration = _appConfigurationAccessor.GetConfiguration("Orchard.Users.KeepOldPasswordHash");
if (String.IsNullOrEmpty(keepSha1Configuration) || keepSha1Configuration.Equals("false", StringComparison.InvariantCultureIgnoreCase)) {
userPart.HashAlgorithm = Pkdf2;
userPart.Password = ComputeHashBase64(userPart.HashAlgorithm, saltBytes, password);
}
}
return isValid;
}
private static string ComputeHashBase64(string hashAlgorithmName, byte[] saltBytes, string password) {
var combinedBytes = CombineSaltAndPassword(saltBytes, password);
// Extending HashAlgorithm would be too complicated: http://stackoverflow.com/questions/6460711/adding-a-custom-hashalgorithmtype-in-c-sharp-asp-net?lq=1
if (IsPbkdf2(hashAlgorithmName)) {
// HashPassword() already returns a base64 string.
return Crypto.HashPassword(Encoding.Unicode.GetString(combinedBytes));
}
else {
using (var hashAlgorithm = HashAlgorithm.Create(hashAlgorithmName)) {
return Convert.ToBase64String(hashAlgorithm.ComputeHash(combinedBytes));
}
}
}
private static byte[] CombineSaltAndPassword(byte[] saltBytes, string password) {
var passwordBytes = Encoding.Unicode.GetBytes(password);
return saltBytes.Concat(passwordBytes).ToArray();
}
private static bool IsPbkdf2(string hashAlgorithmName) {
return hashAlgorithmName == Pkdf2;
}
private void SetPasswordEncrypted(UserPart userPart, string password) {