1 The Problem
We want a tool that creates a strong password: a random mix of letters, numbers, and symbols, of a length the user chooses. It teaches building a string from random choices — and an important lesson about which randomness is safe for security.
2 How to Think About It
The whole program is two small, pure functions: build the character pool from what was selected, then pick that many random characters from it. The interesting part is what happens when nothing is selected.
length random characters from the pool using a cryptographically secure source.
3 The Build — explained part by part
build_pool assembles the available characters from bit flags; generate_password uses it and arc4random_uniform — a real, cryptographically secure random function available directly in modern glibc, not rand().
#ifndef PASSWORD_GENERATOR_H
#define PASSWORD_GENERATOR_H
#define CS_LOWER 1
#define CS_UPPER 2
#define CS_DIGITS 4
#define CS_SYMBOLS 8
/* Builds the character pool for the given mask of CS_* flags into `pool_out`
* (capacity `pool_cap`). Returns the pool's length, or 0 if `mask` selects no
* character sets at all — the caller must handle that case. */
int build_pool(int mask, char *pool_out, int pool_cap);
/* Generates a password of `length` characters from `mask`'s pool into `out`
* (capacity `out_cap`, must be > length). Returns 1 on success, 0 if the
* pool was empty (mask selected nothing). */
int generate_password(int length, int mask, char *out, int out_cap);
#endif
#include "PasswordGenerator.h"
#include <string.h>
#include <stdlib.h>
/* Appends `set` to `pool` if `mask` selects it, staying within `pool_cap`. */
static void append_if_selected(char *pool, int pool_cap, int mask, int flag, const char *set) {
if (mask & flag) {
size_t room = (size_t)(pool_cap - (int)strlen(pool) - 1);
strncat(pool, set, room);
}
}
int build_pool(int mask, char *pool_out, int pool_cap) {
static const char *LOWER = "abcdefghijklmnopqrstuvwxyz";
static const char *UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static const char *DIGITS = "0123456789";
static const char *SYMBOLS = "!@#$%^&*()-_=+";
pool_out[0] = '\0';
append_if_selected(pool_out, pool_cap, mask, CS_LOWER, LOWER);
append_if_selected(pool_out, pool_cap, mask, CS_UPPER, UPPER);
append_if_selected(pool_out, pool_cap, mask, CS_DIGITS, DIGITS);
append_if_selected(pool_out, pool_cap, mask, CS_SYMBOLS, SYMBOLS);
return (int)strlen(pool_out); /* 0 if mask selected no character set at all */
}
int generate_password(int length, int mask, char *out, int out_cap) {
char pool[128];
int pool_len = build_pool(mask, pool, sizeof(pool));
if (pool_len == 0 || length <= 0 || length >= out_cap) return 0;
for (int i = 0; i < length; i++) {
out[i] = pool[arc4random_uniform((unsigned int)pool_len)];
}
out[length] = '\0';
return 1;
}
#include "PasswordGenerator.h"
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
int length = argc > 1 ? atoi(argv[1]) : 16;
int mask = CS_LOWER | CS_UPPER | CS_DIGITS | CS_SYMBOLS;
char out[256];
if (!generate_password(length, mask, out, sizeof(out))) {
fprintf(stderr, "Could not generate a password (empty pool or bad length).\n");
return 1;
}
printf("%s\n", out);
return 0;
}
|) is the classic C idiom for a set of independent on/off options packed into one integer, covered in the course’s Bitwise Operations lesson.arc4random_uniform, not rand() —
rand() is not cryptographically secure and is explicitly the wrong tool here; modern glibc (2.36+) ships arc4random_buf and arc4random_uniform directly, seeded from the OS’s own secure entropy source, with no extra library needed on this system. arc4random_uniform also avoids the subtle modulo-bias bug that rand() % pool_len would introduce.build_pool returns 0 for an empty selection — this is deliberate, and
generate_password checks for it explicitly and fails rather than silently returning an empty or garbage string. This is the same class of bug the Rust and Java versions of this project caught for real during testing: an empty character-set selection must be treated as a real error.
rand() % pool_len for the random index — not cryptographically secure, and introduces a slight statistical bias toward lower indices when pool_len doesn’t evenly divide RAND_MAX.arc4random_uniform(pool_len) instead, which is both secure and bias-free.build_pool’s return value for 0 before generating — would read from an empty string and produce garbage or a crash.generate_password checks pool_len == 0 explicitly and returns failure.generate_password explicitly sets out[length] = '\0' after filling every character.4 Test & Prove Each Part
C has no built-in test framework and this sandbox can't reach a package registry for one, so these tests use plain assert() calls. Since real randomness can't be asserted against a fixed expected value, the test build swaps in a small seedable stand-in for arc4random_uniform (see the #ifdef TESTING block in the full source above) so results are deterministic — the real, shipped program always uses the genuine secure function.
#define TESTING
#include "PasswordGenerator.h"
#include "PasswordGenerator.c"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#define RUN(name) do { name(); printf("PASS: %s\n", #name); } while (0)
static void build_pool_combines_selected_character_sets(void) {
char pool[128];
int len = build_pool(CS_LOWER | CS_DIGITS, pool, sizeof(pool));
assert(len == 26 + 10);
assert(strchr(pool, 'a') != NULL);
assert(strchr(pool, '5') != NULL);
assert(strchr(pool, 'A') == NULL);
}
static void build_pool_with_no_sets_selected_returns_zero(void) {
char pool[128];
int len = build_pool(0, pool, sizeof(pool));
assert(len == 0);
}
static void generate_password_produces_the_requested_length(void) {
char out[64];
int ok = generate_password(12, CS_LOWER | CS_UPPER, out, sizeof(out));
assert(ok == 1);
assert(strlen(out) == 12);
}
static void generate_password_fails_on_an_empty_pool(void) {
/* The same class of bug Rust's and Java's password-generator projects
* caught for real during testing: an empty selection must fail loudly,
* not silently hand back an empty or garbage string. */
char out[64];
int ok = generate_password(12, 0, out, sizeof(out));
assert(ok == 0);
}
static void every_character_comes_from_the_selected_pool(void) {
char out[128];
int mask = CS_DIGITS;
generate_password(50, mask, out, sizeof(out));
for (int i = 0; out[i]; i++) assert(out[i] >= '0' && out[i] <= '9');
}
int main(void) {
RUN(build_pool_combines_selected_character_sets);
RUN(build_pool_with_no_sets_selected_returns_zero);
RUN(generate_password_produces_the_requested_length);
RUN(generate_password_fails_on_an_empty_pool);
RUN(every_character_comes_from_the_selected_pool);
printf("All tests passed.\n");
return 0;
}
Compile and run with gcc -DTESTING -o test_run test_PasswordGenerator.c && ./test_run. Note the test file #includes PasswordGenerator.c directly (rather than compiling them as two translation units) specifically so the #ifdef TESTING swap applies; the real program is compiled without -DTESTING, which is what makes it use the genuine secure random function.
5 The Interface
Even a tiny program has an interface. Here is its contract, documented plainly.
What it expects
./genpw 20What it returns
UJ-AW(WGn!eZ(GkJN*Kp6 Run It & Automate It
Save the code as PasswordGenerator.h / PasswordGenerator.c / main.c and compile it with gcc — that turns your source directly into a native executable for your machine. No separate runtime needed: the compiled binary runs on its own.
gcc -o genpw main.c PasswordGenerator.c && ./genpw 20Every run genuinely differs — there's no seed to reset, since it draws from the OS's real entropy source.
A CI tool like Jenkins runs the same compile-then-test-then-check-for-leaks steps automatically whenever the code changes — every line below has a plain explanation.
$ ./genpw 20
UJ-AW(WGn!eZ(GkJN*Kp
$ ./genpw 12
&690cF%SbcX#main.c's default selects all four character sets, so seeing this message there means the requested length was 0, negative, or too large for the output buffer.ldd --version; on an older system, linking libbsd and including <bsd/stdlib.h> provides the same function.arc4random_uniform fresh for every character in the loop, not caching one random value and reusing it.// Jenkinsfile — compiles, tests, and checks for leaks on every change.
pipeline {
agent any
stages {
stage('Get the code') {
// download the latest code
steps { checkout scm }
}
stage('Compile') {
steps {
// confirm a compiler is installed
sh 'gcc --version'
// compile with strict warnings on
sh 'gcc -std=c17 -Wall -Wextra -o app *.c'
}
}
stage('Run the tests') {
steps {
// prints PASS/FAIL, exits non-zero on failure
sh './app'
}
}
stage('Check for memory leaks') {
steps {
// fails the build on any leak or invalid access
sh 'valgrind --error-exitcode=1 --leak-check=full ./app'
}
}
}
post {
success { echo 'All tests passed, no leaks found.' }
failure { echo 'A test or Valgrind check failed — see above.' }
}
}
--count N option to print several passwords at once.arc4random_uniform beats rand() for anything security-related, and is available with zero extra dependencies on a modern glibc; and treating an empty selection as a real, explicit failure rather than undefined behavior.