Exemple #1
0
        /// <summary>
        /// Hash the password with different algorithms depending on the HashVersion, and optionally append the version string.
        /// Defaults to using Bcrypt, and appends the version string by default.
        /// This method should only be used for testing, and generation of the initial intermediate hashes.
        /// If version is HashVersion.SHA256, then input MUST be an SHA256 hash of the original password.
        /// </summary>
        /// <param name="input">String to hash</param>
        /// <param name="version">HashVersion to use for hashing</param>
        /// <param name="addVersion">Append the version string to the hash. Defaults true</param>
        /// <returns>Hashed string with appended version</returns>
        public static string CreateHashWithVersion(string input, HashVersionEnum version = HashVersionEnum.Bcrypt, bool addVersion = true)
        {
            string hash;

            switch (version)
            {
            case HashVersionEnum.SHA256:
                // Use original SHA256 hashing.
                hash = CreateSHA256Hash(input);
                break;

            case HashVersionEnum.Intermediate_SHA256_Bcrypt:
                // Use intermediate hashing algorithm.
                // The input MUST be an SHA256 hash of the original password.
                hash = CreateBcryptHash(input);
                break;

            case HashVersionEnum.Bcrypt:
            default:
                // Otherwise we always want to hash with Bcrypt.
                hash    = CreateBcryptHash(input);
                version = HashVersionEnum.Bcrypt;
                break;
            }
            // Optionally append Hash Version to hashed Password.
            if (addVersion)
            {
                hash += CreateHashVersionString(version);
            }
            return(hash);
        }
Exemple #2
0
        /// <summary>
        /// Parse a version string into a HashVersion Enum. The version string should be an integer as string.
        /// If parsing fails, the referenced HashVersion will stay unchanged.
        /// </summary>
        /// <param name="versionString">Version of password hash.</param>
        /// <param name="hashVersion">Current HashVersion</param>
        /// <returns>HashVersion enum</returns>
        public static void TryParseHashVersion(string versionString, ref HashVersionEnum hashVersion)
        {
            // 0 is both the default int value and a HashVersion with value "Unknown", so use 0 as default.
            int intVersion = 0;

            // We're using int.Parse inside a Try/Catch instead of int.TryParse because the
            // build tool is failing to build code with int.TryParse.
            try
            {
                intVersion = int.Parse(versionString);
            }
            // If we catch an error, then the version is obviously invalid, so return false.
            catch
            {
                return;
            }

            // Check that the version is a valid HashVersion, and return false if not.
            // We use Enum.IsDefined because Enum.TryParse returns true for any numeric value.
            // https://stackoverflow.com/questions/6741649/enum-tryparse-returns-true-for-any-numeric-values
            bool isDefined = Enum.IsDefined(typeof(HashVersionEnum), intVersion);

            if (!isDefined)
            {
                return;
            }

            // intVersion is valid, so cast to HashVersion and return true.
            hashVersion = (HashVersionEnum)intVersion;
        }
        /// <summary>
        /// Verify that a Password matches the hashed Password.
        /// </summary>
        /// <param name="hashedPassword">Hashed Password</param>
        /// <param name="providedPassword">Password to verify against hashed Password</param>
        /// <returns></returns>
        public PasswordVerificationResult VerifyHashedPassword(TUser user, string hashedPassword, string providedPassword)
        {
            // Verification should fail if the provided Password is null, empty, or whitespace.
            if (string.IsNullOrEmpty(providedPassword))
            {
                return(PasswordVerificationResult.Failed);
            }

            // Verification should fail if the hashed Password is null, empty, whitespace, or the string "$deleted$".
            if (string.IsNullOrEmpty(hashedPassword) || hashedPassword.Trim() == "$deleted$")
            {
                return(PasswordVerificationResult.Failed);
            }

            // Get the current Hash Version and the Hashed Password without the version string.
            Tuple <string, HashVersionEnum> result = HashHelpers.GetHashVersion(hashedPassword);
            string          currentHash            = result.Item1;
            HashVersionEnum hashVersion            = result.Item2;

            // Verify the provided Password against the current Hash.
            try
            {
                switch (hashVersion)
                {
                case HashVersionEnum.Unknown:
                    // We have an invalid or Unknown HashVersion, so we cannot verify the password.
                    // This case should not be hit if we hash our passwords correctly, but should be included as a precaution.
                    return(PasswordVerificationResult.Failed);

                case HashVersionEnum.SHA256:
                    // Use original SHA256 hashing, return SuccessRehashNeeded if valid.
                    return(VerifySHA256Hash(providedPassword, currentHash) ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed);

                case HashVersionEnum.Intermediate_SHA256_Bcrypt:
                    // Use intermediate hashing algorithm, pass SHA256 hash of password as input to Bcrypt.
                    // Return SuccessRehashNeeded if valid.
                    return(VerifyBcryptHash(HashHelpers.CreateSHA256Hash(providedPassword), currentHash) ? PasswordVerificationResult.SuccessRehashNeeded : PasswordVerificationResult.Failed);

                case HashVersionEnum.Bcrypt:
                default:
                    // Otherwise we always want to verify with Bcrypt by default. Return Success if valid.
                    return(VerifyBcryptHash(providedPassword, currentHash) ? PasswordVerificationResult.Success : PasswordVerificationResult.Failed);
                }
            }
            catch (SaltParseException)
            {
                return(PasswordVerificationResult.Failed);
            }
        }
Exemple #4
0
        /// <summary>
        /// Parses a Hashed Password string for a Hash Version and the original Password Hash.
        /// Returns the split original Password Hash and the Hash Version as a Tuple.
        /// A Hash Version is a $ symbol followed by a number, appended to the Password Hash. e.g. "$1".
        /// SHA256 is returned as the default Hash Version, unless the Hash has a valid Bcrypt prefix.
        /// </summary>
        /// <param name="hashedPassword">A hashed password string, possibly including appended version</param>
        /// <returns>Tuple of original hashed password with appended version string removed, and HashVersion enum</returns>
        public static Tuple <string, HashVersionEnum> GetHashVersion(string hashedPassword)
        {
            // Use Unknown as the default HashVersion.
            HashVersionEnum version      = HashVersionEnum.Unknown;
            string          passwordHash = "";

            // If the Hashed Password starts with "$2" then the Password has already been encrypted with Bcrypt.
            // Therefore we will set the version to Bcrypt in case the Hashed Password doesn't have a version string, or an invalid string.
            if (hashedPassword.StartsWith("$2"))
            {
                version = HashVersionEnum.Bcrypt;
            }
            // Else, we check if the Hashed Password matches the SHA256 regex.
            // Again, we set the version to SHA256 in case the Hashed Password doesn't have a version string, or an invalid string.
            else if (MatchesSHA256(hashedPassword))
            {
                version = HashVersionEnum.SHA256;
            }

            // Our custom hash version will be a single character after the last $ symbol.
            // The $ symbol is the designated delimiter between sections in a hash string.
            int    lastDollarSignIndex = hashedPassword.LastIndexOf('$');
            string versionString       = hashedPassword.Substring(lastDollarSignIndex + 1);

            // If the versionString has a length of 1, then it should be our custom hash version.
            if (versionString.Length == 1)
            {
                // Safely parse the version number into a HashVersion enum.
                TryParseHashVersion(versionString, ref version);
            }

            // Get a count of all $ symbols in the hashed password.
            int dollarSignCount = hashedPassword.Count(f => f == '$');

            // If there are 4 or more $ symbols, then the password is using Bcrypt, and we have a version string appended.
            // Likewise, if there is only one $ symbol, the password is not hashed with Bcrypt, but we still have a version string.
            // In both cases, we need to return the hashed string without the appended version string.
            if (dollarSignCount >= 4 || dollarSignCount == 1)
            {
                passwordHash = hashedPassword.Substring(0, lastDollarSignIndex);
            }
            else
            {
                passwordHash = hashedPassword;
            }

            return(new Tuple <string, HashVersionEnum>(passwordHash, version));
        }
Exemple #5
0
 /// <summary>
 /// Creates a HashVersion string with $ sign. e.g. "$1", "$2"
 /// </summary>
 /// <param name="version">HashVersion enum</param>
 /// <returns>HashVersion string</returns>
 public static string CreateHashVersionString(HashVersionEnum version)
 {
     return("$" + (int)version);
 }