diff options
Diffstat (limited to 'comm/third_party/botan/src/lib/passhash/bcrypt')
3 files changed, 239 insertions, 0 deletions
diff --git a/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.cpp b/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.cpp new file mode 100644 index 0000000000..1d28ddfb4d --- /dev/null +++ b/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.cpp @@ -0,0 +1,181 @@ +/* +* Bcrypt Password Hashing +* (C) 2010,2018 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include <botan/bcrypt.h> +#include <botan/rng.h> +#include <botan/blowfish.h> +#include <botan/base64.h> +#include <botan/parsing.h> + +namespace Botan { + +namespace { + +std::string bcrypt_base64_encode(const uint8_t input[], size_t length) + { + // Bcrypt uses a non-standard base64 alphabet + const uint8_t 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<uint8_t>(b64[i])]; + + return b64; + } + +std::vector<uint8_t> bcrypt_base64_decode(std::string input) + { + const uint8_t 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<uint8_t>(input[i])]; + + return unlock(base64_decode(input)); + } + +std::string make_bcrypt(const std::string& pass, + const std::vector<uint8_t>& salt, + uint16_t work_factor, + char version) + { + /* + * On a 4 GHz Skylake, workfactor == 18 takes about 15 seconds to + * hash a password. This seems like a reasonable upper bound for the + * time being. + * Bcrypt allows up to work factor 31 (2^31 iterations) + */ + BOTAN_ARG_CHECK(work_factor >= 4 && work_factor <= 18, + "Invalid bcrypt work factor"); + + static const uint8_t BCRYPT_MAGIC[8*3] = { + 0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42, + 0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53, + 0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74 + }; + + Blowfish blowfish; + + // Include the trailing NULL byte, so we need c_str() not data() + blowfish.salted_set_key(cast_char_ptr_to_uint8(pass.c_str()), + pass.length() + 1, + salt.data(), + salt.size(), + work_factor); + + std::vector<uint8_t> ctext(BCRYPT_MAGIC, BCRYPT_MAGIC + 8*3); + + for(size_t i = 0; i != 64; ++i) + blowfish.encrypt_n(ctext.data(), ctext.data(), 3); + + std::string salt_b64 = bcrypt_base64_encode(salt.data(), salt.size()); + + std::string work_factor_str = std::to_string(work_factor); + if(work_factor_str.length() == 1) + work_factor_str = "0" + work_factor_str; + + return "$2" + std::string(1, version) + "$" + work_factor_str + + "$" + salt_b64.substr(0, 22) + + bcrypt_base64_encode(ctext.data(), ctext.size() - 1); + } + +} + +std::string generate_bcrypt(const std::string& pass, + RandomNumberGenerator& rng, + uint16_t work_factor, + char version) + { + /* + 2a, 2b and 2y are identical for our purposes because our implementation of 2a + never had the truncation or signed char bugs in the first place. + */ + + if(version != 'a' && version != 'b' && version != 'y') + throw Invalid_Argument("Unknown bcrypt version '" + std::string(1, version) + "'"); + + std::vector<uint8_t> salt; + rng.random_vec(salt, 16); + return make_bcrypt(pass, salt, work_factor, version); + } + +bool check_bcrypt(const std::string& pass, const std::string& hash) + { + if(hash.size() != 60 || + hash[0] != '$' || hash[1] != '2' || hash[3] != '$' || hash[6] != '$') + { + return false; + } + + const char bcrypt_version = hash[2]; + + if(bcrypt_version != 'a' && bcrypt_version != 'b' && bcrypt_version != 'y') + { + return false; + } + + const uint16_t workfactor = to_uint16(hash.substr(4, 2)); + + const std::vector<uint8_t> salt = bcrypt_base64_decode(hash.substr(7, 22)); + if(salt.size() != 16) + return false; + + const std::string compare = make_bcrypt(pass, salt, workfactor, bcrypt_version); + + return same_mem(hash.data(), compare.data(), compare.size()); + } + +} diff --git a/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.h b/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.h new file mode 100644 index 0000000000..cdf9cf3d19 --- /dev/null +++ b/comm/third_party/botan/src/lib/passhash/bcrypt/bcrypt.h @@ -0,0 +1,49 @@ +/* +* Bcrypt Password Hashing +* (C) 2011 Jack Lloyd +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_BCRYPT_H_ +#define BOTAN_BCRYPT_H_ + +#include <botan/types.h> +#include <string> + +namespace Botan { + +class RandomNumberGenerator; + +/** +* Create a password hash using Bcrypt +* +* @warning The password is truncated at at most 72 characters; characters after +* that do not have any effect on the resulting hash. To support longer +* passwords, consider pre-hashing the password, for example by using +* the hex encoding of SHA-256 of the password as the input to bcrypt. +* +* @param password the password. +* @param rng a random number generator +* @param work_factor how much work to do to slow down guessing attacks +* @param version which version to emit (may be 'a', 'b', or 'y' all of which +* have identical behavior in this implementation). +* +* @see https://www.usenix.org/events/usenix99/provos/provos_html/ +*/ +std::string BOTAN_PUBLIC_API(2,0) generate_bcrypt(const std::string& password, + RandomNumberGenerator& rng, + uint16_t work_factor = 12, + char version = 'a'); + +/** +* Check a previously created password hash +* @param password the password to check against +* @param hash the stored hash to check against +*/ +bool BOTAN_PUBLIC_API(2,0) check_bcrypt(const std::string& password, + const std::string& hash); + +} + +#endif diff --git a/comm/third_party/botan/src/lib/passhash/bcrypt/info.txt b/comm/third_party/botan/src/lib/passhash/bcrypt/info.txt new file mode 100644 index 0000000000..6be060ea0f --- /dev/null +++ b/comm/third_party/botan/src/lib/passhash/bcrypt/info.txt @@ -0,0 +1,9 @@ +<defines> +BCRYPT -> 20131128 +</defines> + +<requires> +blowfish +rng +base64 +</requires> |