Last active
January 1, 2016 14:59
-
-
Save Recodify/8161473 to your computer and use it in GitHub Desktop.
A persistence unaware securely hashed password using Rrc2898DeriveBytes. Both the hashed password and the generated salt should be persisted.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SecuredPassword | |
{ | |
private const int saltSize = 256; | |
private readonly byte[] hash; | |
private readonly byte[] salt; | |
public byte[] Hash | |
{ | |
get { return hash; } | |
} | |
public byte[] Salt | |
{ | |
get { return salt; } | |
} | |
// Use when creating a new hashed password. | |
public SecuredPassword(string plainPassword) | |
{ | |
if (string.IsNullOrWhiteSpace(plainPassword)) | |
return; | |
using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize)) | |
{ | |
salt = deriveBytes.Salt; | |
hash = deriveBytes.GetBytes(saltSize); | |
} | |
} | |
// Use when loading a hashed password for comparison. | |
public SecuredPassword(byte[] hash, byte[] salt) | |
{ | |
this.hash = hash; | |
this.salt = salt; | |
} | |
public bool Verify(string password) | |
{ | |
if (string.IsNullOrWhiteSpace(password)) | |
return false; | |
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt)) | |
{ | |
byte[] newKey = deriveBytes.GetBytes(saltSize); | |
return newKey.SequenceEqual(hash); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SecuredPasswordTests | |
{ | |
[Test] | |
public void IsHashed_AsExpected() | |
{ | |
var securedPassword = new SecuredPassword("password"); | |
Assert.That(securedPassword.Hash, Is.Not.EqualTo("password")); | |
Assert.That(securedPassword.Hash.Length, Is.EqualTo(256)); | |
} | |
[Test] | |
public void Generates_Unique_Salt() | |
{ | |
var securedPassword = new SecuredPassword("password"); | |
var securedPassword2 = new SecuredPassword("password"); | |
Assert.That(securedPassword.Salt, Is.Not.Null); | |
Assert.That(securedPassword2.Salt, Is.Not.Null); | |
Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt)); | |
} | |
[Test] | |
public void Generates_Unique_Hash() | |
{ | |
var securedPassword = new SecuredPassword("password"); | |
var securedPassword2 = new SecuredPassword("password"); | |
Assert.That(securedPassword.Hash, Is.Not.Null); | |
Assert.That(securedPassword2.Hash, Is.Not.Null); | |
Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash)); | |
} | |
[Test] | |
public void Verify_WhenMatching_ReturnsTrue() | |
{ | |
var securedPassword = new SecuredPassword("password"); | |
var result = securedPassword.Verify("password"); | |
Assert.That(result, Is.True); | |
} | |
[Test] | |
public void Verify_WhenDifferent_ReturnsFalse() | |
{ | |
var securedPassword = new SecuredPassword("password"); | |
var result = securedPassword.Verify("Password"); | |
Assert.That(result, Is.False); | |
} | |
[Test] | |
public void Verify_WhenRehydrated_AndMatching_ReturnsTrue() | |
{ | |
var securedPassword = new SecuredPassword("password123"); | |
var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt); | |
var result = rehydrated.Verify("password123"); | |
Assert.That(result, Is.True); | |
} | |
[Test] | |
public void Constructor_Handles_Null_Password() | |
{ | |
Assert.DoesNotThrow(() => new SecuredPassword(null)); | |
} | |
[Test] | |
public void Constructor_Handles_Empty_Password() | |
{ | |
Assert.DoesNotThrow(() => new SecuredPassword(string.Empty)); | |
} | |
[Test] | |
public void Verify_Handles_Null_Password() | |
{ | |
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null)); | |
} | |
[Test] | |
public void Verify_Handles_Empty_Password() | |
{ | |
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty)); | |
} | |
[Test] | |
public void Verify_When_Null_Password_ReturnsFalse() | |
{ | |
Assert.That(new SecuredPassword("password").Verify(null), Is.False); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment