Skip to content

Instantly share code, notes, and snippets.

@notcancername
Last active August 28, 2024 17:22
Show Gist options
  • Save notcancername/1fad7244a22c4d3fda508513c1e7dd81 to your computer and use it in GitHub Desktop.
Save notcancername/1fad7244a22c4d3fda508513c1e7dd81 to your computer and use it in GitHub Desktop.
passphrase generator, with adjustable entropy
const std = @import("std");
const delimiter = '\n';
const default_bits = 64;
const Wordlist = struct {
bytes: std.ArrayListUnmanaged(u8),
word_indices: std.ArrayListUnmanaged(usize),
allocator: std.mem.Allocator,
pub fn read(file: std.fs.File, allocator: std.mem.Allocator) !Wordlist {
const meta = try file.metadata();
if (meta.kind() != .file) return error.NotRegularFile;
if (meta.size() >= std.math.maxInt(usize)) return error.FileTooLarge;
const sz: usize = @intCast(meta.size());
if (sz == 0) return .{
.bytes = .{},
.word_indices = .{},
.allocator = allocator,
};
var bytes = try std.ArrayListUnmanaged(u8).initCapacity(allocator, sz + 1);
errdefer bytes.deinit(allocator);
var word_indices = try std.ArrayListUnmanaged(usize).initCapacity(allocator, sz + 1);
errdefer word_indices.deinit(allocator);
bytes.items.len = try file.preadAll(bytes.allocatedSlice(), 0);
var index: usize = 0;
while (std.mem.indexOfScalarPos(u8, bytes.items, index, delimiter)) |end_index| {
word_indices.appendAssumeCapacity(index);
index = end_index + 1;
}
if (bytes.items[bytes.items.len - 1] != delimiter) try bytes.append(allocator, delimiter);
word_indices.shrinkAndFree(allocator, word_indices.items.len);
return .{
.bytes = bytes,
.word_indices = word_indices,
.allocator = allocator,
};
}
pub fn at(w: Wordlist, index: usize) [:delimiter]const u8 {
std.debug.assert(index < w.word_indices.items.len);
const p: [*:delimiter]const u8 = @ptrCast(w.bytes.items[w.word_indices.items[index]..].ptr);
return std.mem.span(p);
}
pub fn choice(w: Wordlist, random: std.Random) [:delimiter]const u8 {
return w.at(random.uintLessThan(usize, w.word_indices.items.len));
}
pub fn deinit(w: *Wordlist) void {
w.word_indices.deinit(w.allocator);
w.bytes.deinit(w.allocator);
w.* = undefined;
}
};
pub fn main() !u8 {
var buf_stdout = std.io.bufferedWriter(std.io.getStdOut().writer());
defer buf_stdout.flush() catch {};
const stdout = buf_stdout.writer();
var buf_stderr = std.io.bufferedWriter(std.io.getStdErr().writer());
defer buf_stderr.flush() catch {};
const stderr = buf_stderr.writer();
var sf = std.heap.stackFallback(32 << 10, std.heap.page_allocator);
const allocator = sf.get();
const wl_file, const bits = parse_args: {
var ai = try std.process.argsWithAllocator(allocator);
defer ai.deinit();
const progname = ai.next() orelse return error.ShitsFucked;
const wl_path = ai.next() orelse {
try stderr.print("{s} <wordlist> [bits of entropy, default {d}]\n", .{progname, default_bits});
return 1;
};
const wl_file = std.fs.cwd().openFile(wl_path, .{}) catch |e| {
try stderr.print("failed to open `{s}': {s}\n", .{wl_path, @errorName(e)});
return 1;
};
const bits_str = ai.next() orelse break :parse_args .{
wl_file,
default_bits,
};
const bits = std.fmt.parseInt(u64, bits_str, 10) catch |e| {
switch (e) {
error.InvalidCharacter => try stderr.print("{s} doesn't look like an integer to me\n", .{bits_str}),
error.Overflow => return e,
}
return 1;
};
break :parse_args .{
wl_file,
bits,
};
};
var wl = try Wordlist.read(wl_file, allocator);
defer wl.deinit();
const nb_words = calculate_nb_words: {
const log2_all_words = std.math.log2(@as(f64, @floatFromInt(wl.word_indices.items.len)));
const f_bits: f64 = @floatFromInt(bits);
break :calculate_nb_words @as(u64, @intFromFloat(@ceil(f_bits / log2_all_words)));
};
var rng_state = seed: {
var seed: [std.Random.DefaultCsprng.secret_seed_length]u8 = undefined;
try std.posix.getrandom(&seed);
break :seed std.Random.DefaultCsprng.init(seed);
};
const rng = rng_state.random();
for (0..nb_words - 1) |_| {
try stdout.print("{s} ", .{wl.choice(rng)});
}
try stdout.print("{s}\n", .{wl.choice(rng)});
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment