Kjetil's Information Center: A Blog About My Projects

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;
}
          


Topic: Scripts and Code, by Kjetil @ 23/06-2023, Article Link