Botan 3.13.0
Crypto and TLS for C&
bcrypt.cpp
Go to the documentation of this file.
1/*
2* Bcrypt Password Hashing
3* (C) 2010,2018,2020 Jack Lloyd
4*
5* Botan is released under the Simplified BSD License (see license.txt)
6*/
7
8#include <botan/bcrypt.h>
9
10#include <botan/base64.h>
11#include <botan/exceptn.h>
12#include <botan/mem_ops.h>
13#include <botan/rng.h>
14#include <botan/internal/blowfish.h>
15#include <botan/internal/ct_utils.h>
16#include <botan/internal/fmt.h>
17#include <botan/internal/int_utils.h>
18#include <botan/internal/mem_utils.h>
19#include <botan/internal/parsing.h>
20
21namespace Botan {
22
23namespace {
24
25// Bcrypt uses a non-standard base64 alphabet
26uint8_t base64_to_bcrypt_encoding(uint8_t c) {
27 const auto is_ab = CT::Mask<uint8_t>::is_within_range(c, 'a', 'b');
28 const auto is_cz = CT::Mask<uint8_t>::is_within_range(c, 'c', 'z');
29 const auto is_CZ = CT::Mask<uint8_t>::is_within_range(c, 'C', 'Z');
30
31 const auto is_01 = CT::Mask<uint8_t>::is_within_range(c, '0', '1');
32 const auto is_29 = CT::Mask<uint8_t>::is_within_range(c, '2', '9');
33
34 const auto is_A = CT::Mask<uint8_t>::is_equal(c, 'A');
35 const auto is_B = CT::Mask<uint8_t>::is_equal(c, 'B');
36 const auto is_plus = CT::Mask<uint8_t>::is_equal(c, '+');
37 const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
38
39 uint8_t ret = 0x80;
40 ret = is_ab.select(c - 'a' + 'Y', ret);
41 ret = is_cz.select(c - 2, ret);
42 ret = is_CZ.select(c - 2, ret);
43 ret = is_01.select(c - '0' + 'y', ret);
44 ret = is_29.select(c - '2' + '0', ret);
45 ret = is_A.select('.', ret);
46 ret = is_B.select('/', ret);
47 ret = is_plus.select('8', ret);
48 ret = is_slash.select('9', ret);
49
50 return ret;
51}
52
53uint8_t bcrypt_encoding_to_base64(uint8_t c) {
54 const auto is_ax = CT::Mask<uint8_t>::is_within_range(c, 'a', 'x');
55 const auto is_yz = CT::Mask<uint8_t>::is_within_range(c, 'y', 'z');
56
57 const auto is_AX = CT::Mask<uint8_t>::is_within_range(c, 'A', 'X');
58 const auto is_YZ = CT::Mask<uint8_t>::is_within_range(c, 'Y', 'Z');
59 const auto is_07 = CT::Mask<uint8_t>::is_within_range(c, '0', '7');
60
61 const auto is_8 = CT::Mask<uint8_t>::is_equal(c, '8');
62 const auto is_9 = CT::Mask<uint8_t>::is_equal(c, '9');
63 const auto is_dot = CT::Mask<uint8_t>::is_equal(c, '.');
64 const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
65
66 uint8_t ret = 0x80;
67 ret = is_ax.select(c - 'a' + 'c', ret);
68 ret = is_yz.select(c - 'y' + '0', ret);
69 ret = is_AX.select(c - 'A' + 'C', ret);
70 ret = is_YZ.select(c - 'Y' + 'a', ret);
71 ret = is_07.select(c - '0' + '2', ret);
72 ret = is_8.select('+', ret);
73 ret = is_9.select('/', ret);
74 ret = is_dot.select('A', ret);
75 ret = is_slash.select('B', ret);
76
77 return ret;
78}
79
80std::string bcrypt_base64_encode(std::span<const uint8_t> input) {
81 std::string b64 = base64_encode(input);
82
83 while(!b64.empty() && b64.back() == '=') {
84 b64.pop_back();
85 }
86
87 for(char& c : b64) {
88 c = static_cast<char>(base64_to_bcrypt_encoding(static_cast<uint8_t>(c)));
89 }
90
91 return b64;
92}
93
94std::vector<uint8_t> bcrypt_base64_decode(std::string_view input) {
95 std::string translated;
96 for(const char c : input) {
97 translated.push_back(bcrypt_encoding_to_base64(static_cast<uint8_t>(c)));
98 }
99
100 return unlock(base64_decode(translated));
101}
102
103std::string make_bcrypt(std::string_view pass, std::span<const uint8_t> salt, uint16_t work_factor, char version) {
104 /*
105 * On a 4 GHz Skylake, workfactor == 18 takes about 15 seconds to
106 * hash a password. This seems like a reasonable upper bound for the
107 * time being.
108 * Bcrypt allows up to work factor 31 (2^31 iterations)
109 */
110 BOTAN_ARG_CHECK(work_factor >= 4 && work_factor <= 18, "Invalid bcrypt work factor");
111
112 alignas(64) static const uint8_t BCRYPT_MAGIC[8 * 3] = {0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42,
113 0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53,
114 0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74};
115
116 Blowfish blowfish;
117
118 // Bcrypt is defined with the key including the trailing NULL so we must copy it to a local
119 // variable since std::string_view is not necessarily NULL terminated.
120 const size_t pass_w_null_len = add_or_throw<size_t>(pass.size(), 1, "bcrypt password is too long");
121 secure_vector<uint8_t> pass_w_null(pass_w_null_len);
122 copy_mem(std::span{pass_w_null}.first(pass.size()), as_span_of_bytes(pass));
123
124 blowfish.salted_set_key(pass_w_null.data(), pass_w_null.size(), salt.data(), salt.size(), work_factor);
125
126 std::vector<uint8_t> ctext(BCRYPT_MAGIC, BCRYPT_MAGIC + 8 * 3);
127
128 for(size_t i = 0; i != 64; ++i) {
129 blowfish.encrypt_n(ctext.data(), ctext.data(), 3);
130 }
131
132 const std::string salt_b64 = bcrypt_base64_encode(salt);
133
134 std::string work_factor_str = std::to_string(work_factor);
135 if(work_factor_str.length() == 1) {
136 work_factor_str = "0" + work_factor_str;
137 }
138
139 return fmt("$2{}${}${}{}",
140 version,
141 work_factor_str,
142 salt_b64.substr(0, 22),
143 bcrypt_base64_encode(std::span{ctext}.first(ctext.size() - 1)));
144}
145
146} // namespace
147
148std::string generate_bcrypt(std::string_view pass, RandomNumberGenerator& rng, uint16_t work_factor, char version) {
149 /*
150 2a, 2b and 2y are identical for our purposes because our implementation of 2a
151 never had the truncation or signed char bugs in the first place.
152 */
153
154 if(version != 'a' && version != 'b' && version != 'y') {
155 throw Invalid_Argument("Unknown bcrypt version '" + std::string(1, version) + "'");
156 }
157
158 std::vector<uint8_t> salt;
159 rng.random_vec(salt, 16);
160 return make_bcrypt(pass, salt, work_factor, version);
161}
162
163bool check_bcrypt(std::string_view pass, std::string_view hash) {
164 if(hash.size() != 60 || hash[0] != '$' || hash[1] != '2' || hash[3] != '$' || hash[6] != '$') {
165 return false;
166 }
167
168 const char bcrypt_version = hash[2];
169
170 if(bcrypt_version != 'a' && bcrypt_version != 'b' && bcrypt_version != 'y') {
171 return false;
172 }
173
174 // bcrypt workfactor spec is always two characters
175 const auto workfactor = parse_u16(hash.substr(4, 2));
176
177 // bcrypt does support larger range of workfactors, this is what make_bcrypt allows
178 if(!workfactor.has_value() || *workfactor < 4 || *workfactor > 18) {
179 return false;
180 }
181
182 const std::vector<uint8_t> salt = bcrypt_base64_decode(hash.substr(7, 22));
183 if(salt.size() != 16) {
184 return false;
185 }
186
187 const std::string compare = make_bcrypt(pass, salt, *workfactor, bcrypt_version);
188
189 return CT::is_equal(as_span_of_bytes(hash), as_span_of_bytes(compare)).as_bool();
190}
191
192} // namespace Botan
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static constexpr Mask< T > is_within_range(T v, T l, T u)
Definition ct_utils.h:470
static constexpr Mask< T > is_equal(T x, T y)
Definition ct_utils.h:442
void random_vec(std::span< uint8_t > v)
Definition rng.h:244
constexpr CT::Mask< T > is_equal(const T x[], const T y[], size_t len)
Definition ct_utils.h:798
constexpr T add_or_throw(T a, T b, std::string_view msg)
Definition int_utils.h:66
std::span< const uint8_t > as_span_of_bytes(const char *s, size_t len)
Definition mem_utils.h:59
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
std::string generate_bcrypt(std::string_view pass, RandomNumberGenerator &rng, uint16_t work_factor, char version)
Definition bcrypt.cpp:148
size_t base64_encode(char out[], const uint8_t in[], size_t input_length, size_t &input_consumed, bool final_inputs)
Definition base64.cpp:161
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
size_t base64_decode(uint8_t out[], const char in[], size_t input_length, size_t &input_consumed, bool final_inputs, bool ignore_ws)
Definition base64.cpp:169
std::vector< T > unlock(const secure_vector< T > &in)
Definition secmem.h:155
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:128
std::optional< uint16_t > parse_u16(std::string_view input, bool require_canonical)
Definition parsing.cpp:60
bool check_bcrypt(std::string_view pass, std::string_view hash)
Definition bcrypt.cpp:163