Posted 2007-06-17T16:51:00+01:00 in web unix

Toying with SHA-1 HMAC digest and libgcrypt

Dear lazy web,

After reading up on OpenID recently, I thought it would be a nice experiment to see what it would take to write a CGI script in C that takes care of OpenID on my website. I haven't managed to write a complete set of CGI scripts for OpenID yet, and I'm not sure I will do that (given the feature creep in the recent spec proposals). But I did write this tiny driver program to get a hang of HMAC SHA-1 digests and the libgcrypt library. Although libgcrypt does all the hard work for me, I thought it might be useful to copy paste this snippet to the net for future reference.

The program takes the data from standard input and, combined with a key that was given as argument, calculates the HMAC-SHA1 digest for the data. HMAC is a message authentication mechanism where a unique hash is calculated on a piece of data. The key that is used in generating the hash is normally shared between the two parties exchanging the data. See for more info HMAC: Keyed-Hashing for Message Authentication, Test Cases for HMAC-MD5 and HMAC-SHA-1 and a discussion of HMAC on Wikipedia.

To prove that the program works, here is one of the test vectors from RFC 2202:

$ echo -n "what do ya want for nothing?" | ./sha1-hmac-digest Jefe
0xeffcdf6ae5eb2fa2d27416d5f184df9c259a7c79

sha1-hmac-digest.c

/* calculate a SHA-1 HMAC digest 
 * FIXME common encoding for the digest... (base64?) */
#include <stdlib.h>
#include <gcrypt.h>
#include <stdio.h>
#include <glib-2.0/glib.h>


/* write md value into message string: */
static void print_hash(const unsigned char *md_string)
{
    int len = gcry_md_get_algo_dlen(GCRY_MD_SHA1);
    char * mes = g_malloc(2 + (2 * len) + 1);
    char * mes_i = 0;
    int i = 0;

    mes[0] = '0';
    mes[1] = 'x';

    mes_i = mes + 2;
    for (i = 0; i < len; i++) {
        g_snprintf(mes_i, 3, "%x\n", md_string[i]);
        mes_i += 2;/* XXX assert pointer width equals char width */
    }
    mes[2 + (2 * len)] = 0;

    g_print(mes);
    g_print("\n");
    g_free(mes);
}


/* Read everything from standard input and calculate a SHA-1 HMAC from it.  */
static void calc_hmac(FILE *instream, const char *key)
{
    size_t num;
    gcry_error_t err = 0;
    gcry_md_hd_t digest = NULL;
    unsigned char *md_string = NULL;
    static const size_t bufsize = 4096;
    void *buffer = g_malloc(bufsize);

    err = gcry_md_open(
                &digest, GCRY_MD_SHA1,
                GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
    if (err) {
        g_printerr("%s\n", gcry_strerror(err));
        goto done;
    }
    err = gcry_md_setkey(digest, key, strlen(key));
    if (err) {
        g_printerr("%s\n", gcry_strerror(err));
        goto done;
    }
    while ((num = fread(buffer, 1, bufsize, instream)) != 0) {
        gcry_md_write(digest, buffer, num);
    }

    md_string = gcry_md_read(digest, 0);
    g_assert(md_string != NULL);
    print_hash(md_string);
    done:
    gcry_md_close(digest);
    g_free(buffer);
}


int main(int argc, const char **argv)
{
    if (argc > 1) {
        gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
        calc_hmac(stdin, argv[1]);
    }
    exit(0);
    return 0;
}

Compiling sha1-hmac-digest.c

For overcompleteness, here are the commands to compile this script (with gcrypt.h safely living in /usr/include and libgrypt.so in /usr/lib):

cc -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -Wall -g -c sha1-hmac-digest.c
cc -lglib-2.0 -lgcrypt -o sha1-hmac-digest sha1-hmac-digest.o

Obviously, there is no need to use glib. That's just the library I use for printing to screen. You might as well have used ordinary printf and friends from stdio.h.