sql_hashers/Hashers/Hashers.cs

131 lines
4.4 KiB
C#

using Konscious.Security.Cryptography;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
public class Hashers
{
struct settings
{
public short parallel;
public short memory;
public short iterations;
public short bc;
}
static settings[] all_versions = new settings[]
{
new settings { }, // Custom
new settings { parallel = 4, memory = 64, iterations = 3, bc = 32 },
};
const int use_version = 2;
public static string Argon2id_hash(string password)
{
byte[] raw_salt = new byte[16];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(raw_salt);
}
settings complexity = all_versions[use_version - 1];
byte[] raw_hash = argon2id_hash(password, raw_salt, complexity);
string base64_hash = Convert.ToBase64String(raw_hash);
string base64_salt = Convert.ToBase64String(raw_salt);
return String.Format("{0}${1}${2}${3}", use_version, FormatComplexity(complexity), base64_salt, base64_hash);
}
public static string Argon2id_hash_custom(string password, short parallel, short memory, short iterations, short bc)
{
byte[] raw_salt = new byte[16];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(raw_salt);
}
settings complexity = new settings { parallel = parallel, memory = memory, iterations = iterations, bc = bc };
byte[] raw_hash = argon2id_hash(password, raw_salt, complexity);
string base64_hash = Convert.ToBase64String(raw_hash);
string base64_salt = Convert.ToBase64String(raw_salt);
return String.Format("{0}${1}${2}${3}", 1, FormatComplexity(complexity), base64_salt, base64_hash);
}
public static bool Argon2id_verify(string password, string hash)
{
string[] split = hash.Split('$');
if (split.Length == 0)
{
throw new Exception("Invalid or unknown Argon2id hash");
}
short version = Convert.ToInt16(split[0]);
if (version != 1 && version != 2)
{
throw new Exception("Invalid or unknown Argon2id hash version");
}
// version$settings$salt$hash
if (split.Length != 4)
{
throw new Exception("Invalid or unknown Argon2id hash format");
}
settings s = ParseComplexity(version, split[1]);
byte[] raw_salt = Convert.FromBase64String(split[2]);
byte[] raw_hash = argon2id_hash(password, raw_salt, s);
string new_hash = Convert.ToBase64String(raw_hash);
Console.WriteLine("{0} == {1}", new_hash, split[3]);
return new_hash == split[3];
}
private static byte[] argon2id_hash(string password, byte[] salt, settings complexity)
{
using (var argon2 = new Argon2id(Encoding.UTF8.GetBytes(password)))
{
argon2.DegreeOfParallelism = complexity.parallel;
argon2.MemorySize = 1024 * complexity.memory;
argon2.Iterations = complexity.iterations;
argon2.Salt = salt;
return argon2.GetBytes(complexity.bc);
}
}
private static string FormatComplexity(settings complexity)
{
return String.Format("p={0};m={1};i={2};bc={3}", complexity.parallel, complexity.memory, complexity.iterations, complexity.bc);
}
private static settings ParseComplexity(int version, string raw_complexity)
{
if (version != 1 && version != 2)
{
throw new Exception("Invalid or unknown Argon2id hash version in ParseComplexity");
}
settings s = new settings();
foreach (string item in raw_complexity.Split(';'))
{
string[] key_value = item.Split('=');
if (key_value.Length != 2) { continue; }
if (key_value[0] == "p") { s.parallel = Convert.ToInt16(key_value[1]); }
if (key_value[0] == "m") { s.memory = Convert.ToInt16(key_value[1]); }
if (key_value[0] == "i") { s.iterations = Convert.ToInt16(key_value[1]); }
if (key_value[0] == "bc") { s.bc = Convert.ToInt16(key_value[1]); }
}
if (s.parallel == 0 || s.memory == 0 || s.iterations == 0 || s.bc == 0)
{
throw new Exception("Invalid or missing Argon2id hash settings");
}
return s;
}
}