Posts
Search
Contact
Cookies
About
RSS

Checking password hashes with glibc

Added 14 Oct 2022, 9:38 p.m. edited 18 Jun 2023, 1:12 a.m.

Like many people developing on the web, I've had cause to use password hashes as part of an authorisation system - usually with a language like PHP, however I'd never had the need to do similar in a lower level language like C. Fortunately you can make one way SHA-512 hashes using functions available as part of glibc.

I was pleasantly surprised to discover the quality of the glibc manual, which is rather better than the usual skimpy doxygen type generated document with little or no information on how the functions are intended to be used, and shock horror there is even example code! It makes a change not to have to read through minimally commented source code to support auto-generated API docs, just to figure out how you're supposed to use a library.

The code examples were necessarily brief, so before actually using it in anger, I decided to make my own example based on several of the code examples in the glibc manual.

I wanted to be sure I could generate hashes and match them appropriately but also wanted to be sure I would have no issue using the re-entrant version of the crypt function, as my use case will be in a multi threaded environment.

The API (or the entirety of it I need) consists of just one function but it is used in two different ways, the first parameter is the pass phrase to hash, and the second is the salt. The salt consists of a simple string, identifying which algorithm to use and also some random data to "stir thing up", the random data must be selected from an "alphabet" of 64 characters.

If the salt parameter is instead a hash then the result can be used to check against a previously stored reference hash, this enables the checking of password entered at a later date for verification purposes.

I've commented the example code, and it should be fairly easy to see what's going on, so here it is...

Enjoy!

// gcc test.c -o test -lcrypt

#include <stdio.h>
#include <unistd.h>     // getentropy

#define __USE_GNU       // crypt_r
#include <crypt.h>

#include <string.h>     // strncpy
#include <stdlib.h>     // calloc


// based on code in the glibc manual

char* dohash(char* phrase, void* data) 
{
    unsigned char ubytes[16];
    const char *const saltchars =
        "./0123456789ABCDEFGHIJKLMNOPQRST"
        "UVWXYZabcdefghijklmnopqrstuvwxyz";
    
    char salt[20] = {0};
    char *hash;

    // get random bytes
    if (getentropy (ubytes, sizeof ubytes)) {
        perror ("getentropy");
        return NULL;
    }

    // fill in the salt string. based on random bytes
    salt[0] = '$';
    salt[1] = '6'; // SHA-512 
    salt[2] = '$';
    for (int i = 0; i < 16; i++) {
        salt[3+i] = saltchars[ubytes[i] & 0x3f];
    }
    
    // actually do the hash
    hash = crypt_r(phrase , salt, data);
    if (!hash || hash[0] == '*') {
        perror ("crypt");
        return NULL;
    }  
    
    return hash;  
}

#define MAXLEN 106 // sha512 length 

int main(void)
{
    char hash[MAXLEN+1];
    char phrase[128];
    
    // must be initially cleared, doesn't need clearing for subsequent calls
    void* cryptData = calloc(1, sizeof(struct crypt_data));

    printf("Enter 1st passphrase: ");
    scanf("%127s", phrase);

    // the returned string from crypt will be overwritten next time its called
    char* tmp = dohash( phrase, cryptData );
    if (tmp) {
        strncpy(hash, tmp, MAXLEN);
    } else {
        return -1;
    }
    // hash would be the reference hash saved for example in a database
    printf("hash1: %s\n",hash);
    
    // we have the hash for reference phrase so reuse phrase for the second phrase 
    // this for example would be a password entered during a login attempt
    printf("Enter 2nd passphrase: ");
    scanf("%127s", phrase);
    tmp = dohash( phrase, cryptData );
    printf("hash2: %s\n",tmp);
    
    // check the entered phrase with the hash of the original phrase
    char* check = crypt_r(phrase, hash, cryptData);
    printf("check: %s\n",check);
    
    // do they match
    if (strcmp(check, hash)) {
        printf("phrases don't match\n");
    } else {
        printf("phrases match\n");
    }

    free(cryptData);

    return 0;
}