/// <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); }
/// <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); }
/// <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(); }
/// <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()); }