Multi-threaded Password Hasher
A long time ago I read about a programming challenge that involved writing a multi-threaded password hasher, but I never got around to it and had almost forgotten about it. That is, until now, so I present here my effort after combining POSIX Message Queues and POSIX Threads. This particular implementation uses MD5 but that can be easily changed by editing the pwgen_hash() function.
Here's the code, link with -lpthread -lrt -lcrypto:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <mqueue.h>
#include <errno.h>
#include <unistd.h>
#include <openssl/md5.h>
#define QUEUE_NAME "/pwgen_print"
#define QUEUE_MAX_SIZE 10
#define QUEUE_MSG_SIZE 128
#define PW_LEN_MAX 32
static char set[256] = {0};
static int set_size = 1;
static int min_len = 1;
static int max_len = 1;
typedef struct pwgen_arg_s {
int my_index;
int pw_len;
} pwgen_arg_t;
static inline void pwgen_hash(char *pw, mqd_t mq)
{
char buffer[QUEUE_MSG_SIZE];
unsigned char md[MD5_DIGEST_LENGTH];
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, pw, strlen(pw));
MD5_Final(md, &ctx);
snprintf(buffer, QUEUE_MSG_SIZE,
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x %s",
md[0], md[1], md[2], md[3], md[4], md[5], md[6], md[7],
md[8], md[9], md[10], md[11], md[12], md[13], md[14], md[15],
pw);
mq_send(mq, buffer, strlen(buffer), 0);
}
static void pwgen_traverse(uint8_t pw[], int pw_len,
int index, int lowest_index, mqd_t mq)
{
if (index == lowest_index) {
do {
/* Hashing is done here */
char buffer[pw_len + 1];
for (index = 0; index < pw_len; index++) {
buffer[index] = set[pw[index]];
}
buffer[pw_len] = '\0';
pwgen_hash(buffer, mq);
pw[lowest_index]++;
} while (pw[lowest_index] < set_size);
} else if (index > lowest_index) {
do {
pwgen_traverse(pw, pw_len, index - 1, lowest_index, mq);
pw[index - 1] = 0;
pw[index]++;
} while (pw[index] < set_size);
}
}
static void *pwgen_thread(void *arg)
{
uint8_t pw[((pwgen_arg_t *)arg)->pw_len];
mqd_t mq;
memset(pw, 0, ((pwgen_arg_t *)arg)->pw_len);
pw[0] = ((pwgen_arg_t *)arg)->my_index;
mq = mq_open(QUEUE_NAME, O_WRONLY);
if (mq == (mqd_t)-1) {
fprintf(stderr, "%s: mq_open(): %s\n", __func__, strerror(errno));
return ((void *)-1);
}
pwgen_traverse(pw,
((pwgen_arg_t *)arg)->pw_len,
((pwgen_arg_t *)arg)->pw_len - 1,
1, mq);
mq_close(mq);
return ((void *)0);
}
static void *pwgen_print_thread(void *arg)
{
(void)arg;
mqd_t mq;
ssize_t read;
struct mq_attr attr;
char buffer[QUEUE_MSG_SIZE];
int i;
memset(&attr, 0, sizeof(struct mq_attr));
attr.mq_maxmsg = QUEUE_MAX_SIZE;
attr.mq_msgsize = QUEUE_MSG_SIZE;
mq = mq_open(QUEUE_NAME, O_CREAT | O_RDONLY, 0644, &attr);
if (mq == (mqd_t)-1) {
fprintf(stderr, "%s: mq_open(): %s\n", __func__, strerror(errno));
return ((void *)-1);
}
while (1) {
read = mq_receive(mq, buffer, QUEUE_MSG_SIZE, NULL);
if (read > 0) {
for (i = 0; i < read; i++) {
fputc(buffer[i], stdout);
}
fputc('\n', stdout);
} else {
if (read == -1) {
fprintf(stderr, "%s: mq_receive(): %s\n", __func__, strerror(errno));
}
break;
}
}
mq_close(mq);
mq_unlink(QUEUE_NAME);
return ((void *)0);
}
static void pwgen_start(void)
{
pthread_t pwgen_tid[set_size];
pwgen_arg_t arg[set_size];
int len, index;
for (len = min_len; len <= max_len; len++) {
/* Start all threads. */
for (index = 0; index < set_size; index++) {
arg[index].my_index = index;
arg[index].pw_len = len;
pthread_create(&pwgen_tid[index], NULL, pwgen_thread, &arg[index]);
}
/* Wait for all threads. */
for (index = 0; index < set_size; index++) {
pthread_join(pwgen_tid[index], NULL);
}
}
}
static int pwgen(void)
{
pthread_t print_tid;
mqd_t mq;
/* Create print thread and wait for it to create its message queue. */
pthread_create(&print_tid, NULL, pwgen_print_thread, NULL);
while (1) {
mq = mq_open(QUEUE_NAME, O_WRONLY);
if (mq == (mqd_t)-1) {
if (errno == ENOENT) {
usleep(1000);
} else {
fprintf(stderr, "%s: mq_open(): %s\n", __func__, strerror(errno));
return -1;
}
} else {
break;
}
}
/* Start password generating threads. */
pwgen_start();
/* Send dummy message of 0 bytes to terminate print thread. */
char dummy[1];
mq_send(mq, dummy, 0, 0);
pthread_join(print_tid, NULL);
return 0;
}
static void display_help(char *progname)
{
fprintf(stderr, "Usage: %s <options>\n", progname);
fprintf(stderr, "Options:\n"
" -h Display this help and exit.\n"
" -s SET Character SET to use. (E.g. \"abcde\")\n"
" -n MIN Minimum length of generated password. (2 - %d)\n"
" -x MAX Maximum length of generated password. (2 - %d)\n"
"\n", PW_LEN_MAX, PW_LEN_MAX);
}
int main(int argc, char *argv[])
{
int c;
while ((c = getopt(argc, argv, "hs:n:x:")) != -1) {
switch (c) {
case 'h':
display_help(argv[0]);
break;
case 's':
strncpy(set, optarg, sizeof(set));
set_size = strlen(set);
break;
case 'n':
min_len = atoi(optarg);
break;
case 'x':
max_len = atoi(optarg);
break;
case '?':
default:
display_help(argv[0]);
return EXIT_FAILURE;
}
}
if (set_size <= 1) {
fprintf(stderr, "Please specify a character set!\n");
display_help(argv[0]);
return EXIT_FAILURE;
}
if (min_len < 2 || min_len > PW_LEN_MAX) {
fprintf(stderr, "Please specify minimum length within range!\n");
display_help(argv[0]);
return EXIT_FAILURE;
}
if (max_len < 2 || max_len > PW_LEN_MAX) {
fprintf(stderr, "Please specify minimum length within range!\n");
display_help(argv[0]);
return EXIT_FAILURE;
}
pwgen();
return EXIT_SUCCESS;
}