Botan 3.0.0-alpha0
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#include <botan/rng.h>
10#include <botan/internal/blowfish.h>
11#include <botan/base64.h>
12#include <botan/internal/parsing.h>
13#include <botan/internal/ct_utils.h>
14
15namespace Botan {
16
17namespace {
18
19// Bcrypt uses a non-standard base64 alphabet
20uint8_t base64_to_bcrypt_encoding(uint8_t c)
21 {
22 const auto is_ab = CT::Mask<uint8_t>::is_within_range(c, 'a', 'b');
23 const auto is_cz = CT::Mask<uint8_t>::is_within_range(c, 'c', 'z');
24 const auto is_CZ = CT::Mask<uint8_t>::is_within_range(c, 'C', 'Z');
25
26 const auto is_01 = CT::Mask<uint8_t>::is_within_range(c, '0', '1');
27 const auto is_29 = CT::Mask<uint8_t>::is_within_range(c, '2', '9');
28
29 const auto is_A = CT::Mask<uint8_t>::is_equal(c, 'A');
30 const auto is_B = CT::Mask<uint8_t>::is_equal(c, 'B');
31 const auto is_plus = CT::Mask<uint8_t>::is_equal(c, '+');
32 const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
33
34 uint8_t ret = 0x80;
35 ret = is_ab.select(c - 'a' + 'Y', ret);
36 ret = is_cz.select(c - 2, ret);
37 ret = is_CZ.select(c - 2, ret);
38 ret = is_01.select(c - '0' + 'y', ret);
39 ret = is_29.select(c - '2' + '0', ret);
40 ret = is_A.select('.', ret);
41 ret = is_B.select('/', ret);
42 ret = is_plus.select('8', ret);
43 ret = is_slash.select('9', ret);
44
45 return ret;
46 }
47
48uint8_t bcrypt_encoding_to_base64(uint8_t c)
49 {
50 const auto is_ax = CT::Mask<uint8_t>::is_within_range(c, 'a', 'x');
51 const auto is_yz = CT::Mask<uint8_t>::is_within_range(c, 'y', 'z');
52
53 const auto is_AX = CT::Mask<uint8_t>::is_within_range(c, 'A', 'X');
54 const auto is_YZ = CT::Mask<uint8_t>::is_within_range(c, 'Y', 'Z');
55 const auto is_07 = CT::Mask<uint8_t>::is_within_range(c, '0', '7');
56
57 const auto is_8 = CT::Mask<uint8_t>::is_equal(c, '8');
58 const auto is_9 = CT::Mask<uint8_t>::is_equal(c, '9');
59 const auto is_dot = CT::Mask<uint8_t>::is_equal(c, '.');
60 const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
61
62 uint8_t ret = 0x80;
63 ret = is_ax.select(c - 'a' + 'c', ret);
64 ret = is_yz.select(c - 'y' + '0', ret);
65 ret = is_AX.select(c - 'A' + 'C', ret);
66 ret = is_YZ.select(c - 'Y' + 'a', ret);
67 ret = is_07.select(c - '0' + '2', ret);
68 ret = is_8.select('+', ret);
69 ret = is_9.select('/', ret);
70 ret = is_dot.select('A', ret);
71 ret = is_slash.select('B', ret);
72
73 return ret;
74 }
75
76std::string bcrypt_base64_encode(const uint8_t input[], size_t length)
77 {
78 std::string b64 = base64_encode(input, length);
79
80 while(!b64.empty() && b64[b64.size()-1] == '=')
81 b64 = b64.substr(0, b64.size() - 1);
82
83 for(size_t i = 0; i != b64.size(); ++i)
84 b64[i] = static_cast<char>(base64_to_bcrypt_encoding(static_cast<uint8_t>(b64[i])));;
85
86 return b64;
87 }
88
89std::vector<uint8_t> bcrypt_base64_decode(std::string input)
90 {
91 for(size_t i = 0; i != input.size(); ++i)
92 input[i] = bcrypt_encoding_to_base64(static_cast<uint8_t>(input[i]));
93
94 return unlock(base64_decode(input));
95 }
96
97std::string make_bcrypt(const std::string& pass,
98 const std::vector<uint8_t>& salt,
99 uint16_t work_factor,
100 char version)
101 {
102 /*
103 * On a 4 GHz Skylake, workfactor == 18 takes about 15 seconds to
104 * hash a password. This seems like a reasonable upper bound for the
105 * time being.
106 * Bcrypt allows up to work factor 31 (2^31 iterations)
107 */
108 BOTAN_ARG_CHECK(work_factor >= 4 && work_factor <= 18,
109 "Invalid bcrypt work factor");
110
111 alignas(64) static const uint8_t BCRYPT_MAGIC[8*3] = {
112 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
117 Blowfish blowfish;
118
119 // Include the trailing NULL byte, so we need c_str() not data()
120 blowfish.salted_set_key(cast_char_ptr_to_uint8(pass.c_str()),
121 pass.length() + 1,
122 salt.data(),
123 salt.size(),
124 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 std::string salt_b64 = bcrypt_base64_encode(salt.data(), salt.size());
132
133 std::string work_factor_str = std::to_string(work_factor);
134 if(work_factor_str.length() == 1)
135 work_factor_str = "0" + work_factor_str;
136
137 return "$2" + std::string(1, version) + "$" + work_factor_str +
138 "$" + salt_b64.substr(0, 22) +
139 bcrypt_base64_encode(ctext.data(), ctext.size() - 1);
140 }
141
142}
143
144std::string generate_bcrypt(const std::string& pass,
146 uint16_t work_factor,
147 char version)
148 {
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 std::vector<uint8_t> salt;
158 rng.random_vec(salt, 16);
159 return make_bcrypt(pass, salt, work_factor, version);
160 }
161
162bool check_bcrypt(const std::string& pass, const std::string& hash)
163 {
164 if(hash.size() != 60 ||
165 hash[0] != '$' || hash[1] != '2' || hash[3] != '$' || hash[6] != '$')
166 {
167 return false;
168 }
169
170 const char bcrypt_version = hash[2];
171
172 if(bcrypt_version != 'a' && bcrypt_version != 'b' && bcrypt_version != 'y')
173 {
174 return false;
175 }
176
177 const uint16_t workfactor = to_uint16(hash.substr(4, 2));
178
179 const std::vector<uint8_t> salt = bcrypt_base64_decode(hash.substr(7, 22));
180 if(salt.size() != 16)
181 return false;
182
183 const std::string compare = make_bcrypt(pass, salt, workfactor, bcrypt_version);
184
185 return same_mem(hash.data(), compare.data(), compare.size());
186 }
187
188}
#define BOTAN_ARG_CHECK(expr, msg)
Definition: assert.h:36
static Mask< T > is_equal(T x, T y)
Definition: ct_utils.h:147
static Mask< T > is_within_range(T v, T l, T u)
Definition: ct_utils.h:184
secure_vector< uint8_t > random_vec(size_t bytes)
Definition: rng.h:167
std::string to_string(const BER_Object &obj)
Definition: asn1_obj.cpp:209
Definition: alg_id.cpp:13
std::string generate_bcrypt(const std::string &pass, RandomNumberGenerator &rng, uint16_t work_factor, char version)
Definition: bcrypt.cpp:144
uint16_t to_uint16(const std::string &str)
Definition: parsing.cpp:19
size_t base64_encode(char out[], const uint8_t in[], size_t input_length, size_t &input_consumed, bool final_inputs)
Definition: base64.cpp:185
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:200
std::vector< T > unlock(const secure_vector< T > &in)
Definition: secmem.h:72
bool check_bcrypt(const std::string &pass, const std::string &hash)
Definition: bcrypt.cpp:162
bool same_mem(const T *p1, const T *p2, size_t n)
Definition: mem_ops.h:210
const uint8_t * cast_char_ptr_to_uint8(const char *s)
Definition: mem_ops.h:183
MechanismType hash