Writing a Local Password Generator in Zig and Storing it in a Config File
Tags: zig, programming
Published: 2023-06-21 03:22:51

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

> $ 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: user@example.com
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](https://github.com/arpitsr/zpassword)

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.

Originally posted on https://sumofbytes.com/writing-a-local-password-generator-in-zig-and-storing-it-in-a-config-file-2