Skip to content

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

// password-strength-analyser — Zig port: password strength scoring & feedback. Language: Zig (0.12+). Port of src/lib/password-strength.ts — same heuristic estimator as this dir's python.py (the live TS/Go twins wrap zxcvbn, too large to vendor); userInputs seeding omitted here for the 80-line budget (see java.java).
const std = @import("std");

const labels = [_][]const u8{ "Very weak", "Weak", "Fair", "Good", "Strong" };
const thresholds = [_]f64{ 28, 36, 60, 128 };
const common = [_][]const u8{
    "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",
};
const sequences = [_][]const u8{ "qwertyuiop", "asdfghjkl", "zxcvbnm", "1234567890", "0987654321", "abcdefg", "gfedcba" };
const Analysis = struct { score: u8, label: []const u8, entropy: f64, crack_time: []const u8 };

fn poolSize(pw: []const u8) u32 {
    var mask: u8 = 0; // bits: 1 lower, 2 upper, 4 digit, 8 symbol, 16 other
    for (pw) |c| {
        const bit: u8 = if (c >= 128) 16 else if (std.ascii.isLower(c)) 1 else if (std.ascii.isUpper(c)) 2 else if (std.ascii.isDigit(c)) 4 else 8;
        mask |= bit;
    }
    var pool: u32 = 0;
    if (mask & 1 != 0) pool += 26;
    if (mask & 2 != 0) pool += 26;
    if (mask & 4 != 0) pool += 10;
    if (mask & 8 != 0) pool += 33;
    if (mask & 16 != 0) pool += 128;
    return pool;
}
fn containsSequence(lower: []const u8, seq: []const u8) bool {
    var i: usize = 0;
    while (i + 4 <= seq.len) : (i += 1)
        if (std.mem.indexOf(u8, lower, seq[i .. i + 4]) != null) return true;
    return false;
}
var crack_buf: [40]u8 = undefined; // one buffer — fine for a single-threaded demo
fn crackTime(e: f64) []const u8 {
    if (e <= 0) return "instant";
    const s = std.math.pow(f64, 2, e) / 1e10;
    if (s < 1) return "instant";
    const units = [_]f64{ 1, 60, 3600, 86400, 2592000, 31536000 };
    const names = [_][]const u8{ "second", "minute", "hour", "day", "month", "year" };
    var i: usize = 0;
    while (i < 5 and s >= units[i + 1]) i += 1;
    const v = s / units[i];
    if (v >= 1e9) return "centuries";
    if (v >= 1e6) return std.fmt.bufPrint(&crack_buf, "{d:.0} million years", .{v / 1e6}) catch "centuries";
    if (v >= 1e3) return std.fmt.bufPrint(&crack_buf, "{d:.0} thousand years", .{v / 1e3}) catch "centuries";
    return std.fmt.bufPrint(&crack_buf, "{d:.0} {s}s", .{ v, names[i] }) catch "centuries";
}

fn analyse(password: []const u8) Analysis {
    const pw = password[0..@min(password.len, 255)]; // display port: first 255 bytes
    const len: f64 = @floatFromInt(pw.len); const pool = poolSize(pw);
    const lp: f64 = if (pool > 1) std.math.log2(@floatFromInt(pool)) else 0;
    var lower_buf: [255]u8 = undefined; const lower = std.ascii.lowerString(&lower_buf, pw);
    var seen = [_]bool{false} ** 256; var unique: usize = 0;
    for (lower) |c| if (!seen[c]) {
        seen[c] = true;
        unique += 1;
    };
    // Base entropy len*log2(pool), capped by variety — repeats earn quarter credit.
    const u: f64 = @floatFromInt(unique); var e: f64 = if (pool > 1) @min(len * lp, (u + (len - u) * 0.25) * lp) else 0;
    var is_common = false; for (common) |c| if (std.mem.eql(u8, lower, c)) is_common = true;
    if (is_common) {
        e = 0; // blocklist hit — zxcvbn's dictionary collapse
    } else {
        for (sequences) |seq| if (containsSequence(lower, seq)) e -= 12;
        if (e < 0) e = 0;
    }
    const score: u8 = if (pw.len < 4 or e < thresholds[0]) 0 else if (e < thresholds[1]) 1 else if (e < thresholds[2]) 2 else if (e < thresholds[3]) 3 else 4;
    return .{ .score = score, .label = labels[score], .entropy = e, .crack_time = crackTime(e) };
}

pub fn main() void {
    const samples = [_][]const u8{ "password", "12345678", "Tr0ub4dour&3", "correct horse battery staple", "u2#9Xq!Lp$7wZ" };
    for (samples) |pw| {
        const r = analyse(pw);
        std.debug.print("{s:<26} {d}/4 {s:<9} {d:6.1} bits  {s}\n", .{ pw, r.score, r.label, r.entropy, r.crack_time });
    }
}

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 →