Passphrase Generator — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// passphrase-generator — passphrase generation + entropy scoring.
//
// Language: Swift 5.9+ (Foundation 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 disk / in a bundle — and
// passed into generatePassphrase as a [String]. It is deliberately not
// inlined (it would dwarf the logic ~40:1). Word selection uses a CSPRNG
// (SystemRandomNumberGenerator) with rejection sampling, so every word index
// is uniform over the 7,776-word list — no modulo bias.
import Foundation
// MARK: - Types
enum Separator: String {
case space
case dash
case dot
case underscore
case none
var string: String {
switch self {
case .space: return " "
case .dash: return "-"
case .dot: return "."
case .underscore: return "_"
case .none: return ""
}
}
}
struct PassphraseOptions {
var wordCount: Int? = nil
var separator: Separator = .space
var capitalize: Bool = false
var appendDigit: Bool = false
/// Attack speed for the crack-time estimate. Default 1 trillion guesses/s.
var guessesPerSecond: Double = defaultGuessesPerSecond
}
struct CrackTimeEstimate {
let seconds: Double
let human: String
}
struct Passphrase {
let words: [String]
let passphrase: String
let entropy: Double
let crackTime: CrackTimeEstimate
}
// MARK: - Constants
/// Size of the EFF long wordlist (6^5).
let effWordlistLength = 7776
let minWords = 3
let maxWords = 10
let defaultWords = 6
let defaultGuessesPerSecond = 1e12
// 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). Computed from the actual list size so
// any injected wordlist stays uniform too.
private func wordLimit(for count: Int) -> Int {
(65536 / count) * count
}
// MARK: - Random draws (CSPRNG + rejection sampling)
/// Draw one uniformly-random word from the EFF long wordlist.
private func randomWord(_ wordlist: [String]) -> String {
let n = wordlist.count
guard n > 0 else { return "" }
let limit = wordLimit(for: n)
var rng = SystemRandomNumberGenerator()
var v: Int
repeat {
v = Int(UInt16.random(in: .min ... .max, using: &rng))
} while v >= limit
return wordlist[v % n]
}
/// Draw one uniformly-random digit 0-9 (rejection-sampled on uint8).
private func randomDigit() -> String {
var rng = SystemRandomNumberGenerator()
var v: Int
repeat {
v = Int(UInt8.random(in: .min ... .max, using: &rng))
} while v >= 250 // largest multiple of 10 in [0, 256)
return String(v % 10)
}
// MARK: - Entropy + crack time
/// Theoretical word entropy in bits: wordCount x log2(7776).
func entropyBits(_ wordCount: Int) -> Double {
guard wordCount > 0 else { return 0 }
return Double(wordCount) * log2(Double(effWordlistLength))
}
let year = 31_557_600.0 // seconds (Julian year)
let scales: [(Double, String)] = [
(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).
*/
func formatCrackTime(_ seconds: Double) -> String {
if seconds.isNaN || seconds.isInfinite || 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 < 86_400 { return span(seconds / 3600, "hour") }
if seconds < year { return span(seconds / 86_400, "day") }
let years = seconds / year
if years < 100 { return span(years, "year") }
if years < 1000 { return span(years / 100, "century", plural: "centuries") }
if years < 1e6 { return span(years / 1000, "millennium", plural: "millennia") }
// years >= 1e6 here, so centuries >= 10,000: collapse to a scaled plural.
let centuries = years / 100
let (factor, name) = scales.first { centuries >= $0.0 }!
return "\(round(centuries / factor).formatted()) \(name) centuries"
}
/// Round + pluralize `v unit`; 1 decimal below 10, whole numbers above.
private func span(_ v: Double, _ singular: String, plural: String? = nil) -> String {
let n = round(v)
let unit = n == 1 ? singular : (plural ?? singular + "s")
return "\(n.formatted()) \(unit)"
}
private func round(_ v: Double) -> Double {
v >= 10 ? (v).rounded() : (v * 10).rounded() / 10
}
/// Average time to crack (seconds) = 2^(bits-1) / guessesPerSecond.
func crackTimeSeconds(_ bits: Double, _ guessesPerSecond: Double) -> Double {
return pow(2, bits - 1) / guessesPerSecond
}
// MARK: - Generation
/// Generate a diceware passphrase from the EFF long wordlist.
func generatePassphrase(_ wordlist: [String], options: PassphraseOptions = PassphraseOptions()) -> Passphrase {
let raw = options.wordCount ?? defaultWords
let wordCount = min(maxWords, max(minWords, raw))
let separator = options.separator
let capitalize = options.capitalize
let appendDigit = options.appendDigit
let gps = options.guessesPerSecond
let words = (0..<wordCount).map { _ in randomWord(wordlist) }
let shown = capitalize ? words.map(cap) : words
var passphrase = shown.joined(separator: separator.string)
if appendDigit { passphrase += randomDigit() }
let entropy = entropyBits(wordCount)
let seconds = crackTimeSeconds(entropy, gps)
return Passphrase(words: words, passphrase: passphrase, entropy: entropy,
crackTime: CrackTimeEstimate(seconds: seconds, human: formatCrackTime(seconds)))
}
private func cap(_ w: String) -> String {
w.prefix(1).uppercased() + w.dropFirst()
}
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 →