Making a somewhat functional password manager in C
C is the greatest programming language of all time. Nothing beats it. Nothing will ever beat it. It’s just so, so powerful. It can do anything
Just one small problem: How does one program C? I of course know this already, but as a small excercise I think it can be useful to write a small “application” (glorified CLI) in C. At the time of writing said application, I had also not programmed in a while.
So what project would we choose? Well, the little people on the internet gave me a few suggestions:
- A TODO app
- A HTTP server
- A calculator
- A password manager
As I had not dabbled in encryption a lot before, I decided on writing a password manager, and this semi-tutorial is a partial guide on how to replicate locksmith, my implementation of a password manager for UNIX-like systems. You are expected to have basic knowledge of C, but you don’t need to be a 10x engineer by any means.
1. Encryption
So where do we begin? Well I would say we should start with a cryptography interface for easy use in the more high-level parts of our project. Now I need you to listen to me here: do not write your own encryption library. Use a functioning encryption library instead. I have chosen libsodium for this tutorial, so go ahead and install that.
Oh, and before I forget! All .c and .h files should probably be placed in a directory
called src/ or something. That’s what I did, at least.
Now, I gotta admit: I “borrowed” most of the cryptography code from
pass. So there isn’t much of an explanation here.
But you can go ahead and create your cryptography files: crypto.c
and crypto.h. Then we create a simple cryptography interface, starting with crypto.h:
#ifndef CRYPTO_H
#define CRYPTO_H
#include <sodium.h>
#define MAX_STRING_LEN 128 // Maximum string length: used all across the program.
int encrypt(const char *fname, const char *password, const unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]);
unsigned char *decrypt(const char *source_file, const unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]);
int create_key(const char *key_file);
#endifWe create a macro called MAX_STRING_LEN to hold a general maximum string length to be used across the program.
Set to 128 in my implementation. Then we define our core functions, which will be explained further when we
actually implement them. Lets start with encrypt()
#include <stdio.h>
#include <string.h>
#include <sodium.h>
#include "crypto.h"
int encrypt(const char *fname, const char *password, const unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) {
unsigned char buf_out[MAX_STRING_LEN + crypto_secretstream_xchacha20poly1305_ABYTES];
unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
crypto_secretstream_xchacha20poly1305_state st;
unsigned long long out_len;
unsigned char tag;
FILE *fptr = fopen(fname, "wb");
crypto_secretstream_xchacha20poly1305_init_push(&st, header, key);
fwrite(header, 1, sizeof header, fptr);
size_t pass_len = strlen(password);
do {
tag = EOF ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0;
crypto_secretstream_xchacha20poly1305_push(
&st, buf_out, &out_len, (unsigned char *)password, pass_len, NULL, 0, tag);
fwrite(buf_out, 1, (size_t)out_len, fptr);
} while (!EOF);
fclose(fptr);
return 0;
}The code looks pretty complicated, probably due to the long function names, but in reality it is quite simple.
- We define some needed variables (
buf_out,headerandst) to be used for our cryptography magic later. - We open the file to be encrypted and we initialize our cryptography magic using that function with the
absurdly long name (
crypto_secretstream_xchacha20poly1305_init_push) and then we write the header to the file. - We get the length of the password, and then proceed to do our main cryptography magic inside a do/while loop.
Now, lets move on to decryption:
unsigned char *decrypt(const char *source_file, const unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) {
unsigned char buf_in[MAX_STRING_LEN + crypto_secretstream_xchacha20poly1305_ABYTES];
static unsigned char buf_out[MAX_STRING_LEN];
unsigned char header[crypto_secretstream_xchacha20poly1305_HEADERBYTES];
crypto_secretstream_xchacha20poly1305_state st;
unsigned long long out_len;
int eof;
unsigned char tag;
FILE *fp_s = fopen(source_file, "rb");
fread(header, 1, sizeof(header), fp_s);
if (crypto_secretstream_xchacha20poly1305_init_pull(&st, header, key) != 0) {
return NULL;
}
do {
size_t rlen = fread(buf_in, 1, sizeof buf_in, fp_s);
eof = feof(fp_s);
if (crypto_secretstream_xchacha20poly1305_pull(&st, buf_out, &out_len, &tag, buf_in, rlen, NULL, 0) != 0) {
return NULL;
}
if (tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && ! eof) {
return NULL;
}
} while (!eof);
fclose(fp_s);
return buf_out;
}Again, seems pretty complicated, but is quite simple:
- Again, we define the variables to be used for our cryptography magic.
- Then, we open the file to be decrypted and read it using
fread. - Then, we loop trough the file, we attempt to pull decrypted info and if it succeeds, we return the info.
Otherwise, we return
NULL.
Now, we need to add a function to write our encryption key to a file (we wanna decrypt our passwords with it, of course). It proves to be quite simple, naturally:
int create_key(const char *key_file) {
unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES];
crypto_secretstream_xchacha20poly1305_keygen(key);
FILE *f = fopen(key_file, "w");
if (!f) {
return 1;
}
fwrite(key, 1, crypto_secretstream_xchacha20poly1305_KEYBYTES, f);
fclose(f);
return 0;
}This code is pretty self explanatory.
2. The Actual Password-Handling
Now, lets use our low-level interface to create a higher-level interface for handling passwords.
The plan is to have a directory, ~/.locksmith, to save all of our information to. More
specifically: ~/.locksmith/locksmith.key should hold the key and ~/.locksmith/passwords/
should hold the passwords.
Well, naturally, our first step here is to be able to access the home directory, or ~, since
C doesn’t let us access it by default. So let’s create a commons/utils library! get your
commons.c and commons.h files ready! Go ahead and just create some (basically) boilerplate
for our header file:
#ifndef COMMONS_H
#define COMMONS_H
char *get_home_dir();
#endifThen, we are going to get our home dir, right? Yeah, but only for UNIX-likes, since I couldn’t be bothered to find the Windows implementation of this function. This TECHNICALLY makes locksmith system-dependent. It’s really simple though:
#include <unistd.h>
#include <pwd.h>
#include "commons.h"
char *get_home_dir() {
return getpwuid(getuid())->pw_dir;
}And finally, we can get to implementing the interface, which will be implemented in
password.c/password.h. Lets begin with the basic “locksmith file system”, which is
basically a set of macros and a few functions that will be explained later. But for now,
this’ll do:
#ifndef PASSWORD_H
#define PASSWORD_H
#include <string.h> // strcat
#include "commons.h"
#define locksmith_dir strcat(get_home_dir(), "/.locksmith/")
#define locksmith_passw_dir strcat(locksmith_dir, "passwords/")
#define locksmith_key_file strcat(locksmith_dir, "key.txt")
#define get_locksmith_passw_dir_filepath(name) strcat(locksmith_passw_dir, strcat(name, ".txt"))
#endifIt’s the classic macro expands to another macro bullshittery and it looks AWFUL, but it works. The
get_locksmith_passw_dir_filepath macro appends name to the directory name, e.g
get_locksmith_passw_dir_filepath("hey.txt") becomes ~/.locksmith/passwords/hey.txt
Now, we can move on to our password handling functions:
#ifndef PASSWORD_H
#define PASSWORD_H
#include <string.h> // strcat
#include <sodium.h>
#include "commons.h"
// ...
int create_password(char name[], char password[], unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]);
char *get_password(char name[], unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]);
int delete_password(char name[]);
int list_passwords();
#endifThis is basically all we need for now, I suppose. Let’s begin with implementing the create_password() function
then. Open password.c:
#include <stdio.h>
#include "crypto.h"
int create_password(char name[], char password[], unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) {
char *fname = get_locksmith_passw_dir_filepath(name);
FILE *fptr;
fptr = fopen(fname, "w");
if (fptr == NULL) {
printf("Couldn't create password. Failed to write to file.");
return 200;
}
fprintf(fptr, "%s", password);
fclose(fptr);
encrypt(fname, password, key);
return 0;
}I find this code pretty self explanatory. It writes the encrypted password to ~/.locksmith/passwords/{name}
and exits. Simple as. In practice we got the most complicated stuff out of the way in the beginning.
get_password() is even easier:
char *get_password(char name[], unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) {
char *fname = get_locksmith_passw_dir_filepath(name);
unsigned char *buf = decrypt(fname, key);
encrypt(fname, buf, key);
return buf;
}Additionally, we need to be able to delete passwords, hence delete_password():
int delete_password(char name[]) {
char *fname = get_locksmith_passw_dir_filepath(name);
remove(fname);
return 0;
}And then one of the most important utilities, list_password():
// ...
#include <string.h>
#include <dirent.h>
// ...
int list_passwords() {
struct dirent *de;
DIR *dir = opendir(locksmith_passw_dir);
if (dir == NULL) {
printf("Couldn't list passwords.");
return 200;
}
while ((de = readdir(dir)) != NULL) {
char *pname = strtok(de->d_name, ".");
if (pname != NULL) printf("%s\n", pname);
}
closedir(dir);
return 0;
}This function is actually quite complicated. Firstly, we need to include string.h and
dirent.h. Additionally, we need to remove the file extension from the password file (because
the password name is just the password filename minus the file extension). But really it is quite
simple, aswell.
Oh, and one last thing! In order to actually decrypt the passwords, we need to get the key, not
just create it. So go ahead and add one last function to password.h:
#include <sodium.h>
// ...
int get_key(unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]);And password.c:
#include <sodium.h>
// ...
int get_key(unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES]) {
FILE *key_fd = fopen(locksmith_key_file, "r");
if (key_fd == NULL) {
exit(2);
}
fread(&key, crypto_secretstream_xchacha20poly1305_KEYBYTES, 1, key_fd);
return 0;
}3. The Locksmith Shell
Now, for the last part of our password manager, we’re gonna create a small shell to run our
password-handling commands in. So go ahead and create cli.h and cli.c. Firstly, we need
to create a “global key” (global var containing the contents of ~/.locksmith/locksmith.key)
to be passed to other functions:
static unsigned char key[crypto_secretstream_xchacha20poly1305_KEYBYTES];
// high skill code amiright
int cli_init() {
get_key(key);
return 0;
}Now, we need to basically create user-friendly wrappers around the password.h interface.
These are incredibly easy to implement, we just pass user input to the interface:
#include <stdio.h>
#include "cli.h"
#include "crypto.h"
#include "password.h"
// ...
int cmd_get_password() {
char pass_name[MAX_STRING_LEN];
printf("What password do you want to get?\n"
"List of passwords:\n");
list_passwords();
printf("> ");
scanf("%s", pass_name);
printf("Password: %s\n", get_password(pass_name, key));
return 0;
}
int cmd_delete_password() {
char pass_name[MAX_STRING_LEN];
printf("What password do you want to delete?\n"
"List of passwords:\n");
list_passwords();
printf("> ");
scanf("%s", pass_name);
delete_password(pass_name);
return 0;
}However, you might’ve noticed something: Where’s cmd_create_password()? Well, I want my password name
to have a certain syntax to it, more specifically {site name}:{user name}, which means I have to take
3 inputs: sitename, username, and password. However, in the end it is pretty simple aswell, I just felt
like it needed a mention:
#define format_password_filename(site, user) strcat(strcat(site, ":"), user)
int cmd_create_password() {
char site_name[MAX_STRING_LEN], user_name[MAX_STRING_LEN], password[MAX_STRING_LEN];
printf("Site:\n> ");
scanf("%s", site_name);
printf("Username:\n> ");
scanf("%s", user_name);
printf("Password:\n> ");
scanf("%s", password);
create_password(format_password_filename(site_name, user_name), password, key);
return 0;
}And then we of course need to do what any regular ol’ shell does: actually interpret commands.
Since I’m… Well, y’know: lazy, I’m just gonna read an int as a command and interpret it
using a plain switch statements. It’s as simple as:
#include <stdlib.h>
// ...
int cmd_interface(int *exit) {
printf("\nEnter a command:\n"
"[1]: Create password\n"
"[2]: Get password\n"
"[3]: Delete password\n"
"[4]: Exit program\n");
int command;
printf("> ");
scanf("%d", &command);
char site_name[MAX_STRING_LEN];
char user_name[MAX_STRING_LEN];
char password[MAX_STRING_LEN];
switch (command) {
case 1:
cmd_create_password();
break;
case 2:
cmd_get_password();
break;
case 3:
cmd_delete_password();
break;
case 4:
printf("Exiting...\n");
exit(0);
default:
printf("Invalid command! Exiting...\n");
exit(0);
}
return 0;
}Lastly, we expose the functions in cli.h:
#ifndef CLI_H
#define CLI_H
int cli_init();
int cmd_create_password();
int cmd_get_password();
int cmd_delete_password();
int cmd_interface();
#endif4. Putting It All Together
The very, very last thing we need to do is to strap all of this together trough a main.c file
(and then a Makefile) to make the program… Actually run. However, firstly, how do we initalize
the locksmith filesystem? Well, basically, we need to create all directories and keys, if they
don’t exist. This can easily be solved with a new commons.c function:
#include <sys/stat.h>
// ...
int directory_exists(char *dirname) {
struct stat st;
if (stat(dirname, &st) == 0) {
return 1;
}
return 0;
}
int mkdirifnotexist(char *dirname) {
if (directory_exists(dirname) == 0) {
mkdir(dirname, 0755);
}
return 0;
}Exposing the functions in commons.h:
// ...
int directory_exists(char *dirname);
int mkdirifnotexist(char *dirname);And now we can create main.c and a basic main() function:
#include <stdio.h>
#include "password.h"
#include "cli.h"
#include "crypto.h"
int main() {
mkdirifnotexist(locksmith_dir); // ~/.locksmith
mkdirifnotexist(locksmith_passw_dir); // ~/.locksmith/passwords
create_key(locksmith_key_file); // ~/.locksmith/locksmith.key
cli_init();
while (1) {
cmd_interface();
}
return 0;
}For extra personality, we can also add “Locksmith” in a random figlet font:
#include <stdio.h>
#include "password.h"
#include "cli.h"
#include "crypto.h"
#define locksmith_title \
"88 88 88 88 \n" \
"88 88 \"\" ,d 88 \n" \
"88 88 88 88 \n" \
"88 ,adPPYba, ,adPPYba, 88 ,d8 ,adPPYba, 88,dPYba,,adPYba, 88 MM88MMM 88,dPPYba, \n" \
"88 a8\" \"8a a8\" \"\" 88 ,a8\" I8[ \"\" 88P' \"88\" \"8a 88 88 88P' \"8a\n" \
"88 8b d8 8b 8888[ `\"Y8ba, 88 88 88 88 88 88 88\n" \
"88 \"8a, ,a8\" \"8a, ,aa 88`\"Yba, aa ]8I 88 88 88 88 88, 88 88\n" \
"88888888888 `\"YbbdP\"' \"Ybbd8\"' 88 `Y8a `\"YbbdP\"' 88 88 88 88 \"Y888 88 88\n\n" \
int main() {
mkdirifnotexist(locksmith_dir); // ~/.locksmith
mkdirifnotexist(locksmith_passw_dir); // ~/.locksmith/passwords
create_key(locksmith_key_file); // ~/.locksmith/locksmith.key
cli_init();
printf(locksmith_title);
while (1) {
cmd_interface();
}
return 0;
}And then, finally, a simple Makefile:
SOURCE_FILES = $(shell find src/ -name '*.c') $(shell find src/ -name '*.h')
TARGET = locksmith
CC = cc
CFLAGS = -lsodium
all: build
build: $(SOURCE_FILES)
$(CC) $(CFLAGS) -o $(TARGET) $(SOURCE_FILES)Built to be flawed
And that brings us to the end of this semi-tutorial. I have one thing to say before you go: this version of the software was built to be flawed. full-implementation locksmith is much more complete and much more memory-safe, but it isn’t flawed. And being flawed is good. Being flawed means there is so much more room for improvement. And there is some kind of beauty in that. Have fun.