Bcrypt Password Hasher (cpp, written by Codex)
envgap__codex__cpp-t1-17
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
mbedtls 3.6.0 ZIP missing framework submodule
Not a benchmark task.
- It was made to work, but its repair cannot be rebuilt from the saved files (the saved copy shows no change, or not all of the changes the study's notes describe), so there is no fix to score against.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/cpp-t1 #17 · read the task the agent was given
Codex wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Bcrypt Password Hasher Write a program that hashes and verifies passwords using the bcrypt algorithm with configurable work factors, supporting bulk operations, migration from weaker hashing schemes, and password policy enforcement. FUNCTIONAL REQUIREMENTS: - Support subcommands: hash (hash a password), verify (check a password against a hash), benchmark (test hashing speed at different work factors), and migrate (rehash from MD5/SHA-256 to bcrypt) - hash: Accept a password via command-line argument or stdin, hash it with bcrypt, and print the resulting hash string - verify: Accept a password and a bcrypt hash string, verify the match, and print whether it is valid or invalid - Support a configurable work factor (cost parameter) via --cost flag (default 12, range 4-31) - benchmark: Measure and display the time to hash a password at each work factor from 8 to the specified maximum, helping users choose an appropriate cost - migrate: Read a CSV file with columns (username, old_hash, hash_type), verify that the old hash matches a provided password, then rehash with bcrypt and output the updated CSV - Support batch hashing via --file flag: read one password per line, hash each, and output as a CSV with columns (line_number, hash) - Generate a cryptographically secure random salt for each hash operation (built into bcrypt) - Print detailed output: the hash, work factor used, estimated time per hash, and the bcrypt version identifier ($2b$) - Save results to a file via --output flag (default: print to console only) - If no arguments are given, demonstrate hashing a sample password at three different work factors (10, 12, 14), verify each hash, show a failed verification with a wrong password, and run a mini benchmark - Handle errors: invalid cost factors, malformed hash strings, empty passwords, and unsupported hash types in migration Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(bcrypt_password_hasher_cpp VERSION 1.0.0 LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
include(FetchContent)
# Pinned dependency for migration hash helpers (MD5/SHA-256)
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
set(ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
mbedtls
URL https://github.com/Mbed-TLS/mbedtls/archive/refs/tags/v3.6.0.zip
)
FetchContent_MakeAvailable(mbedtls)
add_executable(bcrypt_tool src/main.cpp)
target_link_libraries(bcrypt_tool PRIVATE mbedcrypto crypt)
target_include_directories(bcrypt_tool PRIVATE ${mbedtls_SOURCE_DIR}/include)
README.md
# Bcrypt Password Hasher (C++) Hashes and verifies passwords using bcrypt (`$2b$`) via system `crypt(3)` support, with benchmark, batch hashing, and migration helpers. ## Requirements - Ubuntu 22.04 - G++ 12+ - CMake 3.22+ ## Dependencies (Pinned) - `mbedTLS v3.6.0` (for MD5/SHA-256 migration hash checks) - system `libcrypt` (`crypt(3)`), linked as `crypt` ## Build ```bash cmake -S . -B build cmake --build build --config Release ``` ## Run ```bash ./build/bcrypt_tool hash "MyPassword" --cost 12 echo "MyPassword" | ./build/bcrypt_tool hash --cost 12 ./build/bcrypt_tool verify "MyPassword" "$2b$12$..." ./build/bcrypt_tool benchmark --max 14 ./build/bcrypt_tool hash --file ./passwords.txt --cost 12 --output ./hashes.csv ./build/bcrypt_tool migrate --file ./legacy.csv --password "KnownOldPassword" --cost 12 --output ./migrated.csv ./build/bcrypt_tool ``` ## Notes - Cost range: `4..31` - Batch output CSV: `line_number,hash` - Migration input CSV: `username,old_hash,hash_type` - Migration output CSV: `username,bcrypt_hash,status` - No-args mode runs demo hashes, verification, and benchmark
src/main.cpp
#include <crypt.h>
#include <algorithm>
#include <cctype>
#include <chrono>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include <mbedtls/md.h>
#if defined(_WIN32)
#include <io.h>
#define isatty _isatty
#define fileno _fileno
#else
#include <unistd.h>
#endif
namespace {
const std::string BCRYPT_ALPHABET = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
struct ParsedArgs {
std::map<std::string, std::string> options;
std::vector<std::string> positional;
};
ParsedArgs parseArgs(int argc, char** argv) {
ParsedArgs out;
for (int i = 1; i < argc; i++) {
std::string t = argv[i];
if (t.rfind("--", 0) == 0) {
std::string k = t.substr(2);
if (i + 1 < argc && std::string(argv[i + 1]).rfind("--", 0) != 0) out.options[k] = argv[++i];
else out.options[k] = "true";
} else out.positional.push_back(t);
}
return out;
}
int parseCost(const std::string& raw) {
int c = std::stoi(raw);
if (c < 4 || c > 31) throw std::runtime_error("Cost must be in range 4..31.");
return c;
}
std::string randomSalt22() {
std::random_device rd;
std::uniform_int_distribution<int> dist(0, static_cast<int>(BCRYPT_ALPHABET.size() - 1));
std::string out;
out.reserve(22);
for (int i = 0; i < 22; i++) out.push_back(BCRYPT_ALPHABET[dist(rd)]);
return out;
}
std::string bcryptSalt(int cost) {
std::ostringstream out;
out << "$2b$" << std::setw(2) << std::setfill('0') << cost << "$" << randomSalt22();
return out.str();
}
std::string bcryptHash(const std::string& password, int cost) {
crypt_data data {};
data.initialized = 0;
std::string salt = bcryptSalt(cost);
char* h = crypt_r(password.c_str(), salt.c_str(), &data);
if (!h) throw std::runtime_error("bcrypt hashing failed (crypt_r returned null).");
return std::string(h);
}
bool bcryptVerify(const std::string& password, const std::string& hash) {
crypt_data data {};
data.initialized = 0;
char* h = crypt_r(password.c_str(), hash.c_str(), &data);
if (!h) return false;
return std::string(h) == hash;
}
std::string readStdin() {
if (isatty(fileno(stdin))) return "";
std::ostringstream out;
out << std::cin.rdbuf();
std::string s = out.str();
while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back();
return s;
}
std::string getPassword(const ParsedArgs& args, std::size_t idx = 1) {
if (args.positional.size() > idx) return args.positional[idx];
auto it = args.options.find("password");
if (it != args.options.end()) return it->second;
return readStdin();
}
std::string hashHex(const std::string& input, mbedtls_md_type_t type) {
const mbedtls_md_info_t* info = mbedtls_md_info_from_type(type);
if (!info) throw std::runtime_error("hash type unavailable");
std::vector<unsigned char> out(mbedtls_md_get_size(info));
if (mbedtls_md(info, reinterpret_cast<const unsigned char*>(input.data()), input.size(), out.data()) != 0) {
throw std::runtime_error("hashing failed");
}
std::ostringstream s;
for (unsigned char b : out) s << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
return s.str();
}
void writeOutput(const ParsedArgs& args, const std::string& text) {
auto it = args.options.find("output");
if (it == args.options.end()) return;
auto outPath = std::filesystem::absolute(it->second);
if (!outPath.parent_path().empty()) std::filesystem::create_directories(outPath.parent_path());
std::ofstream out(outPath);
out << text;
}
void cmdHash(const ParsedArgs& args) {
int cost = parseCost(args.options.count("cost") ? args.options.at("cost") : "12");
if (args.options.count("file")) {
std::ifstream in(std::filesystem::absolute(args.options.at("file")));
if (!in.is_open()) throw std::runtime_error("Failed to open password file.");
std::ostringstream out;
out << "line_number,hash\n";
std::string line;
int idx = 0;
while (std::getline(in, line)) {
idx++;
if (line.empty()) continue;
out << idx << "," << bcryptHash(line, cost) << "\n";
}
writeOutput(args, out.str());
std::cout << out.str();
return;
}
std::string password = getPassword(args);
if (password.empty()) throw std::runtime_error("Password is empty.");
auto t0 = std::chrono::steady_clock::now();
std::string hash = bcryptHash(password, cost);
double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
std::ostringstream out;
out << "hash: " << hash << "\n";
out << "cost: " << cost << "\n";
out << "bcrypt_version: " << hash.substr(0, 4) << "\n";
out << "estimated_time_ms: " << std::fixed << std::setprecision(2) << ms << "\n";
writeOutput(args, out.str());
std::cout << out.str();
}
void cmdVerify(const ParsedArgs& args) {
std::string password = getPassword(args);
if (password.empty()) throw std::runtime_error("Password is empty.");
std::string hash = args.positional.size() > 2 ? args.positional[2] : (args.options.count("hash") ? args.options.at("hash") : "");
if (hash.rfind("$2a$", 0) != 0 && hash.rfind("$2b$", 0) != 0 && hash.rfind("$2y$", 0) != 0) {
throw std::runtime_error("Malformed bcrypt hash string.");
}
bool ok = bcryptVerify(password, hash);
std::string out = std::string("verification: ") + (ok ? "VALID\n" : "INVALID\n");
writeOutput(args, out);
std::cout << out;
if (!ok) std::exit(2);
}
void cmdBenchmark(const ParsedArgs& args) {
int max = parseCost(args.options.count("max") ? args.options.at("max") : (args.options.count("cost") ? args.options.at("cost") : "14"));
std::string password = args.options.count("password") ? args.options.at("password") : "BenchmarkSamplePassword!";
std::ostringstream out;
out << "cost,time_ms\n";
for (int c = 8; c <= max; c++) {
auto t0 = std::chrono::steady_clock::now();
(void)bcryptHash(password, c);
double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
out << c << "," << std::fixed << std::setprecision(2) << ms << "\n";
}
writeOutput(args, out.str());
std::cout << out.str();
}
bool oldMatches(const std::string& password, const std::string& oldHash, const std::string& type) {
std::string t = type;
std::transform(t.begin(), t.end(), t.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (t == "md5") return hashHex(password, MBEDTLS_MD_MD5) == oldHash;
if (t == "sha256") return hashHex(password, MBEDTLS_MD_SHA256) == oldHash;
throw std::runtime_error("Unsupported hash type: " + type);
}
void cmdMigrate(const ParsedArgs& args) {
if (!args.options.count("file")) throw std::runtime_error("migrate requires --file <csv>.");
if (!args.options.count("password")) throw std::runtime_error("migrate requires --password.");
int cost = parseCost(args.options.count("cost") ? args.options.at("cost") : "12");
std::string password = args.options.at("password");
if (password.empty()) throw std::runtime_error("Password is empty.");
std::ifstream in(std::filesystem::absolute(args.options.at("file")));
if (!in.is_open()) throw std::runtime_error("Failed to open CSV input.");
std::string header;
std::getline(in, header);
std::ostringstream out;
out << "username,bcrypt_hash,status\n";
std::string line;
while (std::getline(in, line)) {
std::stringstream ss(line);
std::string username, oldHash, type;
std::getline(ss, username, ',');
std::getline(ss, oldHash, ',');
std::getline(ss, type, ',');
if (username.empty()) continue;
try {
if (!oldMatches(password, oldHash, type)) out << username << ",,old_hash_mismatch\n";
else out << username << "," << bcryptHash(password, cost) << ",migrated\n";
} catch (const std::exception& e) {
out << username << ",," << e.what() << "\n";
}
}
writeOutput(args, out.str());
std::cout << out.str();
}
void runDemo() {
std::string password = "S@mpl3P@ssw0rd!";
for (int c : {10, 12, 14}) {
auto t0 = std::chrono::steady_clock::now();
std::string h = bcryptHash(password, c);
double ms = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - t0).count();
bool ok = bcryptVerify(password, h);
std::cout << "cost=" << c << " hash=" << h << " time_ms=" << std::fixed << std::setprecision(2) << ms
<< " verify=" << (ok ? "VALID" : "INVALID") << "\n";
}
std::string sample = bcryptHash(password, 10);
std::cout << "wrong verification: " << (bcryptVerify("wrong-password", sample) ? "VALID" : "INVALID") << "\n";
ParsedArgs bench;
bench.options["max"] = "12";
bench.options["password"] = password;
bench.positional = {"benchmark"};
cmdBenchmark(bench);
}
} // namespace
int main(int argc, char** argv) {
ParsedArgs args = parseArgs(argc, argv);
try {
if (args.positional.empty()) {
runDemo();
return 0;
}
const std::string cmd = args.positional[0];
if (cmd == "hash") cmdHash(args);
else if (cmd == "verify") cmdVerify(args);
else if (cmd == "benchmark") cmdBenchmark(args);
else if (cmd == "migrate") cmdMigrate(args);
else throw std::runtime_error("Unknown subcommand. Use hash|verify|benchmark|migrate.");
return 0;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
}