From 88a892707f05625cbcf0c8cc4b3e0ed7852967b5 Mon Sep 17 00:00:00 2001 From: lloyd Date: Wed, 16 Feb 2011 21:45:13 +0000 Subject: Add support for bcrypt, the Blowfish-based password hashing scheme used in OpenBSD. Tested as compatible with a common Java implementation (http://www.mindrot.org/projects/jBCrypt/) --- src/block/blowfish/blowfish.cpp | 52 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) (limited to 'src/block/blowfish/blowfish.cpp') diff --git a/src/block/blowfish/blowfish.cpp b/src/block/blowfish/blowfish.cpp index ea227e93e..6610decd8 100644 --- a/src/block/blowfish/blowfish.cpp +++ b/src/block/blowfish/blowfish.cpp @@ -1,6 +1,6 @@ /* * Blowfish -* (C) 1999-2009 Jack Lloyd +* (C) 1999-2011 Jack Lloyd * * Distributed under the terms of the Botan license */ @@ -87,20 +87,61 @@ void Blowfish::key_schedule(const byte key[], size_t length) { clear(); + const byte null_salt[16] = { 0 }; + + key_expansion(key, length, null_salt); + } + +void Blowfish::key_expansion(const byte key[], + size_t length, + const byte salt[16]) + { for(size_t i = 0, j = 0; i != 18; ++i, j += 4) P[i] ^= make_u32bit(key[(j ) % length], key[(j+1) % length], key[(j+2) % length], key[(j+3) % length]); u32bit L = 0, R = 0; - generate_sbox(P, L, R); - generate_sbox(S, L, R); + generate_sbox(P, L, R, salt, 0); + generate_sbox(S, L, R, salt, 2); + } + +/* +* Modified key schedule used for bcrypt password hashing +*/ +void Blowfish::eks_key_schedule(const byte key[], size_t length, + const byte salt[16], size_t workfactor) + { + if(length == 0 || length >= 56) + throw Invalid_Key_Length("EKSBlowfish", length); + + if(workfactor == 0) + throw std::invalid_argument("Bcrypt work factor must be at least 1"); + + if(workfactor > 24) // ok? + throw std::invalid_argument("Requested Bcrypt work factor too large"); + + clear(); + + const byte null_salt[16] = { 0 }; + + key_expansion(key, length, salt); + + const size_t rounds = 1 << workfactor; + + for(size_t r = 0; r != rounds; ++r) + { + key_expansion(key, length, null_salt); + key_expansion(salt, 16, null_salt); + } } /* * Generate one of the Sboxes */ void Blowfish::generate_sbox(MemoryRegion& box, - u32bit& L, u32bit& R) const + u32bit& L, u32bit& R, + const byte salt[16], + size_t salt_off) const { const u32bit* S1 = &S[0]; const u32bit* S2 = &S[256]; @@ -109,6 +150,9 @@ void Blowfish::generate_sbox(MemoryRegion& box, for(size_t i = 0; i != box.size(); i += 2) { + L ^= load_be(salt, (i + salt_off) % 4); + R ^= load_be(salt, (i + salt_off + 1) % 4); + for(size_t j = 0; j != 16; j += 2) { L ^= P[j]; -- cgit v1.2.3 From 20b9d66dc60595e685d99f59a7ef306074d8e0b1 Mon Sep 17 00:00:00 2001 From: lloyd Date: Thu, 17 Feb 2011 19:41:49 +0000 Subject: Move password hashing schemes to src/passhash Set the upper limit on bcrypt hashing to workfactor 18, which takes about 25 seconds to run on my desktop machine. --- src/block/blowfish/blowfish.cpp | 7 +- src/constructs/bcrypt/bcrypt.cpp | 154 ---------------------------------- src/constructs/bcrypt/bcrypt.h | 37 -------- src/constructs/bcrypt/info.txt | 9 -- src/constructs/passhash/info.txt | 9 -- src/constructs/passhash/passhash9.cpp | 147 -------------------------------- src/constructs/passhash/passhash9.h | 51 ----------- src/passhash/bcrypt/bcrypt.cpp | 154 ++++++++++++++++++++++++++++++++++ src/passhash/bcrypt/bcrypt.h | 37 ++++++++ src/passhash/bcrypt/info.txt | 9 ++ src/passhash/passhash9/info.txt | 9 ++ src/passhash/passhash9/passhash9.cpp | 147 ++++++++++++++++++++++++++++++++ src/passhash/passhash9/passhash9.h | 51 +++++++++++ 13 files changed, 413 insertions(+), 408 deletions(-) delete mode 100644 src/constructs/bcrypt/bcrypt.cpp delete mode 100644 src/constructs/bcrypt/bcrypt.h delete mode 100644 src/constructs/bcrypt/info.txt delete mode 100644 src/constructs/passhash/info.txt delete mode 100644 src/constructs/passhash/passhash9.cpp delete mode 100644 src/constructs/passhash/passhash9.h create mode 100644 src/passhash/bcrypt/bcrypt.cpp create mode 100644 src/passhash/bcrypt/bcrypt.h create mode 100644 src/passhash/bcrypt/info.txt create mode 100644 src/passhash/passhash9/info.txt create mode 100644 src/passhash/passhash9/passhash9.cpp create mode 100644 src/passhash/passhash9/passhash9.h (limited to 'src/block/blowfish/blowfish.cpp') diff --git a/src/block/blowfish/blowfish.cpp b/src/block/blowfish/blowfish.cpp index 6610decd8..b6319eec0 100644 --- a/src/block/blowfish/blowfish.cpp +++ b/src/block/blowfish/blowfish.cpp @@ -117,7 +117,12 @@ void Blowfish::eks_key_schedule(const byte key[], size_t length, if(workfactor == 0) throw std::invalid_argument("Bcrypt work factor must be at least 1"); - if(workfactor > 24) // ok? + /* + * On a 2.8 GHz Core-i7, workfactor == 18 takes about 25 seconds to + * hash a password. This seems like a reasonable upper bound for the + * time being. + */ + if(workfactor > 18) throw std::invalid_argument("Requested Bcrypt work factor too large"); clear(); diff --git a/src/constructs/bcrypt/bcrypt.cpp b/src/constructs/bcrypt/bcrypt.cpp deleted file mode 100644 index b2152a0a7..000000000 --- a/src/constructs/bcrypt/bcrypt.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* -* Bcrypt Password Hashing -* (C) 2010 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace Botan { - -namespace { - -std::string bcrypt_base64_encode(const byte input[], size_t length) - { - // Bcrypt uses a non-standard base64 alphabet - const byte OPENBSD_BASE64_SUB[256] = { - 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x38, 0x80, 0x80, 0x80, 0x39, - 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x2E, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, - 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, - 0x76, 0x77, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80 - }; - - std::string b64 = base64_encode(input, length); - - while(b64.size() && b64[b64.size()-1] == '=') - b64 = b64.substr(0, b64.size() - 1); - - for(size_t i = 0; i != b64.size(); ++i) - b64[i] = OPENBSD_BASE64_SUB[static_cast(b64[i])]; - - return b64; - } - -MemoryVector bcrypt_base64_decode(std::string input) - { - const byte OPENBSD_BASE64_SUB[256] = { - 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x41, 0x42, - 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, - 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, - 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, - 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, - 0x7A, 0x30, 0x31, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, - 0x80, 0x80, 0x80, 0x80 - }; - - for(size_t i = 0; i != input.size(); ++i) - input[i] = OPENBSD_BASE64_SUB[static_cast(input[i])]; - - //return base64_decode(input); - Pipe pipe(new Base64_Decoder); - pipe.process_msg(input); - return pipe.read_all(); - } - -std::string make_bcrypt(const std::string& pass, - const MemoryRegion& salt, - u16bit work_factor) - { - const byte magic[24] = { - 0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42, - 0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53, - 0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74 - }; - - MemoryVector ctext(magic, 24); - - Blowfish blowfish; - - // Include the trailing NULL byte - blowfish.eks_key_schedule(reinterpret_cast(pass.c_str()), - pass.length() + 1, - salt, - work_factor); - - for(size_t i = 0; i != 64; ++i) - blowfish.encrypt_n(&ctext[0], &ctext[0], 3); - - std::string salt_b64 = bcrypt_base64_encode(&salt[0], salt.size()); - - return "$2a$" + to_string(work_factor, 2) + "$" + salt_b64.substr(0, 22) + - bcrypt_base64_encode(&ctext[0], ctext.size() - 1); - } - -} - -std::string generate_bcrypt(const std::string& pass, - RandomNumberGenerator& rng, - u16bit work_factor) - { - return make_bcrypt(pass, rng.random_vec(16), work_factor); - } - -bool check_bcrypt(const std::string& pass, const std::string& hash) - { - if(hash.size() != 60 || - hash[0] != '$' || hash[1] != '2' || hash[2] != 'a' || - hash[3] != '$' || hash[6] != '$') - { - return false; - } - - const size_t workfactor = to_u32bit(hash.substr(4, 2)); - - MemoryVector salt = bcrypt_base64_decode(hash.substr(7, 22)); - - const std::string compare = make_bcrypt(pass, salt, workfactor); - - return (hash == compare); - } - -} diff --git a/src/constructs/bcrypt/bcrypt.h b/src/constructs/bcrypt/bcrypt.h deleted file mode 100644 index 8a6ab58ea..000000000 --- a/src/constructs/bcrypt/bcrypt.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -* Bcrypt Password Hashing -* (C) 2011 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#ifndef BOTAN_BCRYPT_H__ -#define BOTAN_BCRYPT_H__ - -#include - -namespace Botan { - -/** -* Create a password hash using Bcrypt -* @param password the password -* @param rng a random number generator -* @param work_factor how much work to do to slow down guessing attacks -* -* @see http://www.usenix.org/events/usenix99/provos/provos_html/ -*/ -std::string BOTAN_DLL generate_bcrypt(const std::string& password, - RandomNumberGenerator& rng, - u16bit work_factor = 10); - -/** -* Check a previously created password hash -* @param password the password to check against -* @param hash the stored hash to check against -*/ -bool BOTAN_DLL check_bcrypt(const std::string& password, - const std::string& hash); - -} - -#endif diff --git a/src/constructs/bcrypt/info.txt b/src/constructs/bcrypt/info.txt deleted file mode 100644 index 91ab92e88..000000000 --- a/src/constructs/bcrypt/info.txt +++ /dev/null @@ -1,9 +0,0 @@ -define BCRYPT - - -libstate -blowfish -rng -base64 - - diff --git a/src/constructs/passhash/info.txt b/src/constructs/passhash/info.txt deleted file mode 100644 index f96809f29..000000000 --- a/src/constructs/passhash/info.txt +++ /dev/null @@ -1,9 +0,0 @@ -define PASSHASH9 - - -libstate -pbkdf2 -rng -base64 - - diff --git a/src/constructs/passhash/passhash9.cpp b/src/constructs/passhash/passhash9.cpp deleted file mode 100644 index e5f379052..000000000 --- a/src/constructs/passhash/passhash9.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/* -* Passhash9 Password Hashing -* (C) 2010 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#include -#include -#include -#include -#include -#include - -namespace Botan { - -namespace { - -const std::string MAGIC_PREFIX = "$9$"; - -const size_t WORKFACTOR_BYTES = 2; -const size_t ALGID_BYTES = 1; -const size_t SALT_BYTES = 12; // 96 bits of salt -const size_t PASSHASH9_PBKDF_OUTPUT_LEN = 24; // 192 bits output - -const byte PASSHASH9_DEFAULT_ALGO = 0; // HMAC(SHA-1) - -const size_t WORK_FACTOR_SCALE = 10000; - -MessageAuthenticationCode* get_pbkdf_prf(byte alg_id) - { - Algorithm_Factory& af = global_state().algorithm_factory(); - - try - { - if(alg_id == 0) - return af.make_mac("HMAC(SHA-1)"); - else if(alg_id == 1) - return af.make_mac("HMAC(SHA-256)"); - else if(alg_id == 2) - return af.make_mac("CMAC(Blowfish)"); - } - catch(Algorithm_Not_Found) {} - - return 0; - } - -} - -std::string generate_passhash9(const std::string& pass, - RandomNumberGenerator& rng, - u16bit work_factor) - { - return generate_passhash9(pass, PASSHASH9_DEFAULT_ALGO, rng, work_factor); - } - -std::string generate_passhash9(const std::string& pass, - byte alg_id, - RandomNumberGenerator& rng, - u16bit work_factor) - { - MessageAuthenticationCode* prf = get_pbkdf_prf(alg_id); - - if(!prf) - throw Invalid_Argument("Passhash9: Algorithm id " + to_string(alg_id) + - " is not defined"); - - PKCS5_PBKDF2 kdf(prf); // takes ownership of pointer - - SecureVector salt(SALT_BYTES); - rng.randomize(&salt[0], salt.size()); - - const size_t kdf_iterations = WORK_FACTOR_SCALE * work_factor; - - SecureVector pbkdf2_output = - kdf.derive_key(PASSHASH9_PBKDF_OUTPUT_LEN, - pass, - &salt[0], salt.size(), - kdf_iterations).bits_of(); - - Pipe pipe(new Base64_Encoder); - pipe.start_msg(); - pipe.write(alg_id); - pipe.write(get_byte(0, work_factor)); - pipe.write(get_byte(1, work_factor)); - pipe.write(salt); - pipe.write(pbkdf2_output); - pipe.end_msg(); - - return MAGIC_PREFIX + pipe.read_all_as_string(); - } - -bool check_passhash9(const std::string& pass, const std::string& hash) - { - const size_t BINARY_LENGTH = - ALGID_BYTES + - WORKFACTOR_BYTES + - PASSHASH9_PBKDF_OUTPUT_LEN + - SALT_BYTES; - - const size_t BASE64_LENGTH = - MAGIC_PREFIX.size() + (BINARY_LENGTH * 8) / 6; - - if(hash.size() != BASE64_LENGTH) - return false; - - for(size_t i = 0; i != MAGIC_PREFIX.size(); ++i) - if(hash[i] != MAGIC_PREFIX[i]) - return false; - - Pipe pipe(new Base64_Decoder); - pipe.start_msg(); - pipe.write(hash.c_str() + MAGIC_PREFIX.size()); - pipe.end_msg(); - - SecureVector bin = pipe.read_all(); - - if(bin.size() != BINARY_LENGTH) - return false; - - byte alg_id = bin[0]; - - const size_t kdf_iterations = - WORK_FACTOR_SCALE * load_be(&bin[ALGID_BYTES], 0); - - if(kdf_iterations == 0) - return false; - - MessageAuthenticationCode* pbkdf_prf = get_pbkdf_prf(alg_id); - - if(pbkdf_prf == 0) - return false; // unknown algorithm, reject - - PKCS5_PBKDF2 kdf(pbkdf_prf); // takes ownership of pointer - - SecureVector cmp = kdf.derive_key( - PASSHASH9_PBKDF_OUTPUT_LEN, - pass, - &bin[ALGID_BYTES + WORKFACTOR_BYTES], SALT_BYTES, - kdf_iterations).bits_of(); - - return same_mem(&cmp[0], - &bin[ALGID_BYTES + WORKFACTOR_BYTES + SALT_BYTES], - PASSHASH9_PBKDF_OUTPUT_LEN); - } - -} diff --git a/src/constructs/passhash/passhash9.h b/src/constructs/passhash/passhash9.h deleted file mode 100644 index 92cc391dc..000000000 --- a/src/constructs/passhash/passhash9.h +++ /dev/null @@ -1,51 +0,0 @@ -/* -* Passhash9 Password Hashing -* (C) 2010 Jack Lloyd -* -* Distributed under the terms of the Botan license -*/ - -#ifndef BOTAN_PASSHASH9_H__ -#define BOTAN_PASSHASH9_H__ - -#include - -namespace Botan { - -/** -* Create a password hash using PBKDF2 -* @param password the password -* @param rng a random number generator -* @param work_factor how much work to do to slow down guessing attacks -*/ -std::string BOTAN_DLL generate_passhash9(const std::string& password, - RandomNumberGenerator& rng, - u16bit work_factor = 10); - -/** -* Create a password hash using PBKDF2 -* @param password the password -* @param alg_id specifies which PRF to use with PBKDF2 -* 0 is HMAC(SHA-1) -* 1 is HMAC(SHA-256) -* 2 is CMAC(Blowfish) -* all other values are currently undefined -* @param rng a random number generator -* @param work_factor how much work to do to slow down guessing attacks -*/ -std::string BOTAN_DLL generate_passhash9(const std::string& password, - byte alg_id, - RandomNumberGenerator& rng, - u16bit work_factor = 10); - -/** -* Check a previously created password hash -* @param password the password to check against -* @param hash the stored hash to check against -*/ -bool BOTAN_DLL check_passhash9(const std::string& password, - const std::string& hash); - -} - -#endif diff --git a/src/passhash/bcrypt/bcrypt.cpp b/src/passhash/bcrypt/bcrypt.cpp new file mode 100644 index 000000000..e533c6081 --- /dev/null +++ b/src/passhash/bcrypt/bcrypt.cpp @@ -0,0 +1,154 @@ +/* +* Bcrypt Password Hashing +* (C) 2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Botan { + +namespace { + +std::string bcrypt_base64_encode(const byte input[], size_t length) + { + // Bcrypt uses a non-standard base64 alphabet + const byte OPENBSD_BASE64_SUB[256] = { + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x38, 0x80, 0x80, 0x80, 0x39, + 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x2E, 0x2F, 0x41, 0x42, 0x43, 0x44, 0x45, + 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, + 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, + 0x76, 0x77, 0x78, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 + }; + + std::string b64 = base64_encode(input, length); + + while(b64.size() && b64[b64.size()-1] == '=') + b64 = b64.substr(0, b64.size() - 1); + + for(size_t i = 0; i != b64.size(); ++i) + b64[i] = OPENBSD_BASE64_SUB[static_cast(b64[i])]; + + return b64; + } + +MemoryVector bcrypt_base64_decode(std::string input) + { + const byte OPENBSD_BASE64_SUB[256] = { + 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x41, 0x42, + 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, + 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, + 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7A, 0x30, 0x31, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 + }; + + for(size_t i = 0; i != input.size(); ++i) + input[i] = OPENBSD_BASE64_SUB[static_cast(input[i])]; + + //return base64_decode(input); + Pipe pipe(new Base64_Decoder); + pipe.process_msg(input); + return pipe.read_all(); + } + +std::string make_bcrypt(const std::string& pass, + const MemoryRegion& salt, + u16bit work_factor) + { + const byte magic[24] = { + 0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42, + 0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74 + }; + + MemoryVector ctext(magic, 24); + + Blowfish blowfish; + + // Include the trailing NULL byte + blowfish.eks_key_schedule(reinterpret_cast(pass.c_str()), + pass.length() + 1, + salt, + work_factor); + + for(size_t i = 0; i != 64; ++i) + blowfish.encrypt_n(&ctext[0], &ctext[0], 3); + + std::string salt_b64 = bcrypt_base64_encode(&salt[0], salt.size()); + + return "$2a$" + to_string(work_factor, 2) + "$" + salt_b64.substr(0, 22) + + bcrypt_base64_encode(&ctext[0], ctext.size() - 1); + } + +} + +std::string generate_bcrypt(const std::string& pass, + RandomNumberGenerator& rng, + u16bit work_factor) + { + return make_bcrypt(pass, rng.random_vec(16), work_factor); + } + +bool check_bcrypt(const std::string& pass, const std::string& hash) + { + if(hash.size() != 60 || + hash[0] != '$' || hash[1] != '2' || hash[2] != 'a' || + hash[3] != '$' || hash[6] != '$') + { + return false; + } + + const u16bit workfactor = to_u32bit(hash.substr(4, 2)); + + MemoryVector salt = bcrypt_base64_decode(hash.substr(7, 22)); + + const std::string compare = make_bcrypt(pass, salt, workfactor); + + return (hash == compare); + } + +} diff --git a/src/passhash/bcrypt/bcrypt.h b/src/passhash/bcrypt/bcrypt.h new file mode 100644 index 000000000..8a6ab58ea --- /dev/null +++ b/src/passhash/bcrypt/bcrypt.h @@ -0,0 +1,37 @@ +/* +* Bcrypt Password Hashing +* (C) 2011 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#ifndef BOTAN_BCRYPT_H__ +#define BOTAN_BCRYPT_H__ + +#include + +namespace Botan { + +/** +* Create a password hash using Bcrypt +* @param password the password +* @param rng a random number generator +* @param work_factor how much work to do to slow down guessing attacks +* +* @see http://www.usenix.org/events/usenix99/provos/provos_html/ +*/ +std::string BOTAN_DLL generate_bcrypt(const std::string& password, + RandomNumberGenerator& rng, + u16bit work_factor = 10); + +/** +* Check a previously created password hash +* @param password the password to check against +* @param hash the stored hash to check against +*/ +bool BOTAN_DLL check_bcrypt(const std::string& password, + const std::string& hash); + +} + +#endif diff --git a/src/passhash/bcrypt/info.txt b/src/passhash/bcrypt/info.txt new file mode 100644 index 000000000..91ab92e88 --- /dev/null +++ b/src/passhash/bcrypt/info.txt @@ -0,0 +1,9 @@ +define BCRYPT + + +libstate +blowfish +rng +base64 + + diff --git a/src/passhash/passhash9/info.txt b/src/passhash/passhash9/info.txt new file mode 100644 index 000000000..f96809f29 --- /dev/null +++ b/src/passhash/passhash9/info.txt @@ -0,0 +1,9 @@ +define PASSHASH9 + + +libstate +pbkdf2 +rng +base64 + + diff --git a/src/passhash/passhash9/passhash9.cpp b/src/passhash/passhash9/passhash9.cpp new file mode 100644 index 000000000..e5f379052 --- /dev/null +++ b/src/passhash/passhash9/passhash9.cpp @@ -0,0 +1,147 @@ +/* +* Passhash9 Password Hashing +* (C) 2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#include +#include +#include +#include +#include +#include + +namespace Botan { + +namespace { + +const std::string MAGIC_PREFIX = "$9$"; + +const size_t WORKFACTOR_BYTES = 2; +const size_t ALGID_BYTES = 1; +const size_t SALT_BYTES = 12; // 96 bits of salt +const size_t PASSHASH9_PBKDF_OUTPUT_LEN = 24; // 192 bits output + +const byte PASSHASH9_DEFAULT_ALGO = 0; // HMAC(SHA-1) + +const size_t WORK_FACTOR_SCALE = 10000; + +MessageAuthenticationCode* get_pbkdf_prf(byte alg_id) + { + Algorithm_Factory& af = global_state().algorithm_factory(); + + try + { + if(alg_id == 0) + return af.make_mac("HMAC(SHA-1)"); + else if(alg_id == 1) + return af.make_mac("HMAC(SHA-256)"); + else if(alg_id == 2) + return af.make_mac("CMAC(Blowfish)"); + } + catch(Algorithm_Not_Found) {} + + return 0; + } + +} + +std::string generate_passhash9(const std::string& pass, + RandomNumberGenerator& rng, + u16bit work_factor) + { + return generate_passhash9(pass, PASSHASH9_DEFAULT_ALGO, rng, work_factor); + } + +std::string generate_passhash9(const std::string& pass, + byte alg_id, + RandomNumberGenerator& rng, + u16bit work_factor) + { + MessageAuthenticationCode* prf = get_pbkdf_prf(alg_id); + + if(!prf) + throw Invalid_Argument("Passhash9: Algorithm id " + to_string(alg_id) + + " is not defined"); + + PKCS5_PBKDF2 kdf(prf); // takes ownership of pointer + + SecureVector salt(SALT_BYTES); + rng.randomize(&salt[0], salt.size()); + + const size_t kdf_iterations = WORK_FACTOR_SCALE * work_factor; + + SecureVector pbkdf2_output = + kdf.derive_key(PASSHASH9_PBKDF_OUTPUT_LEN, + pass, + &salt[0], salt.size(), + kdf_iterations).bits_of(); + + Pipe pipe(new Base64_Encoder); + pipe.start_msg(); + pipe.write(alg_id); + pipe.write(get_byte(0, work_factor)); + pipe.write(get_byte(1, work_factor)); + pipe.write(salt); + pipe.write(pbkdf2_output); + pipe.end_msg(); + + return MAGIC_PREFIX + pipe.read_all_as_string(); + } + +bool check_passhash9(const std::string& pass, const std::string& hash) + { + const size_t BINARY_LENGTH = + ALGID_BYTES + + WORKFACTOR_BYTES + + PASSHASH9_PBKDF_OUTPUT_LEN + + SALT_BYTES; + + const size_t BASE64_LENGTH = + MAGIC_PREFIX.size() + (BINARY_LENGTH * 8) / 6; + + if(hash.size() != BASE64_LENGTH) + return false; + + for(size_t i = 0; i != MAGIC_PREFIX.size(); ++i) + if(hash[i] != MAGIC_PREFIX[i]) + return false; + + Pipe pipe(new Base64_Decoder); + pipe.start_msg(); + pipe.write(hash.c_str() + MAGIC_PREFIX.size()); + pipe.end_msg(); + + SecureVector bin = pipe.read_all(); + + if(bin.size() != BINARY_LENGTH) + return false; + + byte alg_id = bin[0]; + + const size_t kdf_iterations = + WORK_FACTOR_SCALE * load_be(&bin[ALGID_BYTES], 0); + + if(kdf_iterations == 0) + return false; + + MessageAuthenticationCode* pbkdf_prf = get_pbkdf_prf(alg_id); + + if(pbkdf_prf == 0) + return false; // unknown algorithm, reject + + PKCS5_PBKDF2 kdf(pbkdf_prf); // takes ownership of pointer + + SecureVector cmp = kdf.derive_key( + PASSHASH9_PBKDF_OUTPUT_LEN, + pass, + &bin[ALGID_BYTES + WORKFACTOR_BYTES], SALT_BYTES, + kdf_iterations).bits_of(); + + return same_mem(&cmp[0], + &bin[ALGID_BYTES + WORKFACTOR_BYTES + SALT_BYTES], + PASSHASH9_PBKDF_OUTPUT_LEN); + } + +} diff --git a/src/passhash/passhash9/passhash9.h b/src/passhash/passhash9/passhash9.h new file mode 100644 index 000000000..92cc391dc --- /dev/null +++ b/src/passhash/passhash9/passhash9.h @@ -0,0 +1,51 @@ +/* +* Passhash9 Password Hashing +* (C) 2010 Jack Lloyd +* +* Distributed under the terms of the Botan license +*/ + +#ifndef BOTAN_PASSHASH9_H__ +#define BOTAN_PASSHASH9_H__ + +#include + +namespace Botan { + +/** +* Create a password hash using PBKDF2 +* @param password the password +* @param rng a random number generator +* @param work_factor how much work to do to slow down guessing attacks +*/ +std::string BOTAN_DLL generate_passhash9(const std::string& password, + RandomNumberGenerator& rng, + u16bit work_factor = 10); + +/** +* Create a password hash using PBKDF2 +* @param password the password +* @param alg_id specifies which PRF to use with PBKDF2 +* 0 is HMAC(SHA-1) +* 1 is HMAC(SHA-256) +* 2 is CMAC(Blowfish) +* all other values are currently undefined +* @param rng a random number generator +* @param work_factor how much work to do to slow down guessing attacks +*/ +std::string BOTAN_DLL generate_passhash9(const std::string& password, + byte alg_id, + RandomNumberGenerator& rng, + u16bit work_factor = 10); + +/** +* Check a previously created password hash +* @param password the password to check against +* @param hash the stored hash to check against +*/ +bool BOTAN_DLL check_passhash9(const std::string& password, + const std::string& hash); + +} + +#endif -- cgit v1.2.3