anterior índice siguiente

Message Digests

Empecemos por anotar las funciones principales:

  • void EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type);
  • void EVP_DigestUpdate(EVP_MD_CTX *ctx, const void *d, unsigned int cnt);
  • void EVP_DigestFinal(EVP_MD_CTX *ctx, unsigned char *md, unsigned int *s);

Las funciones corresponden respectivamente a las rutinas de inicialización, de actualización y de finalización que nombramos en la introducción. Veamos un ejemplo:




Ejemplo 1. Calcula el message digest SHA-1 de una cadena de caracteres.

Varias cosas cabe destacar del ejemplo. Empecemos por describir cada una de las variables:

Fijémonos en la rutina de inicialización. EVP_DigestInit() toma como argumentos un contexto y un puntero a EVP_MD. Esta estructura de datos contiene información acerca de la función hash que va a ser utilizada. Para obtener el puntero correspondiente a la función hash que queremos utilizar disponemos de una serie de funciones que se muestran en la tabla 1.

Función Función Hash
EVP_MD *EVP_md_null(void); Esta función no devuelve nada
EVP_MD *EVP_md2(void); Usada para obtener el MD2
EVP_MD *EVP_md5(void); Usada para obtener el MD5
EVP_MD *EVP_sha(void); Usada para obtener el SHA
EVP_MD *EVP_sha1(void); Usada para obtener el SHA-1
EVP_MD *EVP_dss(void); Usada para obtener el SHA
EVP_MD *EVP_dss1(void); Usada para obtener el SHA-1
EVP_MD *EVP_mdc2(void); Usada para obtener el MDC2
EVP_MD *EVP_ripemd160(void); Usada para obtener el ripemd160
Tabla 1. Obtención de funciones hash.

No, no se trata de un error. EVP_sha() y EVP_dss obtienen ambos un SHA; EVP_sha1 y EVP_dss1 obtienen un SHA-1. ¿Por qué dos funciones diferentes para una sóla función hash? Desgraciadamente la implementación de estas funciones no es independiente de los algoritmos de firma. De esta forma EVP_sha y EVP_sha1 serán utilizados para obtener un SHA y SHA-1 que serán firmados con RSA, mientras que los otros dos van destinados al DSA. Esta dependencia se da en todas las funciones expuestas, de modo que EVP_md2(), EVP_md5(), EVP_sha(), EVP_sha1(), EVP_mdc2() y EVP_ripemd160() van atadas inexorablemente a RSA mientras que únicamente EVP_dss() y EVP_dss1() son utilizadas para el DSA.

Por otro lado, la función de actualización EVP_DigestUpdate() especifica el mensaje a cifrar en el contexto que estamos utilizando. Los argumentos que toma son el propio contexto, un puntero al buffer que vamos a hashear y el número de bytes del mensaje que se van a utilizar. En nuestro caso utilizamos todo el mensaje. Sin embargo, en determinadas ocasiones calcular la función hash de un mensaje de golpe puede que no sea lo más eficiente, sobre todo cuando el mensaje es muy largo. Es por ello por lo que EVP_DigestUpdate() puede ser utilizada varias veces. Veámoslo con un ejemplo.




Ejemplo 2. Calcula el message digest SHA-1 de la entrada estándar.

Ahora nuestra entrada va a tener una longitud indefinida. Almacenarla toda ella en un buffer no sería lo más conveniente (imáginese que nos pasan un ficherito de 3 o 4 MB). La solución pasa por crear un buffer ligerito (en el ejemplo 512 KB) e ir llamando a EVP_DigestUpdate() según se va llenando.

Una vez se ha añadido al contexto todo el mensaje únicamente queda llamar a la rutina de finalización (EVP_DigestFinal()). Ésta termina realizando las tareas pertinentes y deja en md el message digest y en el tercer parámetro la longitud del mismo. Este último parámetro admite NULL como entrada.


Cuestiones avanzadas

En los ejemplos 1, 2 y 3 hemos utilizado la constante simbólica EVP_MAX_MD_SIZE para reservar la memoria necesaria en la varible md. Ésta contiene el tamaño en bytes de la función hash más larga implementada en OpenSSL. Existe una serie de macros definidas que nos permiten extraer información de la estructura EVP_MD. De entre ellas EVP_MD_size() nos devuelve la longitud en bytes del message digest que le pasamos como parámetro. Un ejemplo.




Ejemplo 3. Utilizamos la macro EVP_MD_size() para extraer la longitud del MD.

La misma información podemos extraer a partir del contexto que hemos inicializado. La macro definida a tal fin es EVP_MD_CTX_size(). Nótese la similitud de los identificadores. Cuando tratamos con un contexto añadimos CTX_ entre MD y size. Pues bien, el resto de macros definidas se comportan igual. Éstas son EVP_MD_type(), EVP_MD_pkey_type() y EVP_MD_block_size() y sus equivalentes para contextos.

Para finalizar con la descripción de esta parte de la librería veremos una forma alternativa, pero menos eficiente, de obtener el message digest (un puntero a EVP_MD) que queremos. En particular lo haremos especificando el nombre de la función hash que deseamos. Para ello utilizaremos la función EVP_get_digestbyname(char *). Sin embargo, previamente debemos decirle a OpenSSL que cargue dichos nombres en una tabla interna mediante la función OpenSSL_add_all_digests(void). Veamos un ejemplo y lo explicamos.




Ejemplo 4. Obtenemos un message digest por su nombre.

Con EVP_cleanup() liberamos la tabla que cargamos anteriormente. Como veremos en otros apartados existe una función homóloga que nos permitirá obtener un algoritmo de cifrado a partir de su nombre, así como la correspondiente funcón que carga las tablas para dichos algoritmos.

En algunos casos cargar TODAS las tablas es necesario para evitar que se produzcan errores en la librería. Pero esto es adelantar acontecimientos. En próximos apartados veremos con más detalle este aspecto de OpenSSL.


anterior índice siguiente