Skip to content

Password Strength Analyser — Kotlin 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 Kotlin implementation — the same logic the interactive tool runs, in a shareable, citable form.

// password-strength-analyser — password strength scoring & feedback. Language: Kotlin (JVM 1.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 kotlin.math.log2
import kotlin.math.max
import kotlin.math.min

object PasswordStrengthAnalyser {
    val LABELS = arrayOf("Very weak", "Weak", "Fair", "Good", "Strong")
    val THRESHOLDS = doubleArrayOf(28.0, 36.0, 60.0, 128.0)
    /** Curated common-password blocklist — an exact (lowercased) match forces score 0. */
    val COMMON = setOf(
        "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")
    /** Keyboard / numeric runs — any length-4 window inside costs 12 bits. */
    val SEQUENCES = listOf("qwertyuiop", "asdfghjkl", "zxcvbnm", "1234567890", "0987654321", "abcdefg", "gfedcba")
    /** Strength breakdown — mirrors the TS PasswordAnalysis / Go pwstrength.Result. */
    data class Analysis(val score: Int, val label: String, val entropy: Double, val crackTime: String)

    /** Guess-pool size: 26 lower + 26 upper + 10 digit + 33 ASCII symbol + 128 other. */
    fun poolSize(pw: String): Int =
        (if (pw.any { it in 'a'..'z' }) 26 else 0) + (if (pw.any { it in 'A'..'Z' }) 26 else 0) +
            (if (pw.any { it in '0'..'9' }) 10 else 0) + (if (pw.any { it.code < 128 && !it.isLetterOrDigit() }) 33 else 0) +
            (if (pw.any { it.code >= 128 }) 128 else 0)

    private fun containsSequence(lower: String, seq: String): Boolean =
        (0..seq.length - 4).any { lower.contains(seq.substring(it, it + 4)) }

    /** Human crack time at 1e10 guesses/s — zxcvbn's offline-fast display. */
    fun crackTime(entropy: Double): String {
        if (entropy <= 0) return "instant"
        val seconds = Math.pow(2.0, entropy) / 1e10
        if (seconds < 1) return "instant"
        val (div, name) = listOf(60.0 to "minute", 3_600.0 to "hour", 86_400.0 to "day",
                                 2_592_000.0 to "month", 31_536_000.0 to "year")
            .lastOrNull { (s, _) -> seconds >= s } ?: (1.0 to "second")
        val v = seconds / div
        return when {
            v >= 1e9 -> "centuries"
            v >= 1e6 -> "%.0f million years".format(v / 1e6)
            v >= 1e3 -> "%.0f thousand years".format(v / 1e3)
            else -> "%.0f ${name}s".format(v)
        }
    }

    /** Scores 0-4. [userInputs] seeds the estimator with related tokens (a substring match weakens the score). */
    fun analyse(password: String, userInputs: List<String> = emptyList()): Analysis {
        val len = password.length
        val pool = poolSize(password)
        val lp = if (pool > 1) log2(pool.toDouble()) else 0.0
        val unique = password.toCharArray().distinct().size
        // Base entropy len*log2(pool), capped by variety — repeats earn quarter credit.
        var entropy = if (pool > 1) min(len * lp, (unique + (len - unique) * 0.25) * lp) else 0.0
        val lower = password.lowercase()
        if (lower in COMMON) entropy = 0.0 // blocklist hit — dictionary collapse
        else {
            for (seq in SEQUENCES) if (containsSequence(lower, seq)) entropy -= 12.0
            for (ui in userInputs) {
                val l = ui.lowercase()
                if (l.length >= 3 && lower.contains(l)) entropy -= 10.0
            }
            entropy = max(0.0, entropy)
        }
        val score = when {
            len < 4 || entropy < THRESHOLDS[0] -> 0
            entropy < THRESHOLDS[1] -> 1
            entropy < THRESHOLDS[2] -> 2
            entropy < THRESHOLDS[3] -> 3
            else -> 4
        }
        return Analysis(score, LABELS[score], entropy, crackTime(entropy))
    }
}

fun main() {
    for (pw in listOf("password", "12345678", "Tr0ub4dour&3", "correct horse battery staple", "u2#9Xq!Lp$7wZ")) {
        val r = PasswordStrengthAnalyser.analyse(pw)
        println("%-26s %d/4 %-9s %6.1f bits  %s".format(pw, r.score, r.label, r.entropy, 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 →