Password Strength Analyser — Swift source
Estimate password strength with zxcvbn - realistic dictionary and pattern cracking with crack-time estimates and improvement suggestions. Runs entirely in your browser.
This is the Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// password-strength-analyser — password strength scoring & feedback. Language: Swift (5.9+). Port of src/lib/password-strength.ts — same self-contained heuristic estimator as this dir's python.py (the live TS/Go twins wrap zxcvbn, whose ranking data is too large to vendor).
import Foundation
let labels = ["Very weak", "Weak", "Fair", "Good", "Strong"]
let thresholds: [Double] = [28, 36, 60, 128]
let common: Set<String> = [
"1234", "12345", "123123", "123456", "12345678", "123456789", "1234567890",
"000000", "111111", "654321", "666666", "abc123", "admin", "baseball", "batman",
"dragon", "iloveyou", "letmein", "login", "master", "monkey", "passw0rd", "password",
"password1", "princess", "qwerty", "root", "shadow", "superman", "sunshine", "trustno1", "welcome"]
let sequences = ["qwertyuiop", "asdfghjkl", "zxcvbnm", "1234567890", "0987654321", "abcdefg", "gfedcba"]
struct Analysis {
var score: Int // 0 (worst) ... 4 (best).
var label: String // Human strength label.
var entropy: Double // Estimated guess entropy, in bits.
var crackTime: String // Single human crack-time string.
}
func poolSize(_ pw: String) -> Int {
var lower = false, upper = false, digit = false, sym = false, other = false
for s in pw.unicodeScalars {
switch s.value {
case 97...122: lower = true // a-z
case 65...90: upper = true // A-Z
case 48...57: digit = true // 0-9
case 0...127: sym = true // other ASCII (space, punctuation)
default: other = true // non-ASCII code points
}
}
return (lower ? 26 : 0) + (upper ? 26 : 0) + (digit ? 10 : 0) + (sym ? 33 : 0) + (other ? 128 : 0)
}
func containsSequence(_ lower: String, _ seq: String) -> Bool {
guard seq.count >= 4 else { return false }
return (0...seq.count - 4).contains { lower.contains(seq.dropFirst($0).prefix(4)) }
}
func crackTime(_ entropy: Double) -> String {
guard entropy > 0 else { return "instant" } // instant below 1 second
let seconds = pow(2, entropy) / 1e10
guard seconds >= 1 else { return "instant" }
var v = seconds; var name = "second"
if seconds >= 31_536_000 { v = seconds / 31_536_000; name = "year" }
else if seconds >= 2_592_000 { v = seconds / 2_592_000; name = "month" }
else if seconds >= 86_400 { v = seconds / 86_400; name = "day" }
else if seconds >= 3_600 { v = seconds / 3_600; name = "hour" }
else if seconds >= 60 { v = seconds / 60; name = "minute" }
switch v {
case 1e9...: return "centuries"
case 1e6..<1e9: return String(format: "%.0f million years", v / 1e6)
case 1e3..<1e6: return String(format: "%.0f thousand years", v / 1e3)
default: return String(format: "%.0f %@s", v, name)
}
}
/// Scores 0-4. `userInputs` seeds the estimator with related tokens (a substring match weakens the score).
func analyse(_ password: String, userInputs: [String] = []) -> Analysis {
let len = password.count; let pool = poolSize(password)
let lp = pool > 1 ? log2(Double(pool)) : 0; let unique = Set(password).count
// Base entropy len*log2(pool), capped by variety — repeats earn quarter credit.
var entropy = pool > 1 ? min(Double(len) * lp, (Double(unique) + Double(len - unique) * 0.25) * lp) : 0
let lower = password.lowercased()
if common.contains(lower) { entropy = 0 } // blocklist hit — dictionary collapse
else {
for seq in sequences where containsSequence(lower, seq) { entropy -= 12 }
for ui in userInputs {
let l = ui.lowercased()
if l.count >= 3 && lower.contains(l) { entropy -= 10 }
}
entropy = max(0, entropy)
}
let score = len < 4 || entropy < thresholds[0] ? 0 : entropy < thresholds[1] ? 1 : entropy < thresholds[2] ? 2 : entropy < thresholds[3] ? 3 : 4
return Analysis(score: score, label: labels[score], entropy: entropy, crackTime: crackTime(entropy))
}
for pw in ["password", "12345678", "Tr0ub4dour&3", "correct horse battery staple", "u2#9Xq!Lp$7wZ"] {
let r = analyse(pw)
print("\(pw.padding(toLength: 26, withPad: " ", startingAt: 0)) \(r.score)/4 \(r.label.padding(toLength: 9, withPad: " ", startingAt: 0)) \(String(format: "%6.1f", r.entropy)) bits \(r.crackTime)")
}
Also available in 13 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 →