Passphrase Generator — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// Passphrase generation + entropy scoring.
//
// Language: Java (17+, standard library only)
// Ported from src/lib/passphrase-generator.ts
// display source — part of CosmoDev's polyglot tool pages.
//
// NOTE: the 7,776-word EFF long wordlist (https://www.eff.org/sedol) must be
// loaded separately — e.g. from eff_wordlist.txt on the classpath or disk —
// and passed into generatePassphrase as a List<String>. It is deliberately
// not inlined (it would dwarf the logic ~40:1). Word selection uses
// SecureRandom with rejection sampling, so every word index is uniform over
// the 7,776-word EFF long wordlist — no modulo bias.
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public final class PassphraseGenerator {
public enum Separator { SPACE, DASH, DOT, UNDERSCORE, NONE }
public record PassphraseOptions(
Integer wordCount,
Separator separator,
boolean capitalize,
boolean appendDigit,
/** Attack speed for the crack-time estimate. Default 1 trillion guesses/s. */
Double guessesPerSecond) {
public static PassphraseOptions defaults() {
return new PassphraseOptions(null, Separator.SPACE, false, false, null);
}
}
public record CrackTimeEstimate(double seconds, String human) {
}
public record Passphrase(List<String> words, String passphrase, double entropy,
CrackTimeEstimate crackTime) {
}
/** Size of the EFF long wordlist (6^5). */
public static final int EFF_WORDLIST_LENGTH = 7776;
public static final int MIN_WORDS = 3;
public static final int MAX_WORDS = 10;
public static final int DEFAULT_WORDS = 6;
public static final double DEFAULT_GUESSES_PER_SECOND = 1e12;
private static final SecureRandom RANDOM = new SecureRandom();
private static String separatorString(Separator separator) {
return switch (separator) {
case SPACE -> " ";
case DASH -> "-";
case DOT -> ".";
case UNDERSCORE -> "_";
case NONE -> "";
};
}
// Rejection-sampling ceiling for a uniform uint16 over 7,776: the largest
// multiple of 7,776 that fits in [0, 65536). 7776 x 8 = 62208; draws >= 62208
// are rejected so `v % 7776` is uniform (a plain `% 7776` would favor the
// first 65536 % 7776 = 2048 indexes).
private static final int WORD_LIMIT = EFF_WORDLIST_LENGTH * (65536 / EFF_WORDLIST_LENGTH);
/** Draw one uniformly-random word from the EFF long wordlist (CSPRNG). */
private static String randomWord(List<String> wordlist) {
int v;
do {
v = RANDOM.nextInt(65536);
} while (v >= WORD_LIMIT);
return wordlist.get(v % EFF_WORDLIST_LENGTH);
}
/** Draw one uniformly-random digit 0-9 (CSPRNG, rejection-sampled). */
private static String randomDigit() {
int v;
do {
v = RANDOM.nextInt(256);
} while (v >= 250); // largest multiple of 10 in [0, 256)
return String.valueOf(v % 10);
}
/** Theoretical word entropy in bits: wordCount x log2(7776). */
public static double entropyBits(int wordCount) {
if (wordCount <= 0) {
return 0;
}
return wordCount * (Math.log(EFF_WORDLIST_LENGTH) / Math.log(2));
}
private static final double YEAR = 31_557_600; // seconds (Julian year)
private static final double[][] SCALES = {
{1e12, "trillion"},
{1e9, "billion"},
{1e6, "million"},
{1e3, "thousand"},
};
/**
* 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).
*/
public static String formatCrackTime(double seconds) {
if (!Double.isFinite(seconds) || seconds < 0) {
return "-";
}
if (seconds < 1) {
return "< 1 second";
}
if (seconds < 60) {
return span(seconds, "second", "seconds");
}
if (seconds < 3600) {
return span(seconds / 60, "minute", "minutes");
}
if (seconds < 86_400) {
return span(seconds / 3600, "hour", "hours");
}
if (seconds < YEAR) {
return span(seconds / 86_400, "day", "days");
}
double years = seconds / YEAR;
if (years < 100) {
return span(years, "year", "years");
}
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.
double centuries = years / 100;
for (double[] scale : SCALES) {
if (centuries >= scale[0]) {
return String.format(Locale.US, "%,.1f %s centuries",
round(centuries / scale[0]), scale[1]);
}
}
return String.format(Locale.US, "%,.1f centuries", round(centuries));
}
/** Round + pluralize {@code v unit}; 1 decimal below 10, whole numbers above. */
private static String span(double v, String singular, String plural) {
double n = round(v);
String num = n == Math.floor(n)
? String.format(Locale.US, "%,.0f", n)
: String.format(Locale.US, "%,.1f", n);
return num + " " + (n == 1 ? singular : plural);
}
private static double round(double v) {
return v >= 10 ? Math.round(v) : Math.round(v * 10) / 10.0;
}
/** Average time to crack (seconds) = 2^(bits-1) / guessesPerSecond. */
public static double crackTimeSeconds(double bits, double guessesPerSecond) {
return Math.pow(2, bits - 1) / guessesPerSecond;
}
private static String cap(String w) {
return Character.toUpperCase(w.charAt(0)) + w.substring(1);
}
private static int clamp(int v, int lo, int hi) {
return Math.min(hi, Math.max(lo, v));
}
/** Generate a diceware passphrase from the EFF long wordlist (see header note). */
public static Passphrase generatePassphrase(List<String> effWordlist, PassphraseOptions options) {
if (effWordlist == null || effWordlist.size() != EFF_WORDLIST_LENGTH) {
throw new IllegalArgumentException(
"The EFF long wordlist (exactly " + EFF_WORDLIST_LENGTH
+ " words) must be loaded separately and passed in");
}
int raw = options.wordCount() == null ? DEFAULT_WORDS : options.wordCount();
int wordCount = clamp(raw, MIN_WORDS, MAX_WORDS);
Separator separator = options.separator() == null ? Separator.SPACE : options.separator();
double gps = options.guessesPerSecond() == null
? DEFAULT_GUESSES_PER_SECOND
: options.guessesPerSecond();
List<String> words = new ArrayList<>(wordCount);
for (int i = 0; i < wordCount; i++) {
words.add(randomWord(effWordlist));
}
List<String> shown = new ArrayList<>(wordCount);
if (options.capitalize()) {
for (String w : words) {
shown.add(cap(w));
}
} else {
shown.addAll(words);
}
StringBuilder sb = new StringBuilder(String.join(separatorString(separator), shown));
if (options.appendDigit()) {
sb.append(randomDigit());
}
double entropy = entropyBits(wordCount);
double seconds = crackTimeSeconds(entropy, gps);
return new Passphrase(List.copyOf(words), sb.toString(), entropy,
new CrackTimeEstimate(seconds, formatCrackTime(seconds)));
}
}
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 →