DEV Community

Arpit Kumar
Arpit Kumar

Posted on

Writing a Local Password Generator in Zig and Storing it in a Config File

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
Enter fullscreen mode Exit fullscreen mode

The directory structure should look like

|---build.zig
|---src
|---|---main.zig
Enter fullscreen mode Exit fullscreen mode

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 {

}
Enter fullscreen mode Exit fullscreen mode

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 {

}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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});
Enter fullscreen mode Exit fullscreen mode

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..]);
Enter fullscreen mode Exit fullscreen mode

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..]);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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..]);
}
Enter fullscreen mode Exit fullscreen mode

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.

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

Top comments (0)