• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 13274522654

11 Feb 2025 11:26PM UTC coverage: 91.645% (-0.007%) from 91.652%
13274522654

push

github

web-flow
Merge pull request #4647 from randombit/jack/internal-assert-and-mem-ops

Avoid using mem_ops.h or assert.h in public headers

94854 of 103501 relevant lines covered (91.65%)

11334975.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.94
/src/lib/passhash/bcrypt/bcrypt.cpp
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/mem_ops.h>
12
#include <botan/rng.h>
13
#include <botan/internal/blowfish.h>
14
#include <botan/internal/ct_utils.h>
15
#include <botan/internal/fmt.h>
16
#include <botan/internal/parsing.h>
17

18
namespace Botan {
19

20
namespace {
21

22
// Bcrypt uses a non-standard base64 alphabet
23
uint8_t base64_to_bcrypt_encoding(uint8_t c) {
36,888✔
24
   const auto is_ab = CT::Mask<uint8_t>::is_within_range(c, 'a', 'b');
36,888✔
25
   const auto is_cz = CT::Mask<uint8_t>::is_within_range(c, 'c', 'z');
36,888✔
26
   const auto is_CZ = CT::Mask<uint8_t>::is_within_range(c, 'C', 'Z');
36,888✔
27

28
   const auto is_01 = CT::Mask<uint8_t>::is_within_range(c, '0', '1');
36,888✔
29
   const auto is_29 = CT::Mask<uint8_t>::is_within_range(c, '2', '9');
36,888✔
30

31
   const auto is_A = CT::Mask<uint8_t>::is_equal(c, 'A');
36,888✔
32
   const auto is_B = CT::Mask<uint8_t>::is_equal(c, 'B');
36,888✔
33
   const auto is_plus = CT::Mask<uint8_t>::is_equal(c, '+');
36,888✔
34
   const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
36,888✔
35

36
   uint8_t ret = 0x80;
36,888✔
37
   ret = is_ab.select(c - 'a' + 'Y', ret);
36,888✔
38
   ret = is_cz.select(c - 2, ret);
36,888✔
39
   ret = is_CZ.select(c - 2, ret);
36,888✔
40
   ret = is_01.select(c - '0' + 'y', ret);
36,888✔
41
   ret = is_29.select(c - '2' + '0', ret);
36,888✔
42
   ret = is_A.select('.', ret);
36,888✔
43
   ret = is_B.select('/', ret);
36,888✔
44
   ret = is_plus.select('8', ret);
36,888✔
45
   ret = is_slash.select('9', ret);
36,888✔
46

47
   return ret;
36,888✔
48
}
49

50
uint8_t bcrypt_encoding_to_base64(uint8_t c) {
8,602✔
51
   const auto is_ax = CT::Mask<uint8_t>::is_within_range(c, 'a', 'x');
8,602✔
52
   const auto is_yz = CT::Mask<uint8_t>::is_within_range(c, 'y', 'z');
8,602✔
53

54
   const auto is_AX = CT::Mask<uint8_t>::is_within_range(c, 'A', 'X');
8,602✔
55
   const auto is_YZ = CT::Mask<uint8_t>::is_within_range(c, 'Y', 'Z');
8,602✔
56
   const auto is_07 = CT::Mask<uint8_t>::is_within_range(c, '0', '7');
8,602✔
57

58
   const auto is_8 = CT::Mask<uint8_t>::is_equal(c, '8');
8,602✔
59
   const auto is_9 = CT::Mask<uint8_t>::is_equal(c, '9');
8,602✔
60
   const auto is_dot = CT::Mask<uint8_t>::is_equal(c, '.');
8,602✔
61
   const auto is_slash = CT::Mask<uint8_t>::is_equal(c, '/');
8,602✔
62

63
   uint8_t ret = 0x80;
8,602✔
64
   ret = is_ax.select(c - 'a' + 'c', ret);
8,602✔
65
   ret = is_yz.select(c - 'y' + '0', ret);
8,602✔
66
   ret = is_AX.select(c - 'A' + 'C', ret);
8,602✔
67
   ret = is_YZ.select(c - 'Y' + 'a', ret);
8,602✔
68
   ret = is_07.select(c - '0' + '2', ret);
8,602✔
69
   ret = is_8.select('+', ret);
8,602✔
70
   ret = is_9.select('/', ret);
8,602✔
71
   ret = is_dot.select('A', ret);
8,602✔
72
   ret = is_slash.select('B', ret);
8,602✔
73

74
   return ret;
8,602✔
75
}
76

77
std::string bcrypt_base64_encode(const uint8_t input[], size_t length) {
1,392✔
78
   std::string b64 = base64_encode(input, length);
1,392✔
79

80
   while(!b64.empty() && b64[b64.size() - 1] == '=') {
4,872✔
81
      b64 = b64.substr(0, b64.size() - 1);
2,088✔
82
   }
83

84
   for(size_t i = 0; i != b64.size(); ++i) {
38,280✔
85
      b64[i] = static_cast<char>(base64_to_bcrypt_encoding(static_cast<uint8_t>(b64[i])));
36,888✔
86
   }
87

88
   return b64;
1,392✔
89
}
×
90

91
std::vector<uint8_t> bcrypt_base64_decode(std::string_view input) {
391✔
92
   std::string translated;
391✔
93
   for(size_t i = 0; i != input.size(); ++i) {
8,993✔
94
      char c = bcrypt_encoding_to_base64(static_cast<uint8_t>(input[i]));
8,602✔
95
      translated.push_back(c);
8,602✔
96
   }
97

98
   return unlock(base64_decode(translated));
1,173✔
99
}
391✔
100

101
std::string make_bcrypt(std::string_view pass, const std::vector<uint8_t>& salt, uint16_t work_factor, char version) {
696✔
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, "Invalid bcrypt work factor");
696✔
109

110
   alignas(64) static const uint8_t BCRYPT_MAGIC[8 * 3] = {0x4F, 0x72, 0x70, 0x68, 0x65, 0x61, 0x6E, 0x42,
696✔
111
                                                           0x65, 0x68, 0x6F, 0x6C, 0x64, 0x65, 0x72, 0x53,
112
                                                           0x63, 0x72, 0x79, 0x44, 0x6F, 0x75, 0x62, 0x74};
113

114
   Blowfish blowfish;
696✔
115

116
   secure_vector<uint8_t> pass_with_trailing_null(pass.size() + 1);
696✔
117
   copy_mem(pass_with_trailing_null.data(), cast_char_ptr_to_uint8(pass.data()), pass.length());
696✔
118

119
   // Include the trailing NULL byte, so we need c_str() not data()
120
   blowfish.salted_set_key(
696✔
121
      pass_with_trailing_null.data(), pass_with_trailing_null.size(), salt.data(), salt.size(), work_factor);
696✔
122

123
   std::vector<uint8_t> ctext(BCRYPT_MAGIC, BCRYPT_MAGIC + 8 * 3);
696✔
124

125
   for(size_t i = 0; i != 64; ++i) {
45,240✔
126
      blowfish.encrypt_n(ctext.data(), ctext.data(), 3);
44,544✔
127
   }
128

129
   std::string salt_b64 = bcrypt_base64_encode(salt.data(), salt.size());
696✔
130

131
   std::string work_factor_str = std::to_string(work_factor);
696✔
132
   if(work_factor_str.length() == 1) {
696✔
133
      work_factor_str = "0" + work_factor_str;
674✔
134
   }
135

136
   return fmt("$2{}${}${}{}",
696✔
137
              version,
138
              work_factor_str,
139
              salt_b64.substr(0, 22),
1,392✔
140
              bcrypt_base64_encode(ctext.data(), ctext.size() - 1));
2,784✔
141
}
2,762✔
142

143
}  // namespace
144

145
std::string generate_bcrypt(std::string_view pass, RandomNumberGenerator& rng, uint16_t work_factor, char version) {
306✔
146
   /*
147
   2a, 2b and 2y are identical for our purposes because our implementation of 2a
148
   never had the truncation or signed char bugs in the first place.
149
   */
150

151
   if(version != 'a' && version != 'b' && version != 'y') {
306✔
152
      throw Invalid_Argument("Unknown bcrypt version '" + std::string(1, version) + "'");
4✔
153
   }
154

155
   std::vector<uint8_t> salt;
305✔
156
   rng.random_vec(salt, 16);
305✔
157
   return make_bcrypt(pass, salt, work_factor, version);
305✔
158
}
305✔
159

160
bool check_bcrypt(std::string_view pass, std::string_view hash) {
391✔
161
   if(hash.size() != 60 || hash[0] != '$' || hash[1] != '2' || hash[3] != '$' || hash[6] != '$') {
391✔
162
      return false;
163
   }
164

165
   const char bcrypt_version = hash[2];
391✔
166

167
   if(bcrypt_version != 'a' && bcrypt_version != 'b' && bcrypt_version != 'y') {
391✔
168
      return false;
169
   }
170

171
   const uint16_t workfactor = to_uint16(hash.substr(4, 2));
391✔
172

173
   const std::vector<uint8_t> salt = bcrypt_base64_decode(hash.substr(7, 22));
391✔
174
   if(salt.size() != 16) {
391✔
175
      return false;
176
   }
177

178
   const std::string compare = make_bcrypt(pass, salt, workfactor, bcrypt_version);
391✔
179

180
   return CT::is_equal(cast_char_ptr_to_uint8(hash.data()), cast_char_ptr_to_uint8(compare.data()), compare.size())
782✔
181
      .as_bool();
391✔
182
}
782✔
183

184
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc