Bcrypt Hash & Verify — C++ source
Hash a password with bcrypt or verify a password against an existing bcrypt hash. Configurable cost factor. Runs entirely in your browser.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// bcrypt — Blowfish-based password hashing (Provos-Mazieres, USENIX '99).
//
// Language: C++ (C++17, standard library only)
// Ported from src/lib/bcrypt.ts (the canonical TypeScript implementation).
// display source — part of CosmoDev's polyglot tool pages.
//
// Implements the full algorithm: EksBlowfish key setup, the 64-fold
// encryption of "OrpheanBeholderScryDoubt", and OpenBSD's $2b$ hash format.
// Produces hashes that interoperate with bcrypt implementations everywhere
// (the TS reference is verified against OpenBSD/Go reference vectors).
//
// The Blowfish P-array (18 words) and S-boxes (4 x 256 words) are the first
// 8336 hex digits of the fractional part of pi, parsed once at load.
#include <array>
#include <cctype>
#include <cstdint>
#include <random>
#include <regex>
#include <stdexcept>
#include <string>
#include <vector>
namespace bcrypt {
using Bytes = std::vector<uint8_t>;
/** bcrypt's non-standard Base64 alphabet ('.' + '/' first, then alphanumeric). */
static const char* const B64_CHARS = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static const std::array<int8_t, 128>& b64Index() {
static const auto TABLE = [] {
std::array<int8_t, 128> t;
t.fill(-1);
for (int i = 0; B64_CHARS[i] != '\0'; i++) t[uint8_t(B64_CHARS[i])] = int8_t(i);
return t;
}();
return TABLE;
}
/** The Blowfish P-array: first 18 words of pi's hex expansion. */
static const char* const P_HEX =
"243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89452821"
"e638d01377be5466cf34e90c6cc0ac29b7c97c50dd3f84d5b5b54709179216d5d98979"
"fb1b";
/** The four Blowfish S-boxes: the following 1024 words of pi's hex expansion. */
static const char* const S_HEX =
"d1310ba698dfb5ac2ffd72dbd01adfb7b8e1afed6a267e96ba7c9045f12c7f9924a199"
"47b3916cf70801f2e2858efc16636920d871574e69a458fea3f4933d7e0d95748f728e"
"b658718bcd5882154aee7b54a41dc25a59b59c30d5392af26013c5d1b023286085f0ca"
"417918b8db38ef8e79dcb0603a180e6c9e0e8bb01e8a3ed71577c1bd314b2778af2fda"
"55605c60e65525f3aa55ab945748986263e8144055ca396a2aab10b6b4cc5c341141e8"
"cea15486af7c72e993b3ee1411636fbc2a2ba9c55d741831f6ce5c3e169b87931eafd6"
"ba336c24cf5c7a325381289586773b8f48986b4bb9afc4bfe81b6628219361d809ccfb"
"21a991487cac605dec8032ef845d5de98575b1dc262302eb651b8823893e81d396acc5"
"0f6d6ff383f442392e0b4482a484200469c8f04a9e1f9b5e21c66842f6e96c9a670c9c"
"61abd388f06a51a0d2d8542f68960fa728ab5133a36eef0b6c137a3be4ba3bf0507efb"
"2a98a1f1651d39af017666ca593e82430e888cee8619456f9fb47d84a5c33b8b5ebee0"
"6f75d885c12073401a449f56c16aa64ed3aa62363f77061bfedf72429b023d37d0d724"
"d00a1248db0fead349f1c09b075372c980991b7b25d479d8f6e8def7e3fe501ab6794c"
"3b976ce0bd04c006bac1a94fb6409f60c45e5c9ec2196a246368fb6faf3e6c53b51339"
"b2eb3b52ec6f6dfc511f9b30952ccc814544af5ebd09bee3d004de334afd660f280719"
"2e4bb3c0cba85745c8740fd20b5f39b9d3fbdb5579c0bd1a60320ad6a100c6402c7279"
"679f25fefb1fa3cc8ea5e9f8db3222f83c7516dffd616b152f501ec8ad0552ab323db5"
"fafd23876053317b483e00df829e5c57bbca6f8ca01a87562edf1769dbd542a8f6287e"
"ffc3ac6732c68c4f5573695b27b0bbca58c8e1ffa35db8f011a010fa3d98fd2183b84a"
"fcb56c2dd1d35b9a53e479b6f84565d28e49bc4bfb9790e1ddf2daa4cb7e3362fb1341"
"cee4c6e8ef20cada36774c01d07e9efe2bf11fb495dbda4dae909198eaad8e716b93d5"
"a0d08ed1d0afc725e08e3c5b2f8e7594b78ff6e2fbf2122b648888b812900df01c4fad"
"5ea0688fc31cd1cff191b3a8c1ad2f2f2218be0e1777ea752dfe8b021fa1e5a0cc0fb5"
"6f74e818acf3d6ce89e299b4a84fe0fd13e0b77cc43b81d2ada8d9165fa26680957705"
"93cc7314211a1477e6ad206577b5fa86c75442f5fb9d35cfebcdaf0c7b3e89a0d6411b"
"d3ae1e7e4900250e2d2071b35e226800bb57b8e0af2464369bf009b91e5563911d59df"
"a6aa78c14389d95a537f207d5ba202e5b9c5832603766295cfa911c819684e734a41b3"
"472dca7b14a94a1b5100529a532915d60f573fbc9bc6e42b60a47681e6740008ba6fb5"
"571be91ff296ec6b2a0dd915b6636521e7b9f9b6ff34052ec585566453b02d5da99f8f"
"a108ba47996e85076a4b7a70e9b5b32944db75092ec4192623ad6ea6b049a7df7d9cee"
"60b88fedb266ecaa8c71699a17ff5664526cc2b19ee1193602a575094c29a0591340e4"
"183a3e3f54989a5b429d656b8fe4d699f73fd6a1d29c07efe830f54d2d38e6f0255dc1"
"4cdd20868470eb266382e9c6021ecc5e09686b3f3ebaefc93c9718146b6a70a1687f35"
"8452a0e286b79c5305aa5007373e07841c7fdeae5c8e7d44ec5716f2b8b03ada37f050"
"0c0df01c1f040200b3ffae0cf51a3cb574b225837a58dc0921bdd19113f97ca92ff694"
"32477322f547013ae5e58137c2dadcc8b576349af3dda7a94461460fd0030eecc8c73e"
"a4751e41e238cd993bea0e2f3280bba1183eb3314e548b384f6db9086f420d03f60a04"
"bf2cb8129024977c795679b072bcaf89afde9a771fd9930810b38bae12dccf3f2e5512"
"721f2e6b7124501adde69f84cd877a5847187408da17bc9f9abce94b7d8cec7aec3adb"
"851dfa63094366c464c3d2ef1c18473215d908dd433b3724c2ba1612a14d432a65c451"
"50940002133ae4dd71dff89e10314e5581ac77d65f11199b043556f1d7a3c76b3c1118"
"3b5924a509f28fe6ed97f1fbfa9ebabf2c1e153c6e86e34570eae96fb1860e5e0a5a3e"
"2ab3771fe71c4e3d06fa2965dcb999e71d0f803e89d65266c8252e4cc9789c10b36ac6"
"150eba94e2ea78a5fc3c531e0a2df4f2f74ea7361d2b3d1939260f19c279605223a708"
"f71312b6ebadfe6eeac31f66e3bc4595a67bc883b17f37d1018cff28c332ddefbe6c5a"
"a56558218568ab9802eecea50fdb2f953b2aef7dad5b6e2f841521b62829076170ecdd"
"4775619f151013cca830eb61bd960334fe1eaa0363cfb5735c904c70a239d59e9e0bcb"
"aade14eecc86bc60622ca79cab5cabb2f3846e648b1eaf19bdf0caa02369b9655abb50"
"40685a323c2ab4b3319ee9d5c021b8f79b540b19875fa09995f7997e623d7da8f83788"
"9a97e32d7711ed935f166812810e358829c7e61fd696dedfa17858ba9957f584a51b22"
"72639b83c3ff1ac24696cdb30aeb532e30548fd948e46dbc312858ebf2ef34c6ffeafe"
"28ed61ee7c3c735d4a14d9e864b7e342105d14203e13e045eee2b6a3aaabeadb6c4f15"
"facb4fd0c742f442ef6abbb5654f3b1d41cd2105d81e799e86854dc7e44b476a3d8162"
"50cf62a1f25b8d2646fc8883a0c1c7b6a37f1524c369cb749247848a0b5692b285095b"
"bf00ad19489d1462b17423820e0058428d2a0c55f5ea1dadf43e233f70613372f0928d"
"937e41d65fecf16c223bdb7cde3759cbee74604085f2a7ce77326ea607808419f8509e"
"e8efd85561d99735a969a7aac50c06c25a04abfc800bcadc9e447a2ec3453484fdd567"
"050e1e9ec9db73dbd3105588cd675fda79e3674340c5c43465713e38d83d28f89ef16d"
"ff20153e21e78fb03d4ae6e39f2bdb83adf7e93d5a68948140f7f64c261c9469293441"
"1520f77602d4f7bcf46b2ed4a20068d40824713320f46a43b7d4b7500061af1e39f62e"
"9724454614214f74bf8b88404d95fc1d96b591af70f4ddd366a02f45bfbc09ec03bd97"
"857fac6dd031cb850496eb27b355fd3941da2547e6abca0a9a28507825530429f40a2c"
"86dae9b66dfb68dc1462d7486900680ec0a427a18dee4f3ffea2e887ad8cb58ce0067a"
"f4d6b6aace1e7cd3375fecce78a399406b2a4220fe9e35d9f385b9ee39d7ab3b124e8b"
"1dc9faf74b6d185626a36631eae397b23a6efa74dd5b43326841e7f7ca7820fbfb0af5"
"4ed8feb397454056acba48952755533a3a20838d87fe6ba9b7d096954b55a867bca115"
"9a58cca9296399e1db33a62a4a563f3125f95ef47e1c9029317cfdf8e80204272f7080"
"bb155c05282ce395c11548e4c66d2248c1133fc70f86dc07f9c9ee41041f0f404779a4"
"5d886e17325f51ebd59bc0d1f2bcc18f41113564257b7834602a9c60dff8e8a31f636c"
"1b0e12b4c202e1329eaf664fd1cad181156b2395e0333e92e13b240b62eebeb92285b2"
"a20ee6ba0d99de720c8c2da2f728d012784595b794fd647d0862e7ccf5f05449a36f87"
"7d48fac39dfd27f33e8d1e0a476341992eff743a6f6eabf4f8fd37a812dc60a1ebddf8"
"991be14cdb6e6b0dc67b55106d672c372765d43bdcd0e804f1290dc7cc00ffa3b5390f"
"92690fed0b667b9ffbcedb7d9ca091cf0bd9155ea3bb132f88515bad247b9479bf763b"
"d6eb37392eb3cc1159798026e297f42e312d6842ada7c66a2b3b12754ccc782ef11c6a"
"124237b79251e706a1bbe64bfb63501a6b101811caedfa3d25bdd8e2e1c3c944421659"
"0a121386d90cec6ed5abea2a64af674eda86a85fbebfe98864e4c3fe9dbc8057f0f7c0"
"8660787bf86003604dd1fd8346f6381fb07745ae04d736fccc83426b33f01eab71b080"
"41873c005e5f77a057bebde8ae2455464299bf582e614e58f48ff2ddfda2f474ef3887"
"89bdc25366f9c3c8b38e74b475f25546fcd9b97aeb26618b1ddf84846a0e79915f95e2"
"466e598e20b457708cd55591c902de4cb90bace1bb8205d011a862487574a99eb77f19"
"b6e0a9dc09662d09a1c4324633e85a1f0209f0be8c4a99a0251d6efe101ab93d1d0ba5"
"a4dfa186f20f2868f169dcb7da83573906fea1e2ce9b4fcd7f5250115e01a70683faa0"
"02b5c40de6d0279af88c27773f8641c3604c0661a806b5f0177a28c0f586e0006058aa"
"30dc7d6211e69ed72338ea6353c2dd94c2c21634bbcbee5690bcb6deebfc7da1ce591d"
"766f05e4094b7c018839720a3d7c927c2486e3725f724d9db91ac15bb4d39eb8fced54"
"557808fca5b5d83d7cd34dad0fc41e50ef5eb161e6f8a28514d96c51133c6fd5c7e756"
"e14ec4362abfceddc6c837d79a323492638212670efa8e406000e03a39ce37d3faf5cf"
"abc277375ac52d1b5cb0679e4fa33742d382274099bc9bbed5118e9dbf0f7315d62d1c"
"7ec700c47bb78c1b6b21a19045b26eb1be6a366eb45748ab2fbc946e79c6a376d26549"
"c2c8530ff8ee468dde7dd5730a1d4cd04dc62939bbdba9ba4650ac9526e8be5ee304a1"
"fad5f06a2d519a63ef8ce29a86ee22c089c2b843242ef6a51e03aa9cf2d0a483c061ba"
"9be96a4d8fe51550ba645bd62826a2f9a73a3ae14ba99586ef5562e9c72fefd3f752f7"
"da3f046f6977fa0a5980e4a91587b086019b09e6ad3b3ee593e990fd5a9e34d7972cf0"
"b7d9022b8b5196d5ac3a017da67dd1cf3ed67c7d2d281f9f25cfadf2b89b5ad6b4725a"
"88f54ce029ac71e019a5e647b0acfded93fa9be8d3c48d283b57ccf8d5662979132e28"
"785f0191ed756055f7960e44e3d35e8c15056dd488f46dba03a161250564f0bdc3eb9e"
"153c9057a297271aeca93a072a1b3f6d9b1e6321f5f59c66fb26dcf3197533d928b155"
"fdf5035634828aba3cbb28517711c20ad9f8abcc5167ccad925f4de817513830dc8e37"
"9d58629320f991ea7a90c2fb3e7bce5121ce64774fbe32a8b6e37ec3293d4648de5369"
"6413e680a2ae0810dd6db22469852dfd09072166b39a460a6445c0dd586cdecf1c20c8"
"ae5bbef7dd1b588d40ccd2017f6bb4e3bbdda26a7e3a59ff453e350a44bcb4cdd572ea"
"cea8fa6484bb8d6612aebf3c6f47d29be463542f5d9eaec2771bf64e6370740e0d8de7"
"5b1357f8721671af537d5d4040cb084eb4e2cc34d2466a0115af84e1b0042895983a1d"
"06b89fb4ce6ea0486f3f3b823520ab82011a1d4b277227f8611560b1e7933fdcbb3a79"
"2b344525bda08839e151ce794b2f32c9b7a01fbac9e01cc87ebcc7d1f6cf0111c3a1e8"
"aac71a908749d44fbd9ad0dadecbd50ada380339c32ac69136678df9317ce0b12b4ff7"
"9e59b743f5bb3af2d519ff27d9459cbf97222c15e6fc2a0f91fc719b941525fae59361"
"ceb69cebc2a8645912baa8d1b6c1075ee3056a0c10d25065cb03a442e0ec6e0e1698db"
"3b4c98a0be3278e9649f1f9532e0d392dfd3a0342b8971f21e1b0a74414ba3348cc5be"
"7120c37632d8df359f8d9b992f2ee60b6f470fe3f11de54cda541edad891ce6279cfcd"
"3e7e6f1618b166fd2c1d05848fd2c5f6fb2299f523f357a632762393a8353156cccd02"
"acf081625a75ebb56e16369788d273ccde96629281b949d04c50901b71c65614e6c6c7"
"bd327a140a45e1d006c3f27b9ac9aa53fd62a80f00bb25bfe235bdd2f671126905b204"
"0222b6cbcf7ccd769c2b53113ec01640e3d338abbd602547adf0ba38209cf746ce7677"
"afa1c52075606085cbfe4e8ae88dd87aaaf9b04cf9aa7e1948c25c02fb8a8c01c36ae4"
"d6ebe1f990d4f869a65cdea03f09252dc208e69fb74e6132ce77e25b578fdfe33ac372"
"e6";
static std::vector<uint32_t> parseHexWords(const char* hex, std::size_t hexLen) {
std::vector<uint32_t> out(hexLen / 8);
for (std::size_t i = 0; i < out.size(); i++) {
uint32_t w = 0;
for (int j = 0; j < 8; j++) {
const char c = hex[i * 8 + j];
const int v = c <= '9' ? c - '0' : (c | 0x20) - 'a' + 10;
w = (w << 4) | uint32_t(v);
}
out[i] = w;
}
return out;
}
static const std::vector<uint32_t> P_INIT = parseHexWords(P_HEX, std::string(P_HEX).size());
static const std::vector<uint32_t> S_INIT = parseHexWords(S_HEX, std::string(S_HEX).size());
/** Minimum and maximum bcrypt cost factor (log2 rounds). */
constexpr int MIN_COST = 4;
constexpr int MAX_COST = 31;
/** Cost used when bcryptHash gets no explicit cost argument. */
constexpr int DEFAULT_COST = 12;
/** bcrypt only uses the first 72 bytes of the password. */
constexpr std::size_t MAX_PASSWORD_BYTES = 72;
/** The magic string bcrypt encrypts 64 times to produce the digest. */
static const char* const MAGIC = "OrpheanBeholderScryDoubt";
/** Parsed hash parts: version prefix ('' '$2b$'), cost, 22-char Base64 salt. */
struct BcryptInfo {
std::string version;
int cost = 0;
std::string salt;
};
/**
* Derive the bcrypt key data from a password: UTF-8 bytes, truncated to 72.
* A single NUL terminator is appended unless truncation already reached 72
* bytes - the exact keying OpenBSD's bcrypt uses.
*/
Bytes keyData(const std::string& password) {
const uint8_t* begin = reinterpret_cast<const uint8_t*>(password.data());
const std::size_t len = std::min(password.size(), MAX_PASSWORD_BYTES);
Bytes key(begin, begin + len);
if (key.size() < MAX_PASSWORD_BYTES) key.push_back(0);
return key;
}
/** Blowfish round function F(x) = ((S0[a] + S1[b]) ^ S2[c]) + S3[d]. */
static uint32_t bfRound(const std::vector<uint32_t>& S, uint32_t x) {
return ((((S[(x >> 24) & 0xff] + S[256 + ((x >> 16) & 0xff)]) ^ S[512 + ((x >> 8) & 0xff)]) +
S[768 + (x & 0xff)]));
}
/** One Blowfish encryption of the (xl, xr) pair. */
static std::pair<uint32_t, uint32_t> encipher(const std::vector<uint32_t>& P,
const std::vector<uint32_t>& S,
uint32_t xl, uint32_t xr) {
uint32_t l = xl;
uint32_t r = xr;
for (int i = 0; i < 16; i++) {
l ^= P[i];
r ^= bfRound(S, l);
std::swap(l, r);
}
std::swap(l, r);
r ^= P[16];
l ^= P[17];
return {l, r};
}
/**
* Read 4 bytes at `offset` (wrapping around `data`) as a big-endian word.
* Returns the word and the advanced offset - OpenBSD's stream2word.
*/
static std::pair<uint32_t, std::size_t> stream2word(const Bytes& data, std::size_t offset) {
uint32_t word = 0;
std::size_t j = offset;
for (int i = 0; i < 4; i++, j++) {
if (j >= data.size()) j = 0;
word = (word << 8) | data[j];
}
return {word, j};
}
/** Plain Blowfish key schedule (OpenBSD's expand0state): XOR `data` into P,
* then chain (0,0) through 521 encryptions that re-derive P and every S-box
* entry. Unlike the salted variant, nothing is XORed into the mixing pair. */
static void expand0(std::vector<uint32_t>& P, std::vector<uint32_t>& S, const Bytes& data) {
std::size_t j = 0;
for (int i = 0; i < 18; i++) {
const auto [w, nj] = stream2word(data, j);
P[i] ^= w;
j = nj;
}
uint32_t l = 0;
uint32_t r = 0;
for (int i = 0; i < 18; i += 2) {
std::tie(l, r) = encipher(P, S, l, r);
P[i] = l;
P[i + 1] = r;
}
for (int box = 0; box < 4; box++) {
for (int k = 0; k < 256; k += 2) {
std::tie(l, r) = encipher(P, S, l, r);
S[box * 256 + k] = l;
S[box * 256 + k + 1] = r;
}
}
}
/** Blowfish key schedule seeded with salt: P is XORed with the key while the
* mixing pairs are salted - bcrypt's first expandstate call. */
static void expandState(std::vector<uint32_t>& P, std::vector<uint32_t>& S,
const Bytes& salt, const Bytes& key) {
std::size_t j = 0;
for (int i = 0; i < 18; i++) {
const auto [w, nj] = stream2word(key, j);
P[i] ^= w;
j = nj;
}
uint32_t l = 0;
uint32_t r = 0;
j = 0;
for (int i = 0; i < 18; i += 2) {
uint32_t w;
std::tie(w, j) = stream2word(salt, j);
l ^= w;
std::tie(w, j) = stream2word(salt, j);
r ^= w;
std::tie(l, r) = encipher(P, S, l, r);
P[i] = l;
P[i + 1] = r;
}
for (int box = 0; box < 4; box++) {
for (int k = 0; k < 256; k += 2) {
uint32_t w;
std::tie(w, j) = stream2word(salt, j);
l ^= w;
std::tie(w, j) = stream2word(salt, j);
r ^= w;
std::tie(l, r) = encipher(P, S, l, r);
S[box * 256 + k] = l;
S[box * 256 + k + 1] = r;
}
}
}
/** Encode bytes with bcrypt's Base64 variant (3 bytes -> 4 chars, partial
* groups emit 2-3 chars, no padding). */
std::string encodeB64(const Bytes& data) {
std::string out;
out.reserve((data.size() + 2) / 3 * 4);
for (std::size_t i = 0; i < data.size(); i += 3) {
const int b0 = data[i];
const int b1 = i + 1 < data.size() ? data[i + 1] : -1;
const int b2 = i + 2 < data.size() ? data[i + 2] : -1;
out += B64_CHARS[b0 >> 2];
out += B64_CHARS[((b0 & 0x03) << 4) | (b1 >= 0 ? b1 >> 4 : 0)];
if (b1 < 0) break;
out += B64_CHARS[((b1 & 0x0f) << 2) | (b2 >= 0 ? b2 >> 6 : 0)];
if (b2 < 0) break;
out += B64_CHARS[b2 & 0x3f];
}
return out;
}
/** Decode bcrypt Base64 into exactly `count` bytes (throws on bad chars or
* when the input carries fewer than `count` bytes worth of bits). */
Bytes decodeB64(const std::string& input, std::size_t count) {
Bytes out(count, 0);
const std::size_t totalBits = count * 8;
std::size_t target = 0;
for (std::size_t i = 0; i < input.size() && target < totalBits; i++) {
const uint8_t c = uint8_t(input[i]);
const int v = c < 128 ? b64Index()[c] : -1;
if (v < 0) throw std::invalid_argument("Invalid character in bcrypt base64 data");
for (int bit = 5; bit >= 0 && target < totalBits; bit--) {
if (v & (1 << bit)) out[target >> 3] |= uint8_t(1 << (7 - (target & 7)));
target++;
}
}
if (target < totalBits) throw std::invalid_argument("Bcrypt base64 data is too short");
return out;
}
/** Validate a cost factor, throwing a clear error outside 4-31. */
void assertCost(int cost) {
if (cost < MIN_COST || cost > MAX_COST) {
throw std::invalid_argument("Cost factor must be an integer between 4 and 31");
}
}
/**
* Compute the bcrypt digest of a password with an explicit salt and cost.
* Shared by hash (fresh random salt) and verify (salt parsed from the hash) -
* also the deterministic entry point used by the test suite.
*/
std::string bcryptHashWithSalt(const std::string& password, int cost, const Bytes& salt) {
assertCost(cost);
if (salt.size() != 16) throw std::invalid_argument("Salt must be exactly 16 bytes");
const Bytes key = keyData(password);
std::vector<uint32_t> P = P_INIT;
std::vector<uint32_t> S = S_INIT;
expandState(P, S, salt, key);
const std::uint64_t rounds = std::uint64_t(1) << cost;
for (std::uint64_t k = 0; k < rounds; k++) {
expand0(P, S, key);
expand0(P, S, salt);
}
uint32_t cdata[6];
for (int i = 0; i < 6; i++) {
cdata[i] = uint32_t(uint8_t(MAGIC[i * 4])) << 24 | uint32_t(uint8_t(MAGIC[i * 4 + 1])) << 16 |
uint32_t(uint8_t(MAGIC[i * 4 + 2])) << 8 | uint32_t(uint8_t(MAGIC[i * 4 + 3]));
}
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 6; j += 2) {
std::tie(cdata[j], cdata[j + 1]) = encipher(P, S, cdata[j], cdata[j + 1]);
}
}
Bytes digestBytes(23);
for (int i = 0; i < 23; i++) {
digestBytes[i] = uint8_t((cdata[i >> 2] >> (24 - 8 * (i & 3))) & 0xff);
}
return encodeB64(digestBytes);
}
/**
* Hash a password with bcrypt. Generates a fresh 16-byte random salt, runs
* 2^cost EksBlowfish rounds (default cost 12), and returns a `$2b$` hash
* string.
*/
std::string bcryptHash(const std::string& password, int cost = DEFAULT_COST) {
if (password.empty()) throw std::invalid_argument("Password must not be empty");
Bytes salt(16);
std::random_device rd;
for (auto& b : salt) b = uint8_t(rd());
const std::string digest = bcryptHashWithSalt(password, cost, salt);
char prefix[8];
snprintf(prefix, sizeof(prefix), "$2b$%02d$", cost);
return std::string(prefix) + encodeB64(salt) + digest;
}
/**
* Parse a bcrypt hash string into its parts: version prefix, cost factor and
* 22-character Base64 salt. Throws on malformed input.
*/
BcryptInfo bcryptDecode(const std::string& hash) {
static const std::regex HASH_RE(
"^\\$2[aby]\\$(\\d{2})\\$([./A-Za-z0-9]{22})([./A-Za-z0-9]{31})$");
// Trim ASCII whitespace from both ends, as the TS `hash.trim()` does.
std::size_t begin = 0, end = hash.size();
while (begin < end && std::isspace(static_cast<unsigned char>(hash[begin]))) begin++;
while (end > begin && std::isspace(static_cast<unsigned char>(hash[end - 1]))) end--;
const std::string trimmed = hash.substr(begin, end - begin);
std::smatch m;
if (!std::regex_match(trimmed, m, HASH_RE)) {
throw std::invalid_argument(
"Not a valid bcrypt hash (expected $2a$/$2b$/$2y$CC$ + 53 base64 chars)");
}
const int cost = std::stoi(m[1].str());
if (cost < MIN_COST || cost > MAX_COST) {
throw std::invalid_argument("Cost factor out of range (4-31)");
}
return {trimmed.substr(0, 4), cost, m[2].str()};
}
/**
* Verify a password against a `$2a$` / `$2b$` / `$2y$` bcrypt hash.
* Recomputes the digest with the hash's own salt and cost, then compares
* in constant time. An empty password is allowed here - reference bcrypt
* implementations can hash the empty string, so their hashes must verify.
*/
bool bcryptVerify(const std::string& password, const std::string& hash) {
const BcryptInfo info = bcryptDecode(hash);
const Bytes salt = decodeB64(info.salt, 16);
const std::string digest = bcryptHashWithSalt(password, info.cost, salt);
const std::string expected = hash.substr(hash.size() - 31);
uint8_t diff = 0;
for (std::size_t i = 0; i < digest.size(); i++) {
diff |= uint8_t(digest[i]) ^ uint8_t(expected[i]);
}
return diff == 0;
}
} // namespace bcrypt
Also available in 9 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →