Skip to content

Commit ba25eb1

Browse files
committed
Added support for SuccessRehashNeeded. Updated default work factor to 11
1 parent f471694 commit ba25eb1

File tree

5 files changed

+106
-45
lines changed

5 files changed

+106
-45
lines changed

README.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
1-
# BCrypt Password Hasher for ASP.NET Core Identity (ASP.NET Identity 3)
1+
# BCrypt Password Hasher for ASP.NET Core Identity
22

33
[![NuGet](https://img.shields.io/nuget/v/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher.svg)](https://www.nuget.org/packages/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher/)
44

5-
An implementation of IPasswordHasher<TUser> using [BCrypt.NET - next](https://github.com/BcryptNet/bcrypt.net).
5+
An implementation of `IPasswordHasher<TUser>` using [BCrypt.NET - next](https://github.com/BcryptNet/bcrypt.net).
66

77
## Installation
88

9-
```
9+
```csharp
1010
services.AddIdentity<TUser, TRole>();
1111
services.AddScoped<IPasswordHasher<TUser>, BCryptPasswordHasher<TUser>>();
1212
```
1313

1414
### Options
1515

16-
- **WorkFactor**: int
17-
- **EnhancedEntropy**: bool
16+
- **WorkFactor**: int
17+
- **EnhancedEntropy**: bool
1818

1919
Register with:
20-
```
20+
21+
```csharp
2122
services.Configure<BCryptPasswordHasherOptions>(options => {
22-
options.WorkFactor = 10;
23-
options.EnhancedEntropy = false;
23+
options.WorkFactor = 11;
24+
options.EnhancedEntropy = false;
2425
});
2526
```
2627

src/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher/BCryptPasswordHasher.cs

+8-3
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@ public BCryptPasswordHasher(IOptions<BCryptPasswordHasherOptions> optionsAccesso
1515

1616
public virtual string HashPassword(TUser user, string password)
1717
{
18-
if (password == null) throw new ArgumentNullException(nameof(password));
18+
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentNullException(nameof(password));
1919

2020
return BCrypt.Net.BCrypt.HashPassword(password, options.WorkFactor, options.EnhancedEntropy);
2121
}
2222

2323
public virtual PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
2424
{
25-
if (hashedPassword == null) throw new ArgumentNullException(nameof(hashedPassword));
26-
if (providedPassword == null) throw new ArgumentNullException(nameof(providedPassword));
25+
if (string.IsNullOrWhiteSpace(hashedPassword)) throw new ArgumentNullException(nameof(hashedPassword));
26+
if (string.IsNullOrWhiteSpace(providedPassword)) throw new ArgumentNullException(nameof(providedPassword));
2727

2828
var isValid = BCrypt.Net.BCrypt.Verify(providedPassword, hashedPassword, options.EnhancedEntropy);
2929

30+
if (isValid && BCrypt.Net.BCrypt.PasswordNeedsRehash(hashedPassword, options.WorkFactor))
31+
{
32+
return PasswordVerificationResult.SuccessRehashNeeded;
33+
}
34+
3035
return isValid ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed;
3136
}
3237
}

src/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher/BCryptPasswordHasherOptions.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22
{
33
public class BCryptPasswordHasherOptions
44
{
5-
public int WorkFactor { get; set; } = 10;
5+
/// <summary>
6+
/// The log2 of the number of rounds of hashing to apply. Defaults to 11
7+
/// </summary>
8+
public int WorkFactor { get; set; } = 11;
9+
10+
/// <summary>
11+
/// Enables the use of SHA384 hashing prior to bcrypt hashing. Defaults to false
12+
/// </summary>
613
public bool EnhancedEntropy { get; set; } = false;
714
}
815
}

src/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher.csproj

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<AssemblyName>ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher</AssemblyName>
66
<PackageId>ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher</PackageId>
7-
<PackageVersion>1.2.0</PackageVersion>
87
<Authors>Scott Brady</Authors>
98
<Description>ASP.NET Core Identity IPasswordHasher implementation using BCrypt.Net - next</Description>
109
<Copyright>Copyright (c) 2017 Scott Brady</Copyright>
1110
<PackageTags>aspnetcore;identity;bcrypt;password;hashing;hash;security;blowfish</PackageTags>
1211
<PackageIconUrl>https://www.scottbrady91.com/img/logos/scottbrady91.png</PackageIconUrl>
1312
<PackageProjectUrl>https://github.com/scottbrady91/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher</PackageProjectUrl>
1413
<PackageLicenseExpression>MIT</PackageLicenseExpression>
15-
<PackageReleaseNotes>Updated bcrypt and ASP.NET Identity dependencies. Made methods virtual.</PackageReleaseNotes>
14+
<IncludeSymbols>true</IncludeSymbols>
15+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
16+
<Version>1.2.0</Version>
17+
<PackageReleaseNotes>Updated default work factor to 11. Added support for SuccessRehashNeeded. Updated bcrypt and ASP.NET Identity dependencies.</PackageReleaseNotes>
1618
</PropertyGroup>
1719

1820
<ItemGroup>

test/ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher.Tests/BCryptPasswordHasherTests.cs

+77-31
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,29 @@ namespace ScottBrady91.AspNetCore.Identity.BCryptPasswordHasher.Tests
88
{
99
public class BCryptPasswordHasherTests
1010
{
11+
private BCryptPasswordHasherOptions options = new BCryptPasswordHasherOptions();
12+
13+
private BCryptPasswordHasher<string> CreateSut() =>
14+
new BCryptPasswordHasher<string>(
15+
options != null ? new OptionsWrapper<BCryptPasswordHasherOptions>(options) : null);
16+
17+
[Theory]
18+
[InlineData(null)]
19+
[InlineData("")]
20+
[InlineData(" ")]
21+
public void HashPassword_WhenPasswordIsNullOrWhitespace_ExpectArgumentNullException(string password)
22+
{
23+
var sut = CreateSut();
24+
Assert.Throws<ArgumentNullException>(() => sut.HashPassword(null, password));
25+
}
26+
1127
[Fact]
1228
public void HashPassword_WithDefaultSettings_ExpectVerifiableHash()
1329
{
1430
var password = Guid.NewGuid().ToString();
1531

16-
var hasher = new BCryptPasswordHasher<string>();
17-
var hashedPassword = hasher.HashPassword("", password);
32+
var sut = CreateSut();
33+
var hashedPassword = sut.HashPassword("", password);
1834

1935
BCrypt.Net.BCrypt.Verify(password, hashedPassword).Should().BeTrue();
2036
}
@@ -24,9 +40,9 @@ public void HashPassword_WhenCalledMultipleTimesWithSamePlaintext_ExpectDifferen
2440
{
2541
var password = Guid.NewGuid().ToString();
2642

27-
var hasher = new BCryptPasswordHasher<string>();
28-
var hashedPassword1 = hasher.HashPassword("", password);
29-
var hashedPassword2 = hasher.HashPassword("", password);
43+
var sut = CreateSut();
44+
var hashedPassword1 = sut.HashPassword("", password);
45+
var hashedPassword2 = sut.HashPassword("", password);
3046

3147
hashedPassword1.Should().NotBe(hashedPassword2);
3248
}
@@ -37,10 +53,10 @@ public void HashPassword_WithCustomWorkFactor_ExpectVerifiableHash()
3753
var random = new Random();
3854
var password = Guid.NewGuid().ToString();
3955

40-
var hasher = new BCryptPasswordHasher<string>(
41-
new OptionsWrapper<BCryptPasswordHasherOptions>(
42-
new BCryptPasswordHasherOptions {WorkFactor = random.Next(8, 18)}));
43-
var hashedPassword = hasher.HashPassword("", password);
56+
options.WorkFactor = options.WorkFactor - 1;
57+
var sut = CreateSut();
58+
59+
var hashedPassword = sut.HashPassword("", password);
4460

4561
BCrypt.Net.BCrypt.Verify(password, hashedPassword).Should().BeTrue();
4662
}
@@ -50,11 +66,10 @@ public void HashPassword_WithEnhancedEntropy_ExpectHashNotToVerify()
5066
{
5167
var password = Guid.NewGuid().ToString();
5268

53-
var hasher = new BCryptPasswordHasher<string>(
54-
new OptionsWrapper<BCryptPasswordHasherOptions>(
55-
new BCryptPasswordHasherOptions {EnhancedEntropy = true}));
69+
options.EnhancedEntropy = true;
70+
var sut = CreateSut();
5671

57-
var hashedPassword = hasher.HashPassword("", password);
72+
var hashedPassword = sut.HashPassword("", password);
5873

5974
BCrypt.Net.BCrypt.Verify(password, hashedPassword, true).Should().BeTrue();
6075
BCrypt.Net.BCrypt.EnhancedVerify(password, hashedPassword).Should().BeTrue();
@@ -65,49 +80,68 @@ public void HashPassword_WithPasswordCreatedWithoutEnhancedEntropyButVerifiedWit
6580
{
6681
var password = Guid.NewGuid().ToString();
6782

68-
var hasher = new BCryptPasswordHasher<string>(
69-
new OptionsWrapper<BCryptPasswordHasherOptions>(
70-
new BCryptPasswordHasherOptions { EnhancedEntropy = false }));
83+
options.EnhancedEntropy = false;
84+
var sut = CreateSut();
7185

72-
var hashedPassword = hasher.HashPassword("", password);
86+
var hashedPassword = sut.HashPassword("", password);
7387

7488
BCrypt.Net.BCrypt.Verify(password, hashedPassword, true).Should().BeFalse();
7589
BCrypt.Net.BCrypt.EnhancedVerify(password, hashedPassword).Should().BeFalse();
7690
}
91+
92+
[Theory]
93+
[InlineData(null)]
94+
[InlineData("")]
95+
[InlineData(" ")]
96+
public void VerifyHashedPassword_WhenHashedPasswordIsNullOrWhitespace_ExpectArgumentNullException(string hashedPassword)
97+
{
98+
var sut = CreateSut();
99+
Assert.Throws<ArgumentNullException>(() => sut.VerifyHashedPassword(null, hashedPassword, Guid.NewGuid().ToString()));
100+
}
101+
102+
[Theory]
103+
[InlineData(null)]
104+
[InlineData("")]
105+
[InlineData(" ")]
106+
public void VerifyHashedPassword_WhenPasswordIsNullOrWhitespace_ExpectArgumentNullException(string password)
107+
{
108+
var sut = CreateSut();
109+
Assert.Throws<ArgumentNullException>(() => sut.VerifyHashedPassword(null, Guid.NewGuid().ToString(), password));
110+
}
77111

78112
[Fact]
79113
public void VerifyHashedPassword_WithDefaultSettings_ExpectSuccess()
80114
{
81115
var password = Guid.NewGuid().ToString();
82116
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password);
83117

84-
var hasher = new BCryptPasswordHasher<string>();
118+
var sut = CreateSut();
85119

86-
hasher.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Success);
120+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Success);
87121
}
88122

89123
[Fact]
90124
public void VerifyHashedPassword_WithEnhancedEntropy_ExpectSuccess()
91125
{
92-
var options = new BCryptPasswordHasherOptions {EnhancedEntropy = true};
93126
var password = Guid.NewGuid().ToString();
94127
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, options.WorkFactor, true);
95128

96-
var hasher = new BCryptPasswordHasher<string>(new OptionsWrapper<BCryptPasswordHasherOptions>(options));
129+
options.EnhancedEntropy = true;
130+
var sut = CreateSut();
97131

98-
hasher.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Success);
132+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Success);
99133
}
100134

101135
[Fact]
102136
public void VerifyHashedPassword_WhenPasswordCreatedWithEnhancedEntropyButVerifiedWithout_ExpectFailure()
103137
{
104-
var options = new BCryptPasswordHasherOptions { EnhancedEntropy = true };
105138
var password = Guid.NewGuid().ToString();
106-
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, options.WorkFactor);
139+
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, options.WorkFactor, false);
107140

108-
var hasher = new BCryptPasswordHasher<string>(new OptionsWrapper<BCryptPasswordHasherOptions>(options));
141+
options.EnhancedEntropy = true;
142+
var sut = CreateSut();
109143

110-
hasher.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Failed);
144+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Failed);
111145
}
112146

113147
[Fact]
@@ -116,20 +150,32 @@ public void VerifyHashedPassword_WhenSuppliedPasswordDoesNotMatch_ExpectFailure(
116150
var password = Guid.NewGuid().ToString();
117151
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(Guid.NewGuid().ToString());
118152

119-
var hasher = new BCryptPasswordHasher<string>();
153+
var sut = CreateSut();
120154

121-
hasher.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Failed);
155+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Failed);
122156
}
123157

124158
[Fact]
125-
public void VerifyHashedPassword_WhenCorrectV10Password_ExpectSuccess()
159+
public void VerifyHashedPassword_WhenCorrectV10Password_ExpectSuccessRehashNeeded()
126160
{
127161
const string password = "6@JM}T-3DeZo&2i=U73A^nEY7tXe_3UC%RR";
128162
const string hashedPassword = "$2a$10$SpIhzEv3ATLa0CmTz4L7ouAn/w5NyedFic5X3fKaI9eu0xhW97OUC";
129163

130-
var hasher = new BCryptPasswordHasher<string>();
164+
var sut = CreateSut();
165+
166+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.SuccessRehashNeeded);
167+
}
168+
169+
[Fact]
170+
public void VerifyHashedPassword_WhenPasswordHashedWithLowerEntropy_ExpectSuccessRehashNeeded()
171+
{
172+
var password = Guid.NewGuid().ToString();
173+
var hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, 10);
174+
175+
options.WorkFactor = 11;
176+
var sut = CreateSut();
131177

132-
hasher.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.Success);
178+
sut.VerifyHashedPassword("", hashedPassword, password).Should().Be(PasswordVerificationResult.SuccessRehashNeeded);
133179
}
134180
}
135181
}

0 commit comments

Comments
 (0)