Skip to content

Password Generator — Zig source

Generate cryptographically-random passwords with a CSPRNG using rejection sampling (no modulo bias). Shows live entropy in bits, a 5-tier strength meter, average offline-GPU crack time, and a Pro mode with the entropy formula, a crack-time-vs-length curve, and a 4-scenario attack table. Everything runs locally - nothing is sent anywhere.

This is the Zig implementation — the same logic the interactive tool runs, in a shareable, citable form.

// password-generator — CSPRNG password generation + entropy scoring. Language: Zig 0.14 (std.crypto.random). Port of src/lib/password.ts.

const std = @import("std");
const lower = "abcdefghijklmnopqrstuvwxyz";
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const numbers = "0123456789", symbols = "!@#$%^&*()-_=+[]{};:,.<>?/";
const ambiguous = "O0Il1|"; // O/0, I/l/1, pipe — dropped on request
const Options = struct {
    length: usize, upper: bool = false, lower: bool = false,
    numbers: bool = false, symbols: bool = false, exclude_ambiguous: bool = false,
};
// Concat the selected pools, then drop ambiguous glyphs (mirrors buildCharset);
// the charset is written into caller-owned `buf` — no allocation needed.
fn buildCharset(o: Options, buf: []u8) []const u8 {
    const pools = [_]struct { set: []const u8, on: bool }{
        .{ .set = lower, .on = o.lower },     .{ .set = upper, .on = o.upper },
        .{ .set = numbers, .on = o.numbers }, .{ .set = symbols, .on = o.symbols },
    };
    var n: usize = 0;
    for (pools) |p| if (p.on) {
        @memcpy(buf[n..][0..p.set.len], p.set);
        n += p.set.len;
    };
    if (o.exclude_ambiguous) {
        var w: usize = 0;
        for (buf[0..n]) |c| if (std.mem.indexOfScalar(u8, ambiguous, c) == null) {
            buf[w] = c; // w <= i always, so the compaction is in-place safe
            w += 1;
        };
        n = w;
    }
    return buf[0..n];
}
// Uniform index in [0, n): reject uint32 CSPRNG draws >= the largest multiple
// of n fitting in 2^32, so `% n` carries no modulo bias.
fn unbiasedIndex(n: usize) usize {
    const max: u64 = 1 << 32;
    const limit = max - max % n;
    while (true) {
        const r: u64 = std.crypto.random.int(u32);
        if (r < limit) return @intCast(r % n);
    }
}
// Returns false for an empty pool or empty out — the TS lib returns "" for both.
fn generatePassword(cs: []const u8, out: []u8) bool {
    if (cs.len == 0 or out.len == 0) return false;
    for (out) |*c| c.* = cs[unbiasedIndex(cs.len)];
    return true;
}
// Theoretical entropy in bits: length * log2(|alphabet|).
fn entropyBits(length: usize, charsetSize: usize) f64 {
    if (length == 0 or charsetSize <= 1) return 0.0;
    return @as(f64, @floatFromInt(length)) * std.math.log2(@as(f64, @floatFromInt(charsetSize)));
}
// Strength meter tiers, 1:1 with the TS thresholds.
fn strengthTier(bits: f64) []const u8 {
    if (bits >= 100) return "very strong";
    if (bits >= 70) return "strong";
    if (bits >= 45) return "fair";
    if (bits >= 28) return "weak";
    return "very weak";
}
// Average crack time (s) = 2^(bits-1) / guesses-per-second.
fn crackTimeSeconds(bits: f64, guesses_per_second: f64) f64 {
    return std.math.pow(f64, 2.0, bits - 1) / guesses_per_second;
}
pub fn main() void {
    const o = Options{ .length = 20, .upper = true, .lower = true, .numbers = true, .symbols = true, .exclude_ambiguous = true };
    var buf: [128]u8 = undefined;
    const cs = buildCharset(o, &buf);
    var pw: [20]u8 = undefined;
    if (!generatePassword(cs, &pw)) return;
    const bits = entropyBits(o.length, cs.len);
    std.debug.print("{s} — {d:.1} bits, {s}\n", .{ pw, bits, strengthTier(bits) });
}

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 →