Example #1
0
        /// <summary>
        /// Checks whether the provided password is in the Enzoic database of known, compromised passwords.
        /// @see <a href="https://www.enzoic.com/docs/passwords-api">https://www.enzoic.com/docs/passwords-api</a>
        /// </summary>
        /// <param name="password">The password to be checked</param>
        /// <param name="revealedInExposure">Out parameter.  Whether the password was exposed in a known data Exposure. If this value
        /// is false, the password was found in common password cracking dictionaries, but has not been directly exposed as a user
        /// password in a data breach or other Exposure.</param>
        /// <param name="relativeExposureFrequency">This is a gauge of how frequently the password has been seen in data breaches.
        /// The value is simply the percent of data
        /// breaches indexed by Enzoic that have contained at least one instance of this password, i.e. if the value is 13,
        /// that means 13% of the exposures that Enzoic has indexed contained this password at least one time. This value can
        /// be used to gauge how dangerous this password is by how common it is.</param>
        /// <returns>True if the password is a known, compromised password and should not be used</returns>
        public bool CheckPassword(string password, out bool revealedInExposure, out int?relativeExposureFrequency)
        {
            string md5    = Hashing.CalcMD5(password);
            string sha1   = Hashing.CalcSHA1(password);
            string sha256 = Hashing.CalcSHA256(password);

            String response = MakeRestCall(
                apiBaseURL + PASSWORDS_API_PATH +
                "?partial_md5=" + md5.Substring(0, 10) +
                "&partial_sha1=" + sha1.Substring(0, 10) +
                "&partial_sha256=" + sha256.Substring(0, 10),
                "GET", null);

            if (response != "404")
            {
                dynamic responseObj = JObject.Parse(response);

                foreach (dynamic candidate in responseObj.candidates)
                {
                    if (candidate.md5 == md5 ||
                        candidate.sha1 == sha1 ||
                        candidate.sha256 == sha256)
                    {
                        revealedInExposure        = candidate.revealedInExposure;
                        relativeExposureFrequency = candidate.relativeExposureFrequency;
                        return(true);
                    }
                }
            }

            revealedInExposure        = false;
            relativeExposureFrequency = null;
            return(false);
        }
Example #2
0
        /// <summary>
        /// Calls the Enzoic CheckCredentials API in a secure fashion to check whether the provided username and password
        /// are known to be compromised.
        /// This call is made securely to the server - only a salted and hashed representation of the credentials are passed and
        /// the salt value is not passed along with it.
        /// @see <a href="https://www.enzoic.com/docs/credentials-api">https://www.enzoic.com/docs/credentials-api</a>
        /// </summary>
        /// <param name="username">the username to check - may be an email address or username</param>
        /// <param name="password">the password to check</param>
        /// <param name="lastCheckDate">(Optional) The timestamp for the last check you performed for this user.  If the date/time you provide
        /// for the last check is greater than the timestamp Enzoic has for the last breach affecting this user, the check will
        /// not be performed.This can be used to substantially increase performance.Can be set to null if no last check was performed
        /// or the credentials have changed since.</param>
        /// <param name="excludeHashTypes">(Optional) An array of PasswordTypes to ignore when calculating hashes for the credentials check.
        /// By excluding computationally expensive PasswordTypes, such as BCrypt, it is possible to balance the performance of this
        /// call against security.Can be set to null if you don't wish to exclude any hash types.</param>
        /// <returns>true if the credentials are known to be compromised, false otherwise</returns>
        public bool CheckCredentials(string username, string password, DateTime?lastCheckDate = null,
                                     PasswordType[] excludeHashTypes = null)
        {
            String response = MakeRestCall(
                apiBaseURL + ACCOUNTS_API_PATH + "?username="******"GET", null);

            if (response == "404")
            {
                // this is all we needed to check for this - email wasn't even in the DB
                return(false);
            }

            // deserialize response
            AccountsResponse accountsResponse = JsonConvert.DeserializeObject <AccountsResponse>(response);

            // see if the lastCheckDate was later than the lastBreachDate - if so bail out
            if (lastCheckDate.HasValue && lastCheckDate.Value >= accountsResponse.lastBreachDate)
            {
                return(false);
            }

            int bcryptCount = 0;

            List <string> credentialHashes = new List <string>();
            StringBuilder queryString      = new StringBuilder();

            foreach (PasswordHashSpecification hashSpec in accountsResponse.PasswordHashesRequired)
            {
                if (excludeHashTypes != null && excludeHashTypes.Contains(hashSpec.HashType))
                {
                    // this type is excluded
                    continue;
                }

                // bcrypt gets far too expensive for good response time if there are many of them to calculate.
                // some mostly garbage accounts have accumulated a number of them in our DB and if we happen to hit one it
                // kills performance, so short circuit out after at most 2 BCrypt hashes
                if (hashSpec.HashType != PasswordType.BCrypt || bcryptCount <= 2)
                {
                    if (hashSpec.HashType == PasswordType.BCrypt)
                    {
                        bcryptCount++;
                    }

                    String credentialHash = CalcCredentialHash(username, password, accountsResponse.Salt, hashSpec);

                    if (credentialHash != null)
                    {
                        credentialHashes.Add(credentialHash);
                        if (queryString.Length == 0)
                        {
                            queryString.Append("?partialHashes=").Append(credentialHash.Substring(0, 10));
                        }
                        else
                        {
                            queryString.Append("&partialHashes=").Append(credentialHash.Substring(0, 10));
                        }
                    }
                }
            }

            if (queryString.Length > 0)
            {
                String credsResponse = MakeRestCall(
                    apiBaseURL + CREDENTIALS_API_PATH + queryString, "GET", null);

                if (credsResponse != "404")
                {
                    // loop through candidate hashes returned and see if we have a match with the exact hash
                    dynamic responseObj = JObject.Parse(credsResponse);

                    foreach (dynamic candidate in responseObj.candidateHashes)
                    {
                        if (credentialHashes.FirstOrDefault(hash => hash == candidate.ToString()) != null)
                        {
                            return(true);
                        }
                    }
                }
            }

            return(false);
        }