by Arpit Kumar
20 Jun, 2023
7 minute read
Writing a Local Password Generator in Zig and Storing it in a Config File

Experimenting zig with basic program to generate password


Every time I have to change the mandatory password for web apps I have to go to an online solution and then store that password somewhere in notes. (I know tools like 1Password exist but I haven’t used them till now). So I decided to write a cli tool which can generate a password for me and store it in a file on my disk.

I am currently learning the zig programming language and decided to use the same. The code for this would be simple and we will follow these steps -

  • Ask user for username/email
  • Ask user for domain for which to generate the password
  • Generate the random password
  • Store the username, domain and password combination in the file

We will also learn some of the implementation details of zig while building this small cli tool.

Prerequisites

Before we begin, make sure you have zig installed on your system. I have installed it with the help of asdf. I use asdf as it’s very convenient in maintaining various versions of tools I use.

If you want to use asdf, install it using this link - https://asdf-vm.com/guide/getting-started.html

Or you can follow the official zig guide for installation. https://ziglang.org/learn/getting-started/#installing-zig

On mac - brew install zig should do the trick.

Getting Started

To get started create a directory in your development folder - zpassword

    
        $ mkdir zpassword
        # Then cd to zpassword
        $ cd zpassword
        # Now let’s initialise a new project in ziglang
        $ zig init-exe
    

The directory structure should look like

    
        |---build.zig
        |---src
        |---|---main.zig
    

2 directories, 2 files

Let’s start changing the main.zig. Delete all the content of main.zig and leave this basic structure.

    
        const std = @import("std");

        pub fn main() !void {

        }
    

I would like only these 62 characters be part of the password so let’s add charset for base62

    
        const std = @import("std");
        const allocator = std.heap.page_allocator;
        const RndGen = std.rand.DefaultPrng;

        const charset: [62]u8 = [_]u8{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

        pub fn main() !void {

        }
    

Let’s add code to take input from the user. Here I have restricted the username and domain input to be max size of 512.

    
        const stdin = std.io.getStdIn().reader();

        std.debug.print("Enter username: ", .{});
        var usernameResult = try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 512);
        var username = usernameResult.?;
        defer allocator.free(username);

        // take domain as input
        std.debug.print("Enter domain: ", .{});
        var domainResult = try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 512);
        var domain = domainResult.?;
        defer allocator.free(domain);
    

Most of the languages won’t require manual memory management but in zig we have to manage memory ourselves. It gives some freedom to use a particular type of memory allocator for a particular use case. Read more about memory allocator in my earlier post What’s a memory allocator anyway?

Similar to golang, zig also provides defer which is used to execute a statement while exiting the current block. This will make sure that allocated memory gets free when current block of code exits.

We will store the password in a file name zpass.conf inside the .conf folder under the home directory.

    
        var homeDir = std.os.getenv("HOME").?;
        var confDir = ".config";
        var confFile = "zpass.conf";


        // Allocate memory for the dynamic string
        const fullPath = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ homeDir, confDir, confFile });
        defer allocator.free(fullPath);
    

Now we will try to generate a random number using a seed value generated from slice of bytes

    
        // Generate random seed by picking randombyte from memory
        var seed: u64 = undefined;
        std.os.getrandom(std.mem.asBytes(&seed)) catch unreachable;
        var rnd = RndGen.init(seed);
    

std.mem.asBytes -  /// Given a pointer to a single item, returns a slice of the underlying bytes, preserving pointer attributes.

Let’s now generate a password of length 10.

    
        // Generate Password
        var password: [10]u8 = undefined;
        for (password) |*char| {
            var some_random_num = rnd.random().intRangeLessThan(usize, 0, charset.len);
            char.* = charset[some_random_num];
        }
        std.debug.print("Password: {s}\n", .{password});
    

Now we will open the file to write the password with username and domain. Here we are seeking to file to position 0 so that we can append the content at the beginning of the file.

    
        // Open file to write username, domain and password
        const openFlags = std.fs.File.OpenFlags{ .mode = std.fs.File.OpenMode.read_write };
        var file = try std.fs.openFileAbsolute(fullPath, openFlags);
        defer file.close();

        // seeking file position so that to append at beginning
        try file.seekTo(0);

        var fullText = try std.fmt.allocPrint(allocator, "Username: {s}, Domain: {s}, Password: {s}", .{ username, domain, password });
        defer allocator.free(fullText);
        _ = try file.writeAll(fullText[0..]);
    

Add a new line character to separate it from any earlier lines already stored in the file.

    
        // Adding new line char at end
        const newline = [_]u8{'\n'};
        _ = try file.write(newline[0..]);
    

Running the Program

To run the program, open a terminal, navigate to the directory containing the main.zig file, and execute the following command:

$ zig build run

You will be prompted to enter your username and domain. After entering the required information, the program will generate a password and store it, along with the username and domain, in the configuration file.

    
        Enter username: [email protected]
        Enter domain: gmail.com
        Password: 3zvSlZSUHL
    

Complete Code

    
        const std = @import("std");
        const allocator = std.heap.page_allocator;
        const RndGen = std.rand.DefaultPrng;

        const charset: [62]u8 = [_]u8{ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

        pub fn main() !void {
                const stdin = std.io.getStdIn().reader();

                std.debug.print("Enter username: ", .{});
                var usernameResult = try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 512);
                var username = usernameResult.?;
                defer allocator.free(username);

                // take domain as input
                std.debug.print("Enter domain: ", .{});
                var domainResult = try stdin.readUntilDelimiterOrEofAlloc(allocator, '\n', 512);
                var domain = domainResult.?;
                defer allocator.free(domain);

                var homeDir = std.os.getenv("HOME").?;
                var confDir = ".config";
                var confFile = "zpass.conf";

                // Allocate memory for the dynamic string
                const fullPath = try std.fmt.allocPrint(allocator, "{s}/{s}/{s}", .{ homeDir, confDir, confFile });
                defer allocator.free(fullPath);

                // Generate random seed by picking randombyte from memory
                var seed: u64 = undefined;
                std.os.getrandom(std.mem.asBytes(&seed)) catch unreachable;
                var rnd = RndGen.init(seed);

                // Generate Password
                var password: [10]u8 = undefined;
                for (password) |*char| {
                        var some_random_num = rnd.random().intRangeLessThan(usize, 0, charset.len);
                        char.* = charset[some_random_num];
                }
                std.debug.print("Password: {s}\n", .{password});

                // Open file to write username, domain and password
                const openFlags = std.fs.File.OpenFlags{ .mode = std.fs.File.OpenMode.read_write };
                var file = try std.fs.openFileAbsolute(fullPath, openFlags);
                defer file.close();

                // seeking file position so that to append at beginning
                try file.seekTo(0);

                var fullText = try std.fmt.allocPrint(allocator, "Username: {s}, Domain: {s}, Password: {s}", .{ username, domain, password });
                defer allocator.free(fullText);
                _ = try file.writeAll(fullText[0..]);

                // Adding new line char at end
                const newline = [_]u8{'\n'};
                _ = try file.write(newline[0..]);
        }
    

GitHub Repository

You can find the complete code for the password generator in Zig on the following GitHub repository: Zig Password Generator

Please note that storing passwords in plain text files is not secure and should not be used in real-world scenarios. This demonstration is solely for educational purposes.

Feel free to explore the code, make improvements, and experiment further with Zig’s capabilities.

Recent Posts

Understanding Asynchronous I/O in Linux - io_uring
Explore the evolution of I/O multiplexing from `select(2)` to `epoll(7)`, culminating in the advanced io_uring framework
Building a Rate Limiter or RPM based Throttler for your API/worker
Building a simple rate limiter / throttler based on GCRA algorithm and redis script
MicroVMs, Isolates, Wasm, gVisor: A New Era of Virtualization
Exploring the evolution and nuances of serverless architectures, focusing on the emergence of MicroVMs as a solution for isolation, security, and agility. We will discuss the differences between containers and MicroVMs, their use cases in serverless setups, and highlights notable MicroVM implementations by various companies. Focusing on FirecrackerVM, V8 isolates, wasmruntime and gVisor.

Get the "Sum of bytes" newsletter in your inbox
No spam. Just the interesting tech which makes scale possible.