See comments above. You probably need to check the documentation for
HashAlgorithm.
Hi,
I made the corrections, removed using and went to HashAlgorithm
documentation on MSDN:
http://msdn.microsoft.com/en-us/lib...ryptography.hashalgorithm_members(VS.71).aspx
If I am not wrong it should be used the Clear method instead of the
protected Disposed method:
Clear: Releases all resources used by the HashAlgorithm.
Dispose: Releases the unmanaged resources used by the HashAlgorithm
and optionally releases the managed resources.
I ran the code and got the following results:
Matching test OK
Not matching test OK
200 tests took 123145 mSec
Average 0.615725 seconds per hash
I just kept the 250 000 repeats as you used.
But in this project I will use a lower value to not be so heavy to the
CPU and because there is not really important data to protect but in
future projects this will be really useful.
Any advice for the length I should keep on the database for the
Password and Salt columns? I will make both varbinary.
This is the working code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace SashImplementation {
public class Program {
public static void Main(String[] args) {
Sash sash = new Sash(new SHA256Managed(), 250000);
try {
// Test matching
Byte[] password1 = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
Byte[] salt = sash.GenerateSalt(64);
Byte[] firstHash = sash.CalculateHash(password1, salt);
Byte[] password2 = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
if (sash.CheckHash(password2, salt, firstHash)) {
Console.WriteLine("Matching test OK");
} else {
Console.WriteLine("Matching test failed.");
}
// Test not matching
password1 = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
salt = sash.GenerateSalt(64);
firstHash = sash.CalculateHash(password1, salt);
password2 = new byte[] { 1, 1, 2, 3, 4, 5, 6, 7 };
if (sash.CheckHash(password2, salt, firstHash)) {
Console.WriteLine("Not matching test failed.");
} else {
Console.WriteLine("Not matching test OK.");
}
// Timer test
Stopwatch sw = new Stopwatch();
int numTests = 200;
sw.Start();
for (int i = 0; i < numTests; ++i) {
password1[0] = (byte)i;
firstHash = sash.CalculateHash(password1, salt);
} // end for
sw.Stop();
Console.WriteLine("{0} tests took {1} mSec.", numTests,
sw.ElapsedMilliseconds);
Console.WriteLine("Average {0} seconds per hash.",
sw.ElapsedMilliseconds / (numTests * 1000.0));
// Keep console on screen
Console.Write("Press [Enter] to continue... ");
Console.ReadLine();
} finally {
sash.Dispose();
}
} // end Main()
} // end class HashTest
// Sash
/// <summary>
/// Implements salted hash for managed has algorithms
/// </summary>
public sealed class Sash : IDisposable {
#region Fields
private Boolean disposed = false;
private readonly Int32 repeats;
private readonly HashAlgorithm algorithm;
private static Int32 defaultRepeats = 10000;
private static HashAlgorithm defaultAlgorithm = new SHA256Managed
();
private static RNGCryptoServiceProvider rng = new
RNGCryptoServiceProvider();
#endregion // Fields
#region Constructors
// Sash
/// <summary>
/// Initialize sash with custom hash algorithm and repeats
/// </summary>
public Sash(HashAlgorithm algorithm, Int32 repeats) {
// Check parameters
if (algorithm == null) throw new ArgumentNullException
("algorithm", "Hash algorithm cannot be null");
if (repeats <= 0) throw new ArgumentOutOfRangeException
("repeats", "Repeats must be positive");
// Define fields
this.algorithm = algorithm;
this.repeats = repeats;
} // Sash
// Sash
/// <summary>
/// Initialize sash with default algorithm, SHA256Managed, and
custom repeats value
/// </summary>
public Sash(Int32 repeats)
: this(defaultAlgorithm, repeats) {
} // Sash
// Sash
/// <summary>
/// Initialize sash with custom algorithm and default repeats
equal to 10000
/// </summary>
public Sash(HashAlgorithm algorithm)
: this(algorithm, defaultRepeats) {
} // Sash
// Sash
/// <summary>
/// Initialize sash with defaul algorithm, SHA256Managed, and
default repeats equal to 10000
/// </summary>
public Sash()
: this(defaultAlgorithm, defaultRepeats) {
} // Sash
#endregion // Constructors
#region Methods
// GenerateSalt
/// <summary>
/// Generate random salt
/// </summary>
public Byte[] GenerateSalt(Int32 size) {
Byte[] salt = new Byte[size];
rng.GetBytes(salt);
return salt;
} // GenerateSalt
// CalculateHash
/// <summary>
/// Calculate hash from a hash algorithm and salt
/// </summary>
public Byte[] CalculateHash(Byte[] data, Byte[] salt) {
// Check parameters
if (data == null) throw new ArgumentNullException("data", "Data
cannot be null");
if (data.Length == 0) throw new ArgumentOutOfRangeException
("data", "Data cannot be empty");
if (salt == null) throw new ArgumentNullException("salt", "Salt
cannot be null");
if (salt.Length == 0) throw new ArgumentOutOfRangeException
("salt", "Salt cannot be empty");
// Calculate hash
Byte[] hash;
hash = algorithm.ComputeHash(Concat(data, salt));
for (Int32 i = 0; i < repeats; ++i) {
hash = algorithm.ComputeHash(Concat(hash, salt));
}
// Clear data
for (Int32 j = 0; j < data.Length; ++j) {
data[j] = 0;
}
return hash;
} // CalculateHash
// CheckHash
/// <summary>
/// Check hash against expected
/// </summary>
public Boolean CheckHash(Byte[] data, Byte[] salt, Byte[]
expected) {
// Check parameters
if (data == null) throw new ArgumentNullException("data", "Data
cannot be null");
if (data.Length == 0) throw new ArgumentOutOfRangeException
("data", "Data cannot be empty");
if (salt == null) throw new ArgumentNullException("salt", "Salt
cannot be null");
if (salt.Length == 0) throw new ArgumentOutOfRangeException
("salt", "Salt cannot be empty");
if (expected == null) throw new ArgumentNullException
("expected", "Expected cannot be null");
if (expected.Length != algorithm.HashSize / 8) throw new
ArgumentOutOfRangeException("expected", "Expected length should be one
eight of algorithm hash size");
// CalculateHash
Byte[] actual = CalculateHash(data, salt);
for (Int32 i = 0; i < actual.Length; ++i) {
if (actual
!= expected) { return false; }
}
return true;
} // CheckHash
// Dispose
/// <summary>
/// Dispose resources
/// </summary>
public void Dispose() {
this.Dispose(true);
GC.SuppressFinalize(this);
} // Dispose
// Dispose
/// <summary>
/// Dispose resources
/// </summary>
public void Dispose(Boolean disposing) {
// Already disposed
if (disposed) return;
// Dispose managed resources
if (disposing) {
if (algorithm != null) algorithm.Clear();
}
// Dispose unmanaged resources
// Redefine disposed
disposed = true;
} // Dispose
#region Private
// Concat
/// <summary>
/// Concat two byte arrays
/// </summary>
private Byte[] Concat(byte[] lhs, byte[] rhs) {
Byte[] result = new Byte[lhs.Length + rhs.Length];
Array.Copy(lhs, result, lhs.Length);
Array.Copy(rhs, 0, result, lhs.Length, rhs.Length);
return result;
} // Concat
#endregion // Private
#endregion // Methods
} // Sash
} // SashImplementation