Skip to content

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 →