mkpw

init

a2126a1a08e7b7fa4a53f06fab2ee91b6bd0a6a0

SM <seb.michalk@gmail.com>

2026-04-20 10:50:12 +0000

 build.zig             | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++
 build.zig.zon         |  86 +++++++++++++++++++++++++++++++++++++
 readme.txt            |   8 ++++
 src/main.zig          | 105 ++++++++++++++++++++++++++++++++++++++++++++++
 src/root.zig          |  13 ++++++
 zig-out/bin/mkpw      | Bin 0 -> 32872 bytes
 zig-out/lib/libmkpw.a | Bin 0 -> 1048 bytes
 7 files changed, 326 insertions(+)

diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..62ec841
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,114 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{});
+
+    // Use ReleaseSmall optimization for minimal binary size
+    const optimize = std.builtin.OptimizeMode.ReleaseSmall;
+
+    // This creates a "module", which represents a collection of source files alongside
+    // some compilation options, such as optimization mode and linked system libraries.
+    // Every executable or library we compile will be based on one or more modules.
+    const lib_mod = b.createModule(.{
+        // `root_source_file` is the Zig "entry point" of the module. If a module
+        // only contains e.g. external object files, you can make this `null`.
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = b.path("src/root.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // We will also create a module for our other entry point, 'main.zig'.
+    const exe_mod = b.createModule(.{
+        // `root_source_file` is the Zig "entry point" of the module. If a module
+        // only contains e.g. external object files, you can make this `null`.
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = b.path("src/main.zig"),
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // Modules can depend on one another using the `std.Build.Module.addImport` function.
+    // This is what allows Zig source code to use `@import("foo")` where 'foo' is not a
+    // file path. In this case, we set up `exe_mod` to import `lib_mod`.
+    exe_mod.addImport("mkpw_lib", lib_mod);
+
+    // Now, we will create a static library based on the module we created above.
+    // This creates a `std.Build.Step.Compile`, which is the build step responsible
+    // for actually invoking the compiler.
+    const lib = b.addLibrary(.{
+        .linkage = .static,
+        .name = "mkpw",
+        .root_module = lib_mod,
+    });
+
+    // This declares intent for the library to be installed into the standard
+    // location when the user invokes the "install" step (the default step when
+    // running `zig build`).
+    b.installArtifact(lib);
+
+    // This creates another `std.Build.Step.Compile`, but this one builds an executable
+    // rather than a static library.
+    const exe = b.addExecutable(.{
+        .name = "mkpw",
+        .root_module = exe_mod,
+    });
+
+    // This declares intent for the executable to be installed into the
+    // standard location when the user invokes the "install" step (the default
+    // step when running `zig build`).
+    b.installArtifact(exe);
+
+    // This *creates* a Run step in the build graph, to be executed when another
+    // step is evaluated that depends on it. The next line below will establish
+    // such a dependency.
+    const run_cmd = b.addRunArtifact(exe);
+
+    // By making the run step depend on the install step, it will be run from the
+    // installation directory rather than directly from within the cache directory.
+    // This is not necessary, however, if the application depends on other installed
+    // files, this ensures they will be present and in the expected location.
+    run_cmd.step.dependOn(b.getInstallStep());
+
+    // This allows the user to pass arguments to the application in the build
+    // command itself, like this: `zig build run -- arg1 arg2 etc`
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build run`
+    // This will evaluate the `run` step rather than the default, which is "install".
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+
+    // Creates a step for unit testing. This only builds the test executable
+    // but does not run it.
+    const lib_unit_tests = b.addTest(.{
+        .root_module = lib_mod,
+    });
+
+    const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
+
+    const exe_unit_tests = b.addTest(.{
+        .root_module = exe_mod,
+    });
+
+    const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+    // Similar to creating the run step earlier, this exposes a `test` step to
+    // the `zig build --help` menu, providing a way for the user to request
+    // running the unit tests.
+    const test_step = b.step("test", "Run unit tests");
+    test_step.dependOn(&run_lib_unit_tests.step);
+    test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
new file mode 100644
index 0000000..0f1f458
--- /dev/null
+++ b/build.zig.zon
@@ -0,0 +1,86 @@
+.{
+    // This is the default name used by packages depending on this one. For
+    // example, when a user runs `zig fetch --save <url>`, this field is used
+    // as the key in the `dependencies` table. Although the user can choose a
+    // different name, most users will stick with this provided value.
+    //
+    // It is redundant to include "zig" in this name because it is already
+    // within the Zig package namespace.
+    .name = .mkpw,
+
+    // This is a [Semantic Version](https://semver.org/).
+    // In a future version of Zig it will be used for package deduplication.
+    .version = "0.0.0",
+
+    // Together with name, this represents a globally unique package
+    // identifier. This field is generated by the Zig toolchain when the
+    // package is first created, and then *never changes*. This allows
+    // unambiguous detection of one package being an updated version of
+    // another.
+    //
+    // When forking a Zig project, this id should be regenerated (delete the
+    // field and run `zig build`) if the upstream project is still maintained.
+    // Otherwise, the fork is *hostile*, attempting to take control over the
+    // original project's identity. Thus it is recommended to leave the comment
+    // on the following line intact, so that it shows up in code reviews that
+    // modify the field.
+    .fingerprint = 0x4dd4496a9c8314b9, // Changing this has security and trust implications.
+
+    // Tracks the earliest Zig version that the package considers to be a
+    // supported use case.
+    .minimum_zig_version = "0.14.0",
+
+    // This field is optional.
+    // Each dependency must either provide a `url` and `hash`, or a `path`.
+    // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+    // Once all dependencies are fetched, `zig build` no longer requires
+    // internet connectivity.
+    .dependencies = .{
+        // See `zig fetch --save <url>` for a command-line interface for adding dependencies.
+        //.example = .{
+        //    // When updating this field to a new URL, be sure to delete the corresponding
+        //    // `hash`, otherwise you are communicating that you expect to find the old hash at
+        //    // the new URL. If the contents of a URL change this will result in a hash mismatch
+        //    // which will prevent zig from using it.
+        //    .url = "https://example.com/foo.tar.gz",
+        //
+        //    // This is computed from the file contents of the directory of files that is
+        //    // obtained after fetching `url` and applying the inclusion rules given by
+        //    // `paths`.
+        //    //
+        //    // This field is the source of truth; packages do not come from a `url`; they
+        //    // come from a `hash`. `url` is just one of many possible mirrors for how to
+        //    // obtain a package matching this `hash`.
+        //    //
+        //    // Uses the [multihash](https://multiformats.io/multihash/) format.
+        //    .hash = "...",
+        //
+        //    // When this is provided, the package is found in a directory relative to the
+        //    // build root. In this case the package's hash is irrelevant and therefore not
+        //    // computed. This field and `url` are mutually exclusive.
+        //    .path = "foo",
+        //
+        //    // When this is set to `true`, a package is declared to be lazily
+        //    // fetched. This makes the dependency only get fetched if it is
+        //    // actually used.
+        //    .lazy = false,
+        //},
+    },
+
+    // Specifies the set of files and directories that are included in this package.
+    // Only files and directories listed here are included in the `hash` that
+    // is computed for this package. Only files listed here will remain on disk
+    // when using the zig package manager. As a rule of thumb, one should list
+    // files required for compilation plus any license(s).
+    // Paths are relative to the build root. Use the empty string (`""`) to refer to
+    // the build root itself.
+    // A directory listed here means that all files within, recursively, are included.
+    .paths = .{
+        "build.zig",
+        "build.zig.zon",
+        "src",
+        // For example...
+        //"LICENSE",
+        //"README.md",
+    },
+}
diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..4e3d1e6
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1,8 @@
+mkwp - password generator
+=========================
+
+mkpw is a password generator using Zig's crypto/random and displays it's entropy.
+
+usage
+-----
+mkwp -l <length>
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..a273e04
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,105 @@
+/// mkpw
+/// Author: Sebastian Michalk <sebastian.michalk@pm.me>
+/// ISC License
+/// (c) 2026
+
+const std = @import("std");
+const os = @import("os");
+const exit = std.process.exit;
+const srand = std.crypto.random;
+
+pub var CHARSET = blk: {
+    var chars: [95]u8 = undefined;
+    for (0..95) |i| {
+        chars[i] = 32 + @as(u8, @intCast(i));
+    }
+    break :blk chars;
+};
+
+pub fn die(comptime fmt: []const u8, args: anytype) noreturn {
+    const stderr = std.fs.File.stderr().deprecatedWriter();
+    stderr.print(fmt, args) catch {};
+    exit(1);
+}
+
+pub fn calc_entropy(s: usize) f64 {
+    const n: f64 = 95.0;
+    const l: f64 = @floatFromInt(s);
+    return l * std.math.log2(n);
+}
+
+pub fn mkpw(pwlen: u32, buffer: []u8) ![]u8 {
+    if (pwlen == 0 or pwlen > 256) {
+        die("mkpw: error: length must be greater than 0 and less or equal 256.\n", .{});
+    }
+
+    const target_len = @as(usize, @intCast(pwlen));
+    if (target_len > buffer.len) {
+        die("memcpy\n", .{});
+    }
+
+    var i: usize = 0;
+    while (i < target_len) : (i += 1) {
+        const idx = srand.uintLessThan(usize, CHARSET.len);
+        buffer[i] = CHARSET[idx];
+    }
+
+    return buffer[0..target_len];
+}
+
+pub fn printpw(buf: []u8) !void {
+ 
+    // Zig 0.15.1 // TODO different way to print to stdout
+    const stdout = std.fs.File.stdout().deprecatedWriter();
+    const entropy: f64 = calc_entropy(buf.len);
+    try stdout.print("{s}\n", .{buf});
+    try stdout.print("mkpw: entropy: \x1b[4m{d:.1} bits\x1b[0m.\n", .{entropy});
+    std.crypto.secureZero(u8, buf);
+    exit(0);
+}
+
+pub fn main() !void {
+    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+    defer _ = gpa.deinit();
+    const alloc = gpa.allocator();
+
+    const args = try std.process.argsAlloc(alloc);
+    defer std.process.argsFree(alloc, args);
+
+    var i: usize = 1;
+    var pwlen: u32 = 10;
+    var pwbuf = [_]u8{0} ** 256;
+
+    if (args.len < 2) {
+        const pw = try mkpw(pwlen, &pwbuf);
+        try printpw(pw);
+    }
+
+    while (i < args.len) : (i += 1) {
+        const arg = args[i];
+        if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
+            die("mkpw: usage: -l or --length <uint>\n", .{});
+        }
+        if (std.mem.eql(u8, arg, "-l") or std.mem.eql(u8, arg, "--length")) {
+            i += 1;
+            if (i >= args.len) {
+                die("mkpw: error: length requires a value\n", .{});
+            }
+            pwlen = std.fmt.parseInt(u32, args[i], 10) catch |err| {
+                switch (err) {
+                    error.InvalidCharacter => {
+                        die("mkpw: error: '{s}' is not a valid int\n", .{args[i]});
+                    },
+                    error.Overflow => {
+                        die("mkpw: error: number too large or negative number\n", .{});
+                    },
+                }
+                exit(1);
+            };
+        } else {
+            die("mkpw: usage: -l or --length <uint>\n", .{});
+        }
+        const pw = try mkpw(pwlen, &pwbuf);
+        try printpw(pw);
+    }
+}
diff --git a/src/root.zig b/src/root.zig
new file mode 100644
index 0000000..27d2be8
--- /dev/null
+++ b/src/root.zig
@@ -0,0 +1,13 @@
+//! By convention, root.zig is the root source file when making a library. If
+//! you are making an executable, the convention is to delete this file and
+//! start with main.zig instead.
+const std = @import("std");
+const testing = std.testing;
+
+pub export fn add(a: i32, b: i32) i32 {
+    return a + b;
+}
+
+test "basic add functionality" {
+    try testing.expect(add(3, 7) == 10);
+}
diff --git a/zig-out/bin/mkpw b/zig-out/bin/mkpw
new file mode 100755
index 0000000..84fdd76
Binary files /dev/null and b/zig-out/bin/mkpw differ
diff --git a/zig-out/lib/libmkpw.a b/zig-out/lib/libmkpw.a
new file mode 100644
index 0000000..9d57082
Binary files /dev/null and b/zig-out/lib/libmkpw.a differ