src/bcrypt/hash.cpp

77.4% Lines (48/62) 100.0% List of functions (5/5)
hash.cpp
f(x) Functions (5)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/http
8 //
9
10 #include <boost/http/bcrypt.hpp>
11 #include <boost/http/detail/except.hpp>
12 #include "base64.hpp"
13 #include "crypt.hpp"
14
15 namespace boost {
16 namespace http {
17 namespace bcrypt {
18
19 result
20 7x gen_salt(
21 unsigned rounds,
22 version ver)
23 {
24 // Validate preconditions
25 7x if (rounds < 4 || rounds > 31)
26 http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
27
28 // Generate random salt
29 std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
30 7x detail::generate_salt_bytes(salt_bytes);
31
32 // Format salt string
33 7x result r;
34 7x std::size_t len = detail::format_salt(
35 r.buf(),
36 salt_bytes,
37 rounds,
38 ver);
39
40 7x r.set_size(static_cast<unsigned char>(len));
41 14x return r;
42 }
43
44 result
45 13x hash(
46 core::string_view password,
47 unsigned rounds,
48 version ver)
49 {
50 // Validate preconditions
51 13x if (rounds < 4 || rounds > 31)
52 http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
53
54 // Generate random salt
55 std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
56 13x detail::generate_salt_bytes(salt_bytes);
57
58 // Hash password
59 std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
60 13x detail::bcrypt_hash(
61 password.data(),
62 password.size(),
63 salt_bytes,
64 rounds,
65 hash_bytes);
66
67 // Format output
68 13x result r;
69 13x std::size_t len = detail::format_hash(
70 r.buf(),
71 salt_bytes,
72 hash_bytes,
73 rounds,
74 ver);
75
76 13x r.set_size(static_cast<unsigned char>(len));
77 26x return r;
78 }
79
80 result
81 7x hash(
82 core::string_view password,
83 core::string_view salt,
84 system::error_code& ec)
85 {
86 7x ec = {};
87
88 // Parse salt
89 version ver;
90 unsigned rounds;
91 std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
92
93 7x if (!detail::parse_salt(salt, ver, rounds, salt_bytes))
94 {
95 2x ec = make_error_code(error::invalid_salt);
96 2x return result{};
97 }
98
99 // Hash password
100 std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
101 5x detail::bcrypt_hash(
102 password.data(),
103 password.size(),
104 salt_bytes,
105 rounds,
106 hash_bytes);
107
108 // Format output
109 5x result r;
110 5x std::size_t len = detail::format_hash(
111 r.buf(),
112 salt_bytes,
113 hash_bytes,
114 rounds,
115 ver);
116
117 5x r.set_size(static_cast<unsigned char>(len));
118 5x return r;
119 }
120
121 bool
122 14x compare(
123 core::string_view password,
124 core::string_view hash_str,
125 system::error_code& ec)
126 {
127 14x ec = {};
128
129 // Parse hash to extract salt
130 version ver;
131 unsigned rounds;
132 std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
133
134 14x if (!detail::parse_salt(hash_str, ver, rounds, salt_bytes))
135 {
136 4x ec = make_error_code(error::invalid_hash);
137 4x return false;
138 }
139
140 // Validate hash length
141 10x if (hash_str.size() != detail::BCRYPT_HASH_OUTPUT_LEN)
142 {
143 ec = make_error_code(error::invalid_hash);
144 return false;
145 }
146
147 // Decode stored hash (31 base64 chars starting at position 29)
148 std::uint8_t stored_hash[detail::BCRYPT_HASH_LEN];
149 10x int decoded = detail::base64_decode(
150 stored_hash,
151 10x hash_str.data() + 29,
152 31);
153
154 10x if (decoded < 0)
155 {
156 ec = make_error_code(error::invalid_hash);
157 return false;
158 }
159
160 // Compute hash of provided password
161 std::uint8_t computed_hash[detail::BCRYPT_HASH_LEN];
162 10x detail::bcrypt_hash(
163 password.data(),
164 password.size(),
165 salt_bytes,
166 rounds,
167 computed_hash);
168
169 // Constant-time comparison (only first 23 bytes are used)
170 10x return detail::secure_compare(stored_hash, computed_hash, 23);
171 }
172
173 unsigned
174 4x get_rounds(
175 core::string_view hash_str,
176 system::error_code& ec)
177 {
178 4x ec = {};
179
180 // Minimum length check
181 4x if (hash_str.size() < 7)
182 {
183 ec = make_error_code(error::invalid_hash);
184 return 0;
185 }
186
187 4x char const* s = hash_str.data();
188
189 // Check prefix
190 4x if (s[0] != '$' || s[1] != '2')
191 {
192 2x ec = make_error_code(error::invalid_hash);
193 2x return 0;
194 }
195
196 // Check version character
197 2x if ((s[2] != 'a' && s[2] != 'b' && s[2] != 'y') || s[3] != '$')
198 {
199 ec = make_error_code(error::invalid_hash);
200 return 0;
201 }
202
203 // Parse rounds
204 2x if (s[4] < '0' || s[4] > '9' || s[5] < '0' || s[5] > '9')
205 {
206 ec = make_error_code(error::invalid_hash);
207 return 0;
208 }
209
210 2x unsigned rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0'));
211 2x if (rounds < 4 || rounds > 31)
212 {
213 ec = make_error_code(error::invalid_hash);
214 return 0;
215 }
216
217 2x return rounds;
218 }
219
220 } // bcrypt
221 } // http
222 } // boost
223