sábado, 21 de julio de 2007

Hash mediante C y OpenSSL


OpenSSL es una conocida librería que implementa ciertos algoritmos usados en criptografía. Aunque su nombre pareza indicar lo contrario, no es una librería usada únicamente para desarrollar canales SSL, pero esta es una de sus principales funciones. A continuación veremos como usar OpenSSL para implementar algoritmos de hash.

OpenSSL proporciona también un binario que permite usar criptografía desde la linea de comandos. Como el caso que nos ocupa son los algoritmos de hash, veamos primero como usar openssl para tal propósito.

Primero crearemos un fichero de ejemplo y calcularemos su hash con el algoritmo MD5:


$ echo test > test.txt
$ openssl md5 test.txt
MD5(test.txt)= d8e8fca2dc0f896fd7cb4cb0031ba249



Para usar cualquier otro algoritmo de hash (siempre que sea soportado por OpenSSL) usaremos el mismo método. Por ejemplo, para SHA1:


openssl sha1 test.txt
SHA1(test.txt)= 4e1243bd22c66e76c2ba9eddc1f91394e57f9f83



Para usar las funciones de hash de OpenSSL desde el lenguaje C, necesitaremos incluir la cabecera "openssl/evp.h" en nuestras fuentes y llamar a la función OpenSSL_add_all_digests(). A continuación usaremos EVP_get_digestbyname() para obtener el algoritmo de hash que queremos utilizar: md5, sha1, etc. Finalmente, las funciones EVP_DigestInit(), EVP_DigestUpdate() y EVP_DigestFinal() nos permitirán calcular el hash.

Ejemplo:


unsigned char *simple_digest(char *alg, char *buffer, 
unsigned int len, int *olen)
{
EVP_MD *m;
EVP_MD_CTX ctx;
unsigned char *ret;

OpenSSL_add_all_digests ();
if (!(m = (EVP_MD*) EVP_get_digestbyname(alg)))
return NULL;

if (!(ret = (unsigned char *) malloc(EVP_MAX_MD_SIZE)))
return NULL;

EVP_DigestInit(&ctx, m);
EVP_DigestUpdate(&ctx, buffer, len);
EVP_DigestFinal(&ctx, ret, olen);

return ret;
}




La función simple_digest() permite calcular el hash de una cadena pasada como parámetro "buffer" mediante el algoritmo "alg". Pero en el ejemplo anterior en el que usábamos la herramienta openssl, calculábamos el hash de un fichero entero. Para hacerlo, necesitaremos abrir el fichero, ponerlo en un buffer en memoria y llamar a simple_digest(). A continuación pongo un ejemplo completo:



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>

#define READSIZE 1024

unsigned char *
simple_digest(char *alg, unsigned char *buffer, size_t len, size_t * olen)
{
EVP_MD *m;
EVP_MD_CTX ctx;
unsigned char *ret;

OpenSSL_add_all_digests();
if(!(m = (EVP_MD *) EVP_get_digestbyname(alg)))
return NULL;

if(!(ret = (unsigned char *)malloc(EVP_MAX_MD_SIZE)))
return NULL;

EVP_DigestInit(&ctx, m);
EVP_DigestUpdate(&ctx, buffer, len);
EVP_DigestFinal(&ctx, ret, olen);
return ret;
}

void
print_hex(unsigned char *bs, size_t n)
{
int i;

for(i = 0; i < n; i++)
printf("%02x", bs[i]);
}

unsigned char *
read_file(FILE * f, size_t *len)
{
unsigned char *buffer = NULL, *last = NULL;
unsigned char inbuffer[READSIZE];
int tot, n;

tot = 0;
for(;;)
{
n = fread(inbuffer, sizeof(unsigned char), READSIZE, f);
if(n > 0)
{
last = buffer;
buffer = (unsigned char *)malloc(tot + n);
memcpy(buffer, last, tot);
memcpy(&buffer[tot], inbuffer, n);

if(last)
free(last);
tot += n;

if(feof(f) > 0)
{
*len = tot;
return buffer;
}
}
else
{
if(buffer)
free(buffer);
break;
}
}
return NULL;
}

unsigned char *
process_file(char *algorithm, FILE * f, size_t *olen)
{
size_t filelen;
unsigned char *ret, *contents = read_file(f, &filelen);

if(!contents)
return NULL;

ret = simple_digest(algorithm, contents, filelen, olen);
free(contents);
return ret;
}

int
process_stdin(char *algorithm)
{
size_t olen;
unsigned char *digest = process_file(algorithm, stdin, &olen);

if(!digest)
return 0;

print_hex(digest, olen);
printf("\n");
free(digest);
return 1;
}

void
usage(char *progname)
{

printf("Usage: %s [ md2 | md4 | md5 | sha | sha1 ] [ Files ] \n\n",
progname);
exit(0);
}

int
main(int argc, char *argv[])
{
int i;

/* Sin parametros */
if(argc < 2)
usage(argv[0]);

/* Verificamos los algoritmos */
if((strcmp(argv[1], "md2") != 0) &&
(strcmp(argv[1], "md4") != 0) &&
(strcmp(argv[1], "md5") != 0) &&
(strcmp(argv[1], "sha") != 0) && (strcmp(argv[1], "sha1") != 0))
usage(argv[0]);


/* Un solo parametro, entrada estandar */
if(argc == 2)
{
if(!process_stdin(argv[1]))
perror("stdin");
}

/* Lee y procesa todos los parametros recibidos */
else
{
for(i = 2; i < argc; i++)
{

FILE *file = fopen(argv[i], "rb");
size_t olen;
unsigned char *digest;

if(!file)
{
perror(argv[i]);
return -1;
}

digest = process_file(argv[1], file, &olen);

if(!digest)
{
fclose(file);
return -1;
}

fclose(file);
print_hex(digest, olen);
printf("  %s\n", argv[i]);
free(digest);
}
}
return 1;
}




Finalmente, podemos verificar el correcto funcionamiento de nuestra herramienta comparandola con el binario openssl.


$ gcc -lssl myhash.c -o myhash

$ ./myhash md5 test.txt
d8e8fca2dc0f896fd7cb4cb0031ba249  test.txt

$ openssl md5 test.txt
MD5(test.txt)= d8e8fca2dc0f896fd7cb4cb0031ba249

$ ./myhash sha1 test.txt
4e1243bd22c66e76c2ba9eddc1f91394e57f9f83  test.txt

$ openssl sha1 test.txt
SHA1(test.txt)= 4e1243bd22c66e76c2ba9eddc1f91394e57f9f83



Referencias:
- Network security with OpenSSL, Ed O'Reilly. Viega, Messier, Chandra.

3 comentarios:

Unknown dijo...

Hola, Gracias por la información :D me sirvió de mucho.

Mario dijo...

Muy útil. Muchas gracias!

Mario dijo...

Muy util. Muchas gracias