Skip to content

Commit 97b9b8c

Browse files
committed
Add bcrypt from Capy
1 parent ffcff42 commit 97b9b8c

25 files changed

Lines changed: 2665 additions & 0 deletions

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ add_library(boost_http include/boost/http.hpp build/Jamfile ${BOOST_HTTP_HEADERS
172172
add_library(Boost::http ALIAS boost_http)
173173
boost_http_setup_properties(boost_http)
174174

175+
# bcrypt requires platform-specific libraries
176+
if (WIN32)
177+
target_link_libraries(boost_http PRIVATE bcrypt)
178+
elseif (APPLE)
179+
target_link_libraries(boost_http PRIVATE "-framework Security")
180+
endif ()
181+
175182
#-------------------------------------------------
176183
#
177184
# Tests

build/Jamfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ alias http_sources : [ glob-tree-ex ./src : *.cpp ] ;
3636

3737
explicit http_sources ;
3838

39+
# Windows CNG library for bcrypt random number generation.
40+
lib bcrypt_sys : : <name>bcrypt ;
41+
3942
lib boost_http
4043
: http_sources
4144
: requirements
@@ -44,10 +47,14 @@ lib boost_http
4447
<library>/boost//url
4548
<include>../
4649
<define>BOOST_HTTP_SOURCE
50+
<target-os>windows:<library>bcrypt_sys
51+
<target-os>darwin:<linkflags>"-framework Security"
4752
: usage-requirements
4853
<library>/boost//capy
4954
<library>/boost/json//boost_json/<warnings-as-errors>off
5055
<library>/boost//url
56+
<target-os>windows:<library>bcrypt_sys
57+
<target-os>darwin:<linkflags>"-framework Security"
5158
;
5259

5360
boost-install boost_http ;

doc/modules/ROOT/nav.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* xref:Message.adoc[]
1010
* Server
1111
** xref:server/router.adoc[Router]
12+
* Cryptography
13+
** xref:bcrypt.adoc[BCrypt Password Hashing]
1214
// ** xref:server/middleware.adoc[Middleware]
1315
// ** xref:server/errors.adoc[Error Handling]
1416
// ** xref:server/params.adoc[Route Parameters]

doc/modules/ROOT/pages/bcrypt.adoc

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
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+
= BCrypt Password Hashing
11+
12+
This page explains how to securely hash and verify passwords using bcrypt.
13+
14+
NOTE: Code snippets assume `using namespace boost::http;` is in effect.
15+
16+
== What is BCrypt?
17+
18+
BCrypt is a password-hashing function designed by Niels Provos and David
19+
Mazières. It incorporates:
20+
21+
* A **salt** to protect against rainbow table attacks
22+
* An **adaptive cost factor** that can be increased as hardware improves
23+
* Built-in work factor that makes brute-force attacks expensive
24+
25+
BCrypt is the recommended algorithm for password storage.
26+
27+
== Quick Start
28+
29+
[source,cpp]
30+
----
31+
#include <boost/http/bcrypt.hpp>
32+
33+
// Hash a password
34+
bcrypt::result hash = bcrypt::hash("my_password", 12);
35+
36+
// Store hash.str() in database...
37+
38+
// Later, verify the password
39+
system::error_code ec;
40+
bool valid = bcrypt::compare("my_password", stored_hash, ec);
41+
42+
if (ec)
43+
// Hash was malformed
44+
else if (valid)
45+
// Password matches
46+
else
47+
// Password does not match
48+
----
49+
50+
== Hashing Passwords
51+
52+
The `hash` function generates a salted hash:
53+
54+
[source,cpp]
55+
----
56+
// Default cost factor (10)
57+
bcrypt::result r1 = bcrypt::hash("password");
58+
59+
// Custom cost factor
60+
bcrypt::result r2 = bcrypt::hash("password", 12);
61+
62+
// Custom cost factor and version
63+
bcrypt::result r3 = bcrypt::hash("password", 12, bcrypt::version::v2b);
64+
----
65+
66+
=== Cost Factor
67+
68+
The cost factor (rounds) determines how expensive hashing is. Each increment
69+
doubles the work:
70+
71+
[cols="1,2"]
72+
|===
73+
| Cost | Approximate Time (modern CPU)
74+
75+
| 10
76+
| ~100ms
77+
78+
| 12
79+
| ~400ms
80+
81+
| 14
82+
| ~1.6s
83+
84+
| 16
85+
| ~6.4s
86+
|===
87+
88+
**Guidelines:**
89+
90+
* Minimum: 10 for new applications
91+
* Recommended: 12 for most applications
92+
* Maximum: 31 (impractically slow)
93+
* Adjust based on your hardware and latency requirements
94+
95+
=== Password Length Limit
96+
97+
BCrypt only uses the first 72 bytes of a password. Longer passwords are
98+
silently truncated. If you need to support longer passwords, pre-hash
99+
with SHA-256:
100+
101+
[source,cpp]
102+
----
103+
// For passwords > 72 bytes
104+
std::string pre_hash = sha256(long_password);
105+
bcrypt::result r = bcrypt::hash(pre_hash, 12);
106+
----
107+
108+
== Verifying Passwords
109+
110+
The `compare` function extracts the salt from a stored hash, re-hashes
111+
the input password, and compares:
112+
113+
[source,cpp]
114+
----
115+
system::error_code ec;
116+
bool valid = bcrypt::compare(user_input, stored_hash, ec);
117+
118+
if (ec == bcrypt::error::invalid_hash)
119+
{
120+
// Hash string is malformed - data corruption or tampering
121+
log_security_event("invalid hash format");
122+
return false;
123+
}
124+
125+
if (valid)
126+
grant_access();
127+
else
128+
reject_login();
129+
----
130+
131+
WARNING: Always check the error code. A false return value alone does not
132+
distinguish between "wrong password" and "malformed hash".
133+
134+
== Working with Salts
135+
136+
You can generate and use salts separately:
137+
138+
[source,cpp]
139+
----
140+
// Generate a salt
141+
bcrypt::result salt = bcrypt::gen_salt(12);
142+
143+
// Hash with explicit salt
144+
system::error_code ec;
145+
bcrypt::result hash = bcrypt::hash("password", salt.str(), ec);
146+
----
147+
148+
This is rarely needed since `hash()` generates a salt automatically.
149+
150+
== The result Type
151+
152+
`bcrypt::result` is a fixed-size buffer (no heap allocation):
153+
154+
[source,cpp]
155+
----
156+
bcrypt::result r = bcrypt::hash("password", 12);
157+
158+
// Access the hash string
159+
core::string_view sv = r.str(); // Or just use r (implicit conversion)
160+
char const* cstr = r.c_str(); // Null-terminated
161+
162+
// Check for valid result
163+
if (r)
164+
store(r.str());
165+
----
166+
167+
== Hash String Format
168+
169+
A bcrypt hash string has this format:
170+
171+
----
172+
$2b$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
173+
│ │ │ │
174+
│ │ │ └─ hash (31 chars)
175+
│ │ └─ salt (22 chars)
176+
│ └─ cost factor
177+
└─ version
178+
----
179+
180+
Total length: 60 characters.
181+
182+
== Extracting the Cost Factor
183+
184+
To check the cost factor of an existing hash:
185+
186+
[source,cpp]
187+
----
188+
system::error_code ec;
189+
unsigned rounds = bcrypt::get_rounds(stored_hash, ec);
190+
191+
if (ec)
192+
// Invalid hash format
193+
else if (rounds < 12)
194+
// Consider re-hashing with higher cost
195+
----
196+
197+
== Upgrading Cost Factor
198+
199+
When a user logs in successfully, you can check if their hash needs upgrading:
200+
201+
[source,cpp]
202+
----
203+
system::error_code ec;
204+
bool valid = bcrypt::compare(password, stored_hash, ec);
205+
206+
if (valid && !ec)
207+
{
208+
unsigned current_cost = bcrypt::get_rounds(stored_hash, ec);
209+
210+
if (!ec && current_cost < 12)
211+
{
212+
// Re-hash with higher cost
213+
bcrypt::result new_hash = bcrypt::hash(password, 12);
214+
update_stored_hash(user_id, new_hash.str());
215+
}
216+
}
217+
----
218+
219+
== Error Handling
220+
221+
BCrypt defines two error codes:
222+
223+
[cols="1,3"]
224+
|===
225+
| Error | Meaning
226+
227+
| `bcrypt::error::invalid_salt`
228+
| Salt string is malformed
229+
230+
| `bcrypt::error::invalid_hash`
231+
| Hash string is malformed
232+
|===
233+
234+
These errors indicate either data corruption or malicious input. Log them
235+
as security events.
236+
237+
== Version Selection
238+
239+
BCrypt has multiple version prefixes:
240+
241+
[cols="1,3"]
242+
|===
243+
| Version | Description
244+
245+
| `version::v2a`
246+
| Original specification
247+
248+
| `version::v2b`
249+
| Fixed handling of passwords > 255 chars (recommended)
250+
|===
251+
252+
Use `v2b` for new hashes. All versions produce compatible hashes that can
253+
be verified by any version.
254+
255+
== Security Considerations
256+
257+
**Do:**
258+
259+
* Use cost factor 12 or higher
260+
* Store the complete hash string (includes salt and cost)
261+
* Compare in constant time (handled by `compare`)
262+
* Log invalid hash errors as security events
263+
264+
**Do Not:**
265+
266+
* Store salts separately (they are embedded in the hash)
267+
* Use bcrypt for general-purpose hashing (use SHA-256)
268+
* Compare hashes with `==` (timing attacks)
269+
270+
== Summary
271+
272+
[cols="1,3"]
273+
|===
274+
| Function | Purpose
275+
276+
| `bcrypt::hash(password, rounds)`
277+
| Hash a password with auto-generated salt
278+
279+
| `bcrypt::hash(password, salt, ec)`
280+
| Hash with explicit salt
281+
282+
| `bcrypt::compare(password, hash, ec)`
283+
| Verify a password against a hash
284+
285+
| `bcrypt::gen_salt(rounds)`
286+
| Generate a random salt
287+
288+
| `bcrypt::get_rounds(hash, ec)`
289+
| Extract cost factor from hash
290+
|===

include/boost/http.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#ifndef BOOST_HTTP_HPP
1111
#define BOOST_HTTP_HPP
1212

13+
#include <boost/http/bcrypt.hpp>
1314
#include <boost/http/error.hpp>
1415
#include <boost/http/field.hpp>
1516
#include <boost/http/fields.hpp>

include/boost/http/bcrypt.hpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
/** @file
11+
bcrypt password hashing library.
12+
13+
This header includes all bcrypt-related functionality including
14+
password hashing, verification, and salt generation.
15+
16+
bcrypt is a password-hashing function designed by Niels Provos
17+
and David Mazières based on the Blowfish cipher. It incorporates
18+
a salt to protect against rainbow table attacks and an adaptive
19+
cost parameter that can be increased as hardware improves.
20+
21+
@code
22+
#include <boost/http/bcrypt.hpp>
23+
24+
// Hash a password
25+
http::bcrypt::result r;
26+
http::bcrypt::hash(r, "my_password", 12);
27+
28+
// Store r.str() in database...
29+
30+
// Verify later
31+
system::error_code ec;
32+
bool ok = boost::http::bcrypt::compare("my_password", stored_hash, ec);
33+
if (ec)
34+
handle_malformed_hash();
35+
else if (ok)
36+
grant_access();
37+
@endcode
38+
*/
39+
40+
#ifndef BOOST_HTTP_BCRYPT_HPP
41+
#define BOOST_HTTP_BCRYPT_HPP
42+
43+
#include <boost/http/detail/config.hpp>
44+
#include <boost/http/bcrypt/error.hpp>
45+
#include <boost/http/bcrypt/hash.hpp>
46+
#include <boost/http/bcrypt/result.hpp>
47+
#include <boost/http/bcrypt/version.hpp>
48+
49+
#endif

0 commit comments

Comments
 (0)