I decided to throw this together today figuring it would be cool to add to my repertoire. Before we always had performed this "task" externally. Find the value we want to compare, hash it and compare with a valid hash. But my idea was to bundle this into a object which behaves nicely with string types. It then could internally compare the two and determine if they match.
I'm just curious on what you all think here and where I can improve. And if I've even done something any of you find could save time and find more usable than not.
Benefits
- No generic classes I have to drag around, it is now a type with a specific purpose.
- The data for the type is never stored in plain text, therefore should be safe, right?
Usage
ProtectedString ThisIsMyProtectedString1 = "";
ThisIsMyProtectedString1 = "Test";
if (ThisIsMyProtectedString1 == "Test")
MessageBox.Show("Equal");
Le Code
public struct ProtectedString
{
private string _Data;
public String Data
{
get { return _Data; }
private set { _Data = value; }
}
private const int SALT_BYTE_SIZE = 24;
private const int HASH_BYTE_SIZE = 24;
private const int PBKDF2_ITERATIONS = 1000;
public ProtectedString(String value)
{
_Data = "";
_Data = CreateHash(value);
}
public static implicit operator ProtectedString(String value)
{
return new ProtectedString(value);
}
public static bool operator ==(ProtectedString c1, String c2)
{
return c1.Equals(c2);
}
public static bool operator !=(ProtectedString c1, String c2)
{
return !c1.Equals(c2);
}
public static bool operator ==(String c1, ProtectedString c2)
{
return c2.Equals(c1);
}
public static bool operator !=(String c1, ProtectedString c2)
{
return !c2.Equals(c1);
}
public bool Equals(String value)
{
Boolean result = false;
result = ValidateHash(value, _Data);
return result;
}
public override bool Equals(object obj)
{
return base.Equals(obj);
}
private string CreateHash(String value)
{
RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
byte[] salt = new byte[SALT_BYTE_SIZE];
csprng.GetBytes(salt);
byte[] hash = PBKDF2(value, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
return
Convert.ToBase64String(salt) +
Convert.ToBase64String(hash);
}
private bool ValidateHash(String value, String validHash)
{
byte[] full = Convert.FromBase64String(validHash);
byte[] salt = full.Take(SALT_BYTE_SIZE).ToArray();
byte[] hash = full.Skip(SALT_BYTE_SIZE).ToArray();
byte[] testHash = PBKDF2(value, salt, PBKDF2_ITERATIONS, hash.Length);
return SlowEquals(hash, testHash);
}
private bool SlowEquals(byte[] a, byte[] b)
{
uint diff = (uint)a.Length ^ (uint)b.Length;
for (int i = 0; i < a.Length && i < b.Length; i++)
diff |= (uint)(a[i] ^ b[i]);
return (diff == 0);
}
private byte[] PBKDF2(string value, byte[] salt, int iterations, int outputBytes)
{
Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(value, salt);
pbkdf2.IterationCount = iterations;
return pbkdf2.GetBytes(outputBytes);
}
}