private static void Run(PasswordHasher hasher, string pwd, string salt, bool rawOnly, bool encodedOnly) { try { if (rawOnly) { Console.WriteLine(ToHex(hasher.HashRaw(pwd, salt))); return; } if (encodedOnly) { Console.WriteLine(hasher.Hash(pwd, salt)); return; } var startTime = DateTime.Now; string encoded = hasher.Hash(pwd, salt); var stopTime = DateTime.Now; HashMetadata metadata = PasswordHasher.ExtractMetadata(encoded); Console.WriteLine("Hash:\t\t" + ToHex(metadata.Hash)); Console.WriteLine("Encoded:\t" + encoded); Console.WriteLine("{0:0.000} seconds", (stopTime - startTime).TotalSeconds); if(hasher.Verify(encoded, pwd)) Console.WriteLine("Verification ok"); else throw new Argon2Exception("verifying", Argon2Error.VERIFY_MISMATCH); } catch (Exception ex) { Fatal(ex.Message); } }
// This code is more complicated than necessary IF you do not care about attackers enumerating your user list via timing attacks public ActionResult Login(string username, string password) { // Do profiling to determine what the optimal time and memory cost would be for your server setup // Ideally, PasswordHasher.Hash should take about 200ms to perform as a good balance between security and responsiveness // Prefer to increase MemoryCost instead of TimeCost (increasing MemoryCost will also increase the overall time) // TODO: Edit your web.config or app.config to contain the following keys within your <configuration><appSettings>...</appSettings></configuration>: // TODO: <add key="PasswordHasher.TimeCost" value="3" /> // TODO: <add key="PasswordHasher.MemoryCost" value="65536" /> // If you are updating the time cost or memory cost (because, e.g., you moved to faster hardware) then // add PasswordHasher.OldTimeCost and PasswordHasher.OldMemoryCost keys with the previous values to ensure timing attacks cannot // be done to enumerate the possible users. Remove OldTimeCost and OldMemoryCost when the migration is finished. string timeCostStr = (ConfigurationManager.AppSettings["PasswordHasher.TimeCost"] ?? "3"); string memoryCostStr = (ConfigurationManager.AppSettings["PasswordHasher.MemoryCost"] ?? "65536"); uint timeCost = uint.Parse(timeCostStr); uint memoryCost = uint.Parse(memoryCostStr); uint oldTimeCost = uint.Parse(ConfigurationManager.AppSettings["PasswordHasher.OldTimeCost"] ?? timeCostStr); uint oldMemoryCost = uint.Parse(ConfigurationManager.AppSettings["PasswordHasher.OldMemoryCost"] ?? memoryCostStr); bool costsDiffer = (timeCost != oldTimeCost || memoryCost != oldMemoryCost); string hashFormat = "$argon2i$m={0},t={1},p=1$AAAAAAAAAAAAAAAAAAAAAA$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; string dummyHash = string.Format(hashFormat, memoryCost, timeCost); string oldDummyHash = string.Format(hashFormat, oldMemoryCost, oldTimeCost); var passwordHasher = new PasswordHasher(timeCost, memoryCost); // Get the user from the database in hopefully some constant-time fashion so that timing attacks // cannot be used to enumerate valid usernames User user = GetUser(username); string passwordHash = (user != null ? user.PasswordHash : dummyHash); bool updatedCost; string newPasswordHash; HashMetadata hashMetadata = PasswordHasher.ExtractMetadata(passwordHash); bool usingOldCosts = (hashMetadata.MemoryCost != memoryCost || hashMetadata.TimeCost != timeCost); // always compare against a password hash even if the user is not found (when that happens, we compare against dummyHash) // to prevent timing attacks from enumerating a list of valid usernames if (passwordHasher.VerifyAndUpdate(passwordHash, password, out updatedCost, out newPasswordHash)) { // VerifyAndUpdate will generate a new password hash if the time or memory cost from ConfigurationManager.AppSettings // does not match what was used to generate the password hash which was stored in the database. // This allows you to easily update the password hashing cost if you upgrade to better server hardware by // modifying the PasswordHasher.TimeCost and PasswordHasher.MemoryCost parameters. if (updatedCost) { // As users successfully log in, update their password hash to the hash with the updated cost user.PasswordHash = newPasswordHash; UpdateUser(user); } else if(costsDiffer) { // run the verification with the old costs so that each login attempt performs a hash // with the new costs and with the old costs to ensure the timing is consistent passwordHasher.Verify(oldDummyHash, password); } // Successful login LogInUser(user); return RedirectToAction("Index", "Home"); } // if we're migrating password hashes from the old cost parameters to new cost parameters, we want // each login attempt to perform a single hash with the new cost parameters and a single hash with // the old cost parameters so that the timing is consistent. if(costsDiffer) passwordHasher.Verify((usingOldCosts ? dummyHash : oldDummyHash), password); // User failed to login var model = new LoginViewModel {ErrorMessage = "Username or Password is incorrect."}; return View(model); }
static void Main(string[] args) { uint m_cost = (uint)(1 << LOG_M_COST_DEF); uint t_cost = T_COST_DEF; uint threads = THREADS_DEF; uint hash_len = HASH_LEN_DEF; Argon2Type type = Argon2Type.Argon2i; bool rawOnly = false; bool encodedOnly = false; if (args.Length == 0) { Usage(); Environment.Exit(30); } var pwd = Console.In.ReadToEnd(); if (pwd.EndsWith("\r\n")) pwd = pwd.Substring(0, pwd.Length - 2); else if(pwd.EndsWith("\n")) pwd = pwd.Substring(0, pwd.Length - 1); if (args[0].Length > SALT_LEN) Fatal("salt too long"); var salt = args[0] + new string('\0', SALT_LEN - args[0].Length); for (var i = 1; i < args.Length; i++) { switch (args[i]) { case "-m": m_cost = (1U << (int)ReadArg(args, ++i, "-m", 1, 32)); break; case "-t": t_cost = ReadArg(args, ++i, "-t", 1, int.MaxValue); break; case "-p": threads = ReadArg(args, ++i, "-p", 1, 0xFFFFFF); break; case "-h": hash_len = ReadArg(args, ++i, "-h", 4, int.MaxValue); break; case "-d": type = Argon2Type.Argon2d; break; case "-e": case "-encoded": encodedOnly = true; break; case "-r": case "-raw": rawOnly = true; break; default: Fatal("unknown argument " + args[i]); break; } } if (encodedOnly && rawOnly) Fatal("Only one of -e or -r may be specified"); if (!encodedOnly && !rawOnly) { Console.WriteLine("Type:\t\t{0}", type); Console.WriteLine("Iterations:\t{0}", t_cost); Console.WriteLine("Memory:\t\t{0} KiB", m_cost); Console.WriteLine("Parallelism:\t{0}", threads); } var hasher = new PasswordHasher(t_cost, m_cost, threads, type, hash_len); Run(hasher, pwd, salt, rawOnly, encodedOnly); }