/// <summary> /// Verify the given Argon2 hash as being that of the given password. /// </summary> /// <param name="encoded"> /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash. /// </param> /// <param name="password"> /// The password to verify /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, byte[] password) { SecureArray <byte> hash = null; try { var configToVerify = new Argon2Config { Password = password }; if (!configToVerify.DecodeString(encoded, out hash) || hash == null) { return(false); } var hasherToVerify = new Argon2(configToVerify); var hashToVerify = hasherToVerify.Hash(); return(!hash.Buffer.Where((b, i) => b != hashToVerify[i]).Any()); } finally { hash?.Dispose(); } }
/// <summary> /// Verify the given Argon2 hash as being that of the given password. /// </summary> /// <param name="encoded"> /// The Argon2 hash string. This has the actual hash along with other parameters used in the hash. /// </param> /// <param name="configToVerify"> /// The configuration that contains the values used to created <paramref name="encoded"/>. /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, Argon2Config configToVerify) { SecureArray <byte> hash = null; try { if (!configToVerify.DecodeString(encoded, out hash) || hash == null) { return(false); } using (var hasherToVerify = new Argon2(configToVerify)) { using (var hashToVerify = hasherToVerify.Hash()) { return(!hash.Buffer.Where((b, i) => b != hashToVerify[i]).Any()); } } } finally { hash?.Dispose(); } }
/// <summary> /// Compares a hash, salt and plain password and sets IsValid /// </summary> public SecuredPassword(string plainPassword, byte[] hash, byte[] salt, HashStrategyKind hashStrategy) { _hash = hash; _salt = salt; SetHashStrategy(hashStrategy); byte[] newKey; switch (hashStrategy) { case HashStrategyKind.Pbkdf210001Iterations: var numberOfIterations = (int)_hashingParameter; if (numberOfIterations <= 10000) { throw new ArgumentException("Iterations must be greater than 10000"); } using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, salt, numberOfIterations, HashAlgorithmName.SHA256)) { newKey = deriveBytes.GetBytes(_saltSize); IsValid = newKey.SequenceEqual(hash); } break; case HashStrategyKind.Argon2WorkCost: SecureArray <byte> hashB = null; try { var passwordBytes = Encoding.ASCII.GetBytes(plainPassword); var configOfPasswordToVerify = new Argon2Config { Type = Argon2Type.DataIndependentAddressing, Version = Argon2Version.Nineteen, TimeCost = 10, MemoryCost = (int)_hashingParameter, Lanes = 5, Threads = Environment.ProcessorCount, Salt = _salt, Password = passwordBytes, HashLength = 20 }; var hashString = Encoding.ASCII.GetString(_hash); if (configOfPasswordToVerify.DecodeString(hashString, out hashB) && hashB != null) { var argon2ToVerify = new Argon2(configOfPasswordToVerify); using (var hashToVerify = argon2ToVerify.Hash()) { if (!hashB.Buffer.Where((b, i) => b != hashToVerify[i]).Any()) { IsValid = true; } } } } finally { hashB?.Dispose(); } break; } }
/// <summary> /// Decodes an Argon2 hash string into an Argon2 class instance. /// </summary> /// <param name="config"> /// The configuration to populate with the data found in <paramref name="str"/>. /// </param> /// <param name="str"> /// The string to decode. /// </param> /// <param name="hash"> /// Loaded with the hash found in <paramref name="str"/>; set to null if /// <paramref name="str"/> does not contain a hash. /// </param> /// <returns> /// True on success; false otherwise. <paramref name="hash"/> set to /// null on failure. /// </returns> /// <remarks> /// <para> /// Expected format: /// </para> /// <para> /// $argon2<T>[$v=<num>]$m=<num>,t=<num>,p=<num>[,keyid=<bin>][,data=<bin>][$<bin>[$<bin>]]. /// </para> /// <para> /// where <T> is either 'd' or 'i', <num> is a decimal integer (positive, fits in /// an 'unsigned long'), and <bin> is Base64-encoded data (no '=' padding /// characters, no newline or whitespace). /// The "keyid" is a binary identifier for a key (up to 8 bytes); /// "data" is associated data (up to 32 bytes). When the 'keyid' /// (resp. the 'data') is empty, then it is ommitted from the output. /// </para> /// <para> /// The last two binary chunks (encoded in Base64) are, in that order, /// the salt and the output. Both are optional, but you cannot have an /// output without a salt. The binary salt length is between 8 and 48 bytes. /// The output length is always exactly 32 bytes. /// </para> /// </remarks> public static bool DecodeString(this Argon2Config config, string str, out SecureArray <byte> hash) { int pos; Argon2Type type; if (str.StartsWith("$argon2id")) { type = Argon2Type.HybridAddressing; pos = 9; } else if (str.StartsWith("$argon2i")) { type = Argon2Type.DataIndependentAddressing; pos = 8; } else if (str.StartsWith("$argon2d")) { type = Argon2Type.DataDependentAddressing; pos = 8; } else { hash = null; return(false); } var version = Argon2Version.Sixteen; /* Reading the version number if the default is suppressed */ { var check = "$v="; if (string.Compare(str, pos, check, 0, check.Length) == 0) { pos += check.Length; pos = DecodeDecimal(str, pos, out var decX); if (pos < 0) { hash = null; return(false); } version = (Argon2Version)decX; } } pos = DecodeDecimal(out var memoryCost, "$m=", str, pos); if (pos < 0) { hash = null; return(false); } pos = DecodeDecimal(out var timeCost, ",t=", str, pos); if (pos < 0) { hash = null; return(false); } pos = DecodeDecimal(out var lanes, ",p=", str, pos); if (pos < 0) { hash = null; return(false); } byte[] associatedData = null; { var check = ",data="; if (string.Compare(str, pos, check, 0, check.Length) == 0) { pos += check.Length; pos = FromBase64(out associatedData, str, pos); if (pos < 0) { hash = null; return(false); } } } var validator = new Argon2Config(); if (pos == str.Length) { try { validator.Type = type; validator.TimeCost = (int)timeCost; validator.MemoryCost = (int)memoryCost; validator.Lanes = (int)lanes; validator.AssociatedData = associatedData; validator.Version = version; } catch (Exception) { hash = null; return(false); } config.Type = type; config.TimeCost = (int)timeCost; config.MemoryCost = (int)memoryCost; config.Lanes = (int)lanes; config.AssociatedData = associatedData; config.Version = version; hash = null; return(true); } pos = DecodeBase64(out var salt, "$", str, pos); if (pos < 0) { hash = null; return(false); } if (pos == str.Length) { try { validator.Type = type; validator.TimeCost = (int)timeCost; validator.MemoryCost = (int)memoryCost; validator.Lanes = (int)lanes; validator.Salt = salt; validator.AssociatedData = associatedData; validator.Version = version; } catch (Exception) { hash = null; return(false); } config.Type = type; config.TimeCost = (int)timeCost; config.MemoryCost = (int)memoryCost; config.Lanes = (int)lanes; config.Salt = salt; config.AssociatedData = associatedData; config.Version = version; hash = null; return(true); } if (str[pos] != '$') { hash = null; return(false); } ++pos; int hashlen = Base64Length(str, pos); if (hashlen < 0) { hash = null; return(false); } SecureArray <byte> output; try { output = new SecureArray <byte>(hashlen, SecureArrayType.ZeroedPinnedAndNoSwap, config.SecureArrayCall); } catch (LockFailException) { output = new SecureArray <byte>(hashlen, SecureArrayType.ZeroedAndPinned, config.SecureArrayCall); } bool success = false; try { pos = FromBase64(output.Buffer, str, pos); if (pos < 0) { hash = null; return(false); } if (pos != str.Length) { hash = null; return(false); } try { validator.Type = type; validator.TimeCost = (int)timeCost; validator.MemoryCost = (int)memoryCost; validator.Lanes = (int)lanes; validator.Salt = salt; validator.AssociatedData = associatedData; validator.Version = version; } catch (Exception) { hash = null; return(false); } config.Type = type; config.TimeCost = (int)timeCost; config.MemoryCost = (int)memoryCost; config.Lanes = (int)lanes; config.Salt = salt; config.AssociatedData = associatedData; config.Version = version; hash = output; success = true; return(true); } finally { if (!success) { output.Dispose(); } } }
internal static ObjectResult VerifyUser(string username, string password) { if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { return(new InvalidResult("Error: Username/Password are required.")); } try { var result = from user in context.users where user.username == username select user; if (result.Count() == 0) { return(new NotFoundResult("Error: User does not exist.")); } string passwordHash = result.First().passwordHash; bool? isAdmin = result.First().isAdmin; //START CODE BLOCK //-- Code within used from //https://github.com/mheyman/Isopoh.Cryptography.Argon2/blob/master/README.md Argon2Config config = new Argon2Config { Type = Argon2Type.DataIndependentAddressing, Version = Argon2Version.Nineteen, TimeCost = 3, MemoryCost = 32768, Lanes = 4, Threads = Environment.ProcessorCount, Password = Encoding.ASCII.GetBytes(password), Salt = Convert.FromBase64String(Properties.Settings.Default.Salt), HashLength = 20 }; SecureArray <byte> hashB = null; try { if (config.DecodeString(passwordHash, out hashB) && hashB != null) { var argon2ToVerify = new Argon2(config); using (var hashToVerify = argon2ToVerify.Hash()) { if (!hashB.Buffer.Where((b, i) => b != hashToVerify[i]).Any()) { return(new OkResult(isAdmin.ToString())); } else { return(new WrongResult("Error: Username/Password is incorrect.")); } } } } catch (Exception ex) { return(new InvalidResult("Error: Failed to verify password.")); } finally { hashB?.Dispose(); } //END CODE BLOCK } catch (Exception ex) { return(new InvalidResult("Error: Failed retrieving data from the database.")); } return(new InvalidResult("Error: Failed to process verification.")); }
public void Dispose() => _privateKey?.Dispose();