BCrypt implementation.

BCrypt implements OpenBSD-style Blowfish password hashing using the scheme described in "A Future- Adaptable Password Scheme" by Niels Provos and David Mazieres.

This password hashing system tries to thwart off-line password cracking using a computationally-intensive hashing algorithm, based on Bruce Schneier's Blowfish cipher. The work factor of the algorithm is parameterised, so it can be increased as computers get faster.

Usage is really simple. To hash a password for the first time, call the method with a random salt, like this:

string pw_hash = BCrypt.HashPassword(plain_password);

To check whether a plaintext password matches one that has been hashed previously, use the Verify method:

if (BCrypt.Verify(candidate_password, stored_hash)) Console.WriteLine("It matches"); else Console.WriteLine("It does not match");

The GenerateSalt() method takes an optional parameter (workFactor) that determines the computational complexity of the hashing:

string strong_salt = BCrypt.GenerateSalt(10); string stronger_salt = BCrypt.GenerateSalt(12);

The amount of work increases exponentially (2^workFactor), so each increment is twice as much work. The default workFactor is 10, and the valid range is 4 to 31.

Пример #1
0
        /// <summary>
        /// Compute hash using bcrypt_pdkdf function from Openssh.
        /// </summary>
        /// <param name="pass">The passphrase.</param>
        /// <param name="salt">The salt.</param>
        /// <param name="key">The key. Must be initalized to the size needed.</param>
        /// <param name="rounds">The number of rounds.</param>
        /// <remarks>
        /// pkcs #5 pbkdf2 implementation using the "bcrypt" hash
        ///
        /// The bcrypt hash function is derived from the bcrypt password hashing
        /// function with the following modifications:
        /// 1. The input password and salt are preprocessed with SHA512.
        /// 2. The output length is expanded to 256 bits.
        /// 3. Subsequently the magic string to be encrypted is lengthened and modifed
        ///    to "OxychromaticBlowfishSwatDynamite"
        /// 4. The hash function is defined to perform 64 rounds of initial state
        ///    expansion. (More rounds are performed by iterating the hash.)
        ///
        /// One modification from official pbkdf2. Instead of outputting key material
        /// linearly, we mix it. pbkdf2 has a known weakness where if one uses it to
        /// generate (e.g.) 512 bits of key material for use as two 256 bit keys, an
        /// attacker can merely run once through the outer loop, but the user
        /// always runs it twice. Shuffling output bytes requires computing the
        /// entirety of the key material to assemble any subkey. This is something a
        /// wise caller could do; we just do it for you.
        /// </remarks>
        public static void HashUsingOpensshBCryptPbkdf(char[] pass, byte[] salt, ref byte[] key, uint rounds)
        {
            if (pass == null) {
                throw new ArgumentNullException("pass");
            }
            if (salt == null) {
                throw new ArgumentNullException("salt");
            }
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            if (rounds < 1) {
                throw new ArgumentException("Must have at least one round", "rounds");
            }
            if (pass.Length == 0) {
                throw new ArgumentException("Empty password not allowed.", "pass");
            }
            if (salt.Length == 0 || salt.Length > (1 << 20)) {
                throw new ArgumentException("Bad salt size.", "salt");
            }
            if (key.Length == 0 || key.Length > (BCRYPT_HASHSIZE * BCRYPT_HASHSIZE)) {
                throw new ArgumentException("Bad key size.", "key");
            }

            var @out = new byte[BCRYPT_HASHSIZE];
            var countsalt = new byte[salt.Length + 4];
            var stride = (key.Length + @out.Length - 1) / @out.Length;
            var amt = (key.Length + stride - 1) / stride;

            Array.Copy(salt, countsalt, salt.Length);

            var sha512 = SHA512.Create();
            var bcrypt = new BCrypt();

            /* collapse password */
            var passBytes = Encoding.UTF8.GetBytes(pass);
            var sha2pass = sha512.ComputeHash(passBytes);
            Array.Clear(passBytes, 0, passBytes.Length);

            /* generate key, sizeof(out) at a time */
            int keylen = key.Length;
            for (uint count = 1; keylen > 0; count++) {
                countsalt[salt.Length + 0] = (byte)((count >> 24) & 0xff);
                countsalt[salt.Length + 1] = (byte)((count >> 16) & 0xff);
                countsalt[salt.Length + 2] = (byte)((count >> 8) & 0xff);
                countsalt[salt.Length + 3] = (byte)(count & 0xff);

                /* first round, salt is salt */
                var sha2salt = sha512.ComputeHash(countsalt);

                var tmpout = bcrypt.CryptUsingOpensshBcryptHash(sha2pass, sha2salt);
                Array.Copy(tmpout, @out, @out.Length);

                int i;
                for (i = 1; i < rounds; i++) {
                    /* subsequent rounds, salt is previous output */
                    sha2salt = sha512.ComputeHash(tmpout);
                    tmpout = bcrypt.CryptUsingOpensshBcryptHash(sha2pass, sha2salt);
                    for (int j = 0; j < @out.Length; j++)
                        @out[j] ^= tmpout[j];
                }

                /*
                 * pbkdf2 deviation: output the key material non-linearly.
                 */
                amt = Math.Min(amt, keylen);
                for (i = 0; i < amt; i++) {
                    var dest = i * stride + (count - 1);
                    if (dest >= key.Length)
                        break;
                    key[dest] = @out[i];
                }
                keylen -= i;
            }

            Array.Clear(@out, 0, @out.Length);
        }
Пример #2
0
        /// <summary>
        /// Compute hash using bcrypt_pdkdf function from Openssh.
        /// </summary>
        /// <param name="pass">The passphrase.</param>
        /// <param name="salt">The salt.</param>
        /// <param name="key">The key. Must be initalized to the size needed.</param>
        /// <param name="rounds">The number of rounds.</param>
        /// <remarks>
        /// pkcs #5 pbkdf2 implementation using the "bcrypt" hash
        ///
        /// The bcrypt hash function is derived from the bcrypt password hashing
        /// function with the following modifications:
        /// 1. The input password and salt are preprocessed with SHA512.
        /// 2. The output length is expanded to 256 bits.
        /// 3. Subsequently the magic string to be encrypted is lengthened and modifed
        ///    to "OxychromaticBlowfishSwatDynamite"
        /// 4. The hash function is defined to perform 64 rounds of initial state
        ///    expansion. (More rounds are performed by iterating the hash.)
        ///
        /// One modification from official pbkdf2. Instead of outputting key material
        /// linearly, we mix it. pbkdf2 has a known weakness where if one uses it to
        /// generate (e.g.) 512 bits of key material for use as two 256 bit keys, an
        /// attacker can merely run once through the outer loop, but the user
        /// always runs it twice. Shuffling output bytes requires computing the
        /// entirety of the key material to assemble any subkey. This is something a
        /// wise caller could do; we just do it for you.
        /// </remarks>
        public static void HashUsingOpensshBCryptPbkdf(char[] pass, byte[] salt, ref byte[] key, uint rounds)
        {
            if (pass == null)
            {
                throw new ArgumentNullException("pass");
            }
            if (salt == null)
            {
                throw new ArgumentNullException("salt");
            }
            if (key == null)
            {
                throw new ArgumentNullException("key");
            }
            if (rounds < 1)
            {
                throw new ArgumentException("Must have at least one round", "rounds");
            }
            if (pass.Length == 0)
            {
                throw new ArgumentException("Empty password not allowed.", "pass");
            }
            if (salt.Length == 0 || salt.Length > (1 << 20))
            {
                throw new ArgumentException("Bad salt size.", "salt");
            }
            if (key.Length == 0 || key.Length > (BCRYPT_HASHSIZE * BCRYPT_HASHSIZE))
            {
                throw new ArgumentException("Bad key size.", "key");
            }

            var @out      = new byte[BCRYPT_HASHSIZE];
            var countsalt = new byte[salt.Length + 4];
            var stride    = (key.Length + @out.Length - 1) / @out.Length;
            var amt       = (key.Length + stride - 1) / stride;

            Array.Copy(salt, countsalt, salt.Length);

            var sha512 = SHA512.Create();
            var bcrypt = new BCrypt();

            /* collapse password */
            var passBytes = Encoding.UTF8.GetBytes(pass);
            var sha2pass  = sha512.ComputeHash(passBytes);

            Array.Clear(passBytes, 0, passBytes.Length);

            /* generate key, sizeof(out) at a time */
            int keylen = key.Length;

            for (uint count = 1; keylen > 0; count++)
            {
                countsalt[salt.Length + 0] = (byte)((count >> 24) & 0xff);
                countsalt[salt.Length + 1] = (byte)((count >> 16) & 0xff);
                countsalt[salt.Length + 2] = (byte)((count >> 8) & 0xff);
                countsalt[salt.Length + 3] = (byte)(count & 0xff);

                /* first round, salt is salt */
                var sha2salt = sha512.ComputeHash(countsalt);

                var tmpout = bcrypt.CryptUsingOpensshBcryptHash(sha2pass, sha2salt);
                Array.Copy(tmpout, @out, @out.Length);

                int i;
                for (i = 1; i < rounds; i++)
                {
                    /* subsequent rounds, salt is previous output */
                    sha2salt = sha512.ComputeHash(tmpout);
                    tmpout   = bcrypt.CryptUsingOpensshBcryptHash(sha2pass, sha2salt);
                    for (int j = 0; j < @out.Length; j++)
                    {
                        @out[j] ^= tmpout[j];
                    }
                }

                /*
                 * pbkdf2 deviation: output the key material non-linearly.
                 */
                amt = Math.Min(amt, keylen);
                for (i = 0; i < amt; i++)
                {
                    var dest = i * stride + (count - 1);
                    if (dest >= key.Length)
                    {
                        break;
                    }
                    key[dest] = @out[i];
                }
                keylen -= i;
            }

            Array.Clear(@out, 0, @out.Length);
        }
Пример #3
0
        /// <summary>Hash a password using the OpenBSD bcrypt scheme.</summary>
        /// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
        ///                                     illegal values.</exception>
        /// <param name="input">The password to hash.</param>
        /// <param name="salt">    the salt to hash with (perhaps generated using BCrypt.gensalt).</param>
        /// <returns>The hashed password</returns>
        public static string HashPassword(string input, string salt)
        {
            if (input == null)
                throw new ArgumentNullException("input");

            if (string.IsNullOrEmpty(salt))
                throw new ArgumentException("Invalid salt", "salt");

            // Determinthe starting offset and validate the salt
            int startingOffset;
            char minor = (char)0;
            if (salt[0] != '$' || salt[1] != '2')
                throw new SaltParseException("Invalid salt version");
            if (salt[2] == '$')
                startingOffset = 3;
            else {
                minor = salt[2];
                if (minor != 'a' || salt[3] != '$')
                    throw new SaltParseException("Invalid salt revision");
                startingOffset = 4;
            }

            // Extract number of rounds
            if (salt[startingOffset + 2] > '$')
                throw new SaltParseException("Missing salt rounds");

            // Extract details from salt
            int logRounds = Convert.ToInt32(salt.Substring(startingOffset, 2));
            string extractedSalt = salt.Substring(startingOffset + 3, 22);

            byte[] inputBytes = Encoding.UTF8.GetBytes((input + (minor >= 'a' ? "\0" : "")));
            byte[] saltBytes = DecodeBase64(extractedSalt, BCRYPT_SALT_LEN);

            BCrypt bCrypt = new BCrypt();
            byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, logRounds);

            // Generate result string
            StringBuilder result = new StringBuilder();
            result.Append("$2");
            if (minor >= 'a')
                result.Append(minor);
            result.AppendFormat("${0:00}$", logRounds);
            result.Append(EncodeBase64(saltBytes, saltBytes.Length));
            result.Append(EncodeBase64(hashed, (_BfCryptCiphertext.Length * 4) - 1));
            return result.ToString();
        }
Пример #4
0
        /// <summary>Hash a password using the OpenBSD bcrypt scheme.</summary>
        /// <exception cref="ArgumentException">Thrown when one or more arguments have unsupported or
        ///                                     illegal values.</exception>
        /// <param name="input">The password to hash.</param>
        /// <param name="salt">    the salt to hash with (perhaps generated using BCrypt.gensalt).</param>
        /// <returns>The hashed password</returns>
        public static string HashPassword(string input, string salt)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }

            if (string.IsNullOrEmpty(salt))
            {
                throw new ArgumentException("Invalid salt", "salt");
            }

            // Determinthe starting offset and validate the salt
            int  startingOffset;
            char minor = (char)0;

            if (salt[0] != '$' || salt[1] != '2')
            {
                throw new SaltParseException("Invalid salt version");
            }
            if (salt[2] == '$')
            {
                startingOffset = 3;
            }
            else
            {
                minor = salt[2];
                if (minor != 'a' || salt[3] != '$')
                {
                    throw new SaltParseException("Invalid salt revision");
                }
                startingOffset = 4;
            }

            // Extract number of rounds
            if (salt[startingOffset + 2] > '$')
            {
                throw new SaltParseException("Missing salt rounds");
            }

            // Extract details from salt
            int    logRounds     = Convert.ToInt32(salt.Substring(startingOffset, 2));
            string extractedSalt = salt.Substring(startingOffset + 3, 22);

            byte[] inputBytes = Encoding.UTF8.GetBytes((input + (minor >= 'a' ? "\0" : "")));
            byte[] saltBytes  = DecodeBase64(extractedSalt, BCRYPT_SALT_LEN);

            BCrypt bCrypt = new BCrypt();

            byte[] hashed = bCrypt.CryptRaw(inputBytes, saltBytes, logRounds);

            // Generate result string
            StringBuilder result = new StringBuilder();

            result.Append("$2");
            if (minor >= 'a')
            {
                result.Append(minor);
            }
            result.AppendFormat("${0:00}$", logRounds);
            result.Append(EncodeBase64(saltBytes, saltBytes.Length));
            result.Append(EncodeBase64(hashed, (_BfCryptCiphertext.Length * 4) - 1));
            return(result.ToString());
        }