/// <summary> /// Checks whether a passed in password has been pwned previously. /// Determines if the password is safe (never been pwned), /// at risk (pwned 1-10 times), or unnacceptable(pwned > 10 times). /// </summary> /// <param name="password">A password</param> /// <returns>PasswordStatus which conatins a number and message that determines the security risk. /// 0 - no risk. 1 - small risk/recommend change password. 2 - risky/password not accepted /// -1 - an error occurred in the http request </returns> public async Task <PasswordStatus> IsPasswordSafe(string password) { if (password.Length < 12) { PasswordStatus status = new PasswordStatus(-2, "That's not a valid password!"); return(status); } else { //Password converted to bytes and hashed. string stringHash = sHash.StringHash(password); //Gets the first five characters of the hashed password (prefix) string prefix = stringHash.Substring(0, 5); //Calls FindPassword to see all pwned passwords that have the same prefix //checkPwned is Task<String> var checkPwned = await pvs.FindPassword(prefix); //restOfHash is part of password hash that is not the prefix string restOfHash = stringHash.Substring(5, stringHash.Length - 5); //Check if password has been pwned and if its usable. Get object with int representing status & a message. //Status number and message are stored in PasswordStatus object PasswordStatus status = checkStatus.StatusOfPassword(checkPwned, restOfHash); return(status); } }
/// <summary> /// Gets the status of the password. Gets both a status number and a status message. /// </summary> /// <param name="checkPwned"></param> /// <param name="restOfHash"></param> /// <returns>A PasswordStatus object containg the status number and message.</returns> public PasswordStatus StatusOfPassword(string checkPwned, string restOfHash) { int statusNumber; //There was an error so checkPwned is empty. if (string.IsNullOrEmpty(checkPwned)) { statusNumber = -1; } //Rest of the hashed password exists in checkpwned else if (checkPwned.Contains(restOfHash)) { int index = checkPwned.IndexOf(restOfHash); //Represents a colon followed by 1 or more digits Regex colon = new Regex(@":(\d+)"); // will search string for first occurence of given expression, starting from index Match counter = colon.Match(checkPwned, index); //Number of times password has been pwned is parsed. int.TryParse(counter.Value.Substring(1), out int result); //Password has been breached a few times(1-10) before, recommend change password. if (result > 0 && result < 11) { statusNumber = 1; } //Password breached more than 10 times, must be changed. else { statusNumber = 2; } } //Password has not been pwned previously. else { statusNumber = 0; } String statusMessage = StatusMessages(statusNumber); PasswordStatus status = new PasswordStatus(statusNumber, statusMessage); return(status); }