Passphrase Generator — C++ source
Generate memorable, high-entropy passphrases from the EFF diceware wordlist using cryptographic randomness. Each word is drawn with crypto.getRandomValues and rejection sampling (no modulo bias), and every passphrase shows its exact entropy in bits plus the estimated crack time at 1 trillion guesses/sec. Everything runs locally - nothing is sent anywhere.
This is the C++ implementation — the same logic the interactive tool runs, in a shareable, citable form.
// passphrase-generator — diceware passphrase generation + entropy scoring.
//
// Language: C++ (C++17, standard library only)
// Ported from src/lib/passphrase-generator.ts (the canonical TypeScript
// implementation). display source — part of CosmoDev's polyglot tool pages.
//
// The TS reference draws words with Web Crypto's CSPRNG plus hand-rolled
// rejection sampling over a uint16 (every word index uniform over the
// 7,776-word EFF long wordlist - no modulo bias). C++ has no standard
// cryptographic RNG, so this port draws from std::random_device through
// std::uniform_int_distribution, which performs the same de-biasing
// internally (rejection to a multiple of the range). The wordlist is supplied
// by the caller; entropy assumes the full 7,776-word EFF long list.
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <random>
#include <string>
#include <vector>
namespace passphrase_generator {
/** Size of the EFF long wordlist (6^5). */
constexpr std::size_t EFF_WORDLIST_LENGTH = 7776;
constexpr int MIN_WORDS = 3;
constexpr int MAX_WORDS = 10;
constexpr int DEFAULT_WORDS = 6;
constexpr double DEFAULT_GUESSES_PER_SECOND = 1e12;
/** Separator inserted between words. */
enum class Separator { Space, Dash, Dot, Underscore, None };
/** Generation knobs; unset fields fall back to the defaults. */
struct PassphraseOptions {
int wordCount = DEFAULT_WORDS;
Separator separator = Separator::Space;
bool capitalize = false;
bool appendDigit = false;
/** Attack speed for the crack-time estimate. Default 1 trillion guesses/s. */
double guessesPerSecond = DEFAULT_GUESSES_PER_SECOND;
};
struct CrackTimeEstimate {
double seconds;
std::string human;
};
struct Passphrase {
std::vector<std::string> words; // as drawn, before capitalization
std::string passphrase;
double entropy = 0;
CrackTimeEstimate crackTime;
};
// --- CSPRNG draws (uniform, no modulo bias) -----------------------------------
/** Uniform random size_t in [0, n) from std::random_device. */
static std::size_t uniformIndex(std::size_t n) {
static std::mt19937_64 engine((std::random_device())());
std::uniform_int_distribution<std::size_t> dist(0, n - 1);
return dist(engine);
}
/** Draw one uniformly-random word from the (EFF long) wordlist. */
static std::string randomWord(const std::vector<std::string>& wordlist) {
return wordlist[uniformIndex(wordlist.size())];
}
/** Draw one uniformly-random digit character 0-9. */
static char randomDigit() {
return char('0' + uniformIndex(10));
}
/** Theoretical word entropy in bits: wordCount x log2(7776). */
double entropyBits(int wordCount) {
if (wordCount <= 0) return 0;
return wordCount * std::log2(double(EFF_WORDLIST_LENGTH));
}
constexpr double YEAR = 31557600.0; // seconds (Julian year)
/** Round to 1 decimal below 10, whole numbers above. */
static double round1(double v) { return v >= 10 ? std::floor(v + 0.5) : std::floor(v * 10 + 0.5) / 10; }
/** Format a rounded number the way the TS side does (no locale grouping). */
static std::string num(double v) {
const double r = round1(v);
if (r == std::floor(r)) return std::to_string(static_cast<long long>(r));
char buf[32];
snprintf(buf, sizeof(buf), "%.1f", r);
return buf;
}
/** Round + pluralize `v unit`; 1 decimal below 10, whole numbers above. */
static std::string span(double v, const std::string& singular, const std::string& plural = "") {
const std::string n = num(v);
return n + " " + (n == "1" ? singular : (plural.empty() ? singular + "s" : plural));
}
/**
* Human-readable crack-time span. Uses centuries/millennia past a year,
* then collapses to scaled words ("2.4 billion centuries") so the string
* stays readable at diceware entropies (60-130 bits).
*/
std::string formatCrackTime(double seconds) {
if (!std::isfinite(seconds) || seconds < 0) return "-";
if (seconds < 1) return "< 1 second";
if (seconds < 60) return span(seconds, "second");
if (seconds < 3600) return span(seconds / 60, "minute");
if (seconds < 86400) return span(seconds / 3600, "hour");
if (seconds < YEAR) return span(seconds / 86400, "day");
const double years = seconds / YEAR;
if (years < 100) return span(years, "year");
if (years < 1000) return span(years / 100, "century", "centuries");
if (years < 1e6) return span(years / 1000, "millennium", "millennia");
// years >= 1e6 here, so centuries >= 10,000: collapse to a scaled plural.
const double centuries = years / 100;
static const double FACTORS[] = {1e12, 1e9, 1e6, 1e3};
static const char* const NAMES[] = {"trillion", "billion", "million", "thousand"};
for (int i = 0; i < 4; i++) {
if (centuries >= FACTORS[i]) return num(centuries / FACTORS[i]) + " " + NAMES[i] + " centuries";
}
return num(centuries) + " centuries";
}
/** Average time to crack (seconds) = 2^(bits-1) / guessesPerSecond. */
double crackTimeSeconds(double bits, double guessesPerSecond) {
return std::pow(2.0, bits - 1) / guessesPerSecond;
}
static std::string separatorString(Separator s) {
switch (s) {
case Separator::Space: return " ";
case Separator::Dash: return "-";
case Separator::Dot: return ".";
case Separator::Underscore: return "_";
case Separator::None: return "";
}
return " ";
}
static std::string cap(const std::string& w) {
if (w.empty()) return w;
std::string out = w;
out[0] = char(std::toupper(static_cast<unsigned char>(out[0])));
return out;
}
static int clampInt(int v, int lo, int hi) { return std::min(hi, std::max(lo, v)); }
/** Generate a diceware passphrase from the given wordlist. */
Passphrase generatePassphrase(const std::vector<std::string>& wordlist,
const PassphraseOptions& options = {}) {
const int wordCount = clampInt(options.wordCount, MIN_WORDS, MAX_WORDS);
const std::string sep = separatorString(options.separator);
std::vector<std::string> words;
words.reserve(wordCount);
for (int i = 0; i < wordCount; i++) words.push_back(randomWord(wordlist));
std::string passphrase;
for (int i = 0; i < wordCount; i++) {
if (i > 0) passphrase += sep;
passphrase += options.capitalize ? cap(words[i]) : words[i];
}
if (options.appendDigit) passphrase += randomDigit();
const double entropy = entropyBits(wordCount);
const double seconds = crackTimeSeconds(entropy, options.guessesPerSecond);
Passphrase out;
out.words = std::move(words);
out.passphrase = std::move(passphrase);
out.entropy = entropy;
out.crackTime = {seconds, formatCrackTime(seconds)};
return out;
}
} // namespace passphrase_generator
Also available in 12 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 →