/// <summary> /// Hash the given password to a Argon2 hash string. /// </summary> /// <param name="configToHash"> /// Contains all the information used to create the hash returned. /// </param> /// <returns> /// The Argon2 hash of the given password. /// </returns> public static string Hash(Argon2Config configToHash) { var argon2 = new Argon2(configToHash); using var hash = argon2.Hash(); return(argon2.config.EncodeString(hash.Buffer)); }
/// <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> /// Encodes an Argon2 instance into a string. /// </summary> /// <param name="config"> /// To encode. /// </param> /// <param name="hash"> /// The hash to put in the encoded string. May be null. /// </param> /// <returns> /// The encoded Argon2 instance. /// </returns> /// <remarks> /// <para> /// Resulting 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 omitted 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 string EncodeString(this Argon2Config config, byte[] hash) { var dst = new StringBuilder(); if (config.Type == Argon2Type.DataIndependentAddressing) { dst.Append("$argon2i$v="); } else if (config.Type == Argon2Type.DataDependentAddressing) { dst.Append("$argon2d$v="); } else if (config.Type == Argon2Type.HybridAddressing) { dst.Append("$argon2id$v="); } else { throw new ArgumentException( $"Expected one of {Argon2Type.DataDependentAddressing}, " + $"{Argon2Type.DataIndependentAddressing}, or {Argon2Type.HybridAddressing}, " + $"got {config.Type}", nameof(config)); } dst.AppendFormat("{0:D}", (int)config.Version); dst.Append("$m="); dst.AppendFormat("{0:D}", config.MemoryCost); dst.Append(",t="); dst.AppendFormat("{0:D}", config.TimeCost); dst.Append(",p="); dst.AppendFormat("{0:D}", config.Lanes); if (config.AssociatedData != null && config.AssociatedData.Length > 0) { dst.Append(",data="); dst.Append(config.AssociatedData.ToB64String()); } if (config.Salt == null || config.Salt.Length == 0) { return(dst.ToString()); } dst.Append("$"); dst.Append(config.Salt.ToB64String()); if (hash == null || hash.Length == 0) { return(dst.ToString()); } dst.Append("$"); dst.Append(hash.ToB64String()); return(dst.ToString()); }
/// <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> /// <param name="secret"> /// The secret hashed into the password. /// </param> /// <param name="secureArrayCall"> /// The methods that get called to secure arrays. A null value defaults to <see cref="SecureArray"/>.<see cref="SecureArray.DefaultCall"/>. /// </param> /// <returns> /// True on success; false otherwise. /// </returns> public static bool Verify( string encoded, byte[] password, byte[] secret, SecureArrayCall secureArrayCall = null) { var configToVerify = new Argon2Config { Password = password, Secret = secret, SecureArrayCall = secureArrayCall ?? SecureArray.DefaultCall, }; return(Verify(encoded, configToVerify)); }
/// <summary> /// Initializes a new instance of the <see cref="Argon2"/> class. /// </summary> /// <param name="config"> /// The configuration to use. /// </param> public Argon2(Argon2Config config) { this.config = config; uint memoryBlocks = (uint)config.MemoryCost; if (memoryBlocks < 2 * SyncPoints * config.Lanes) { memoryBlocks = 2 * SyncPoints * (uint)config.Lanes; } this.SegmentLength = (int)(memoryBlocks / (config.Lanes * SyncPoints)); // ensure that all segments have equal length this.LaneLength = this.SegmentLength * SyncPoints; this.MemoryBlockCount = this.LaneLength * this.config.Lanes; this.memory = BestSecureArray <ulong>(BlockSize * this.MemoryBlockCount / 8); this.Memory = new Blocks(this.memory.Buffer, this.MemoryBlockCount); }
/// <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(); } } }