Tutorial OpenSSL/EVP
u   

Tras varios meses de trabajo parece que el tutorial ya adquiere una cierta fiabilidad. Como siempre, estamos abiertos a sugerencias. Esperamos que le sea de utilidad. Un saludo,

Juan Segarra Montesinos - juan@nisu.org
Última modificación (dd/mm/yyy):


Índice


Introducción a la EVP

La librería EVP proporciona una serie de funciones de alto nivel que sirven de interfaz a los algoritmos criptográficos implementados en OpenSSL. En la librería se pueden identificar varios grupos de funciones:

EVP proporciona una interfaz homogénea que facilita, en gran medida, su aprendizaje. La estructura de datos fundamental es el contexto. Sobre ésta se añaden los datos que van a ser procesados (encriptados, hasheados, etc).

Sobre los contextos se pueden realizar básicamente tres tipos de operaciones:

Además existen otras estructuras de datos fundamentales para la librería. A lo largo del tutorial ejemplificaremos su uso.


Una primera toma de contacto

Un error que se comente con cierta frecuencia consiste en no cargar las tablas de algoritmos cuando escribimos una aplicación. Esto, en algunas ocasiones, no va a ser necesario. De hecho la mayoría de ejemplos que veremos en las siguientes secciones no lo hacen, porque no lo requieren. Sin embargo, una aplicación medianamente grande sí lo va a necesitar.




Ejemplo 1. Comienzo de una aplicación típica

Otra cuestión importante es el manejo adecuando de los errores. La gran mayoría de nuestros ejemplos no realizan un control exhaustivo de los mismos para dar más énfasis a las partes que se tratan. Para más información puede tratar el tutorial sobre tratamiento de errores.

Con evp se pueden hacer muchas, muchas, muchas cosas, como encriptar...




Ejemplo 2. Encriptación basada en password (PBE).

Empiece a familiarizarse con estructuras de datos como EVP_CIPHER o EVP_CIPHER_CTX. Ésta última representa un contexto de encriptación... pero no se altere :-) Todo esto lo veremos más adelante ya que, como dijimos antes, con evp se pueden hacer muchas, muchas, muchas cosas... como firmar...




Ejemplo 3. Firma una información de entrada.

Y muchas otras cosas: verificar una firma, calcular message digests, etc. No se preocupe... el funcionamiento de cada una de estas partes es muy similar. La principal ventaja de la evp es que se pueden hacer muchas, muchas, muchas cosas y todas de un modo similar :-P


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.


Las claves en EVP

El resto del tutorial va a hacer un uso considerable de claves: la firma y verificación requieren de claves asimétricas, la parte de encriptación simétrica necesitará de claves simétricas y, en ocasiones, un vector de inicialización. Parece el momento ideal de enterarnos de qué va esto.


Claves asimétricas

Probablemente no sea la mejor forma de nombrarlas. Por claves asimétricas entendemos las claves utilizadas por los algoritmos asimétricos (RSA, DSA, etc. ). Si bien cada uno de estos algortimos utiliza su propia estructura de datos para almacenar las claves (la estructura RSA y DSA respectivamente), EVP oculta estos detalles proporcionando una única estructura de datos en la que se puede almacenar la clave de cualquier algoritmo de clave pública implementado en OpenSSL. Se trata de la estructura EVP_PKEY.

Existen bastantes funciones que permiten manipular de forma directa una clave de este tipo que escapan al objetivo del tutorial. Nosotros vamos a utilizar las siguientes:

  • EVP_PKEY *EVP_PKEY_new()
  • void EVP_PKEY_free (EVP_PKEY *)
  • int EVP_PKEY_size (EVP_PKEY *)
  • int EVP_PKEY_assign (EVP_PKEY *, int, char *)

Su funcionalidad parece obvia. EVP_PKEY_new() y EVP_PKEY_free() permiten reservar/liberar memoria para las claves. Antes de poder utilizar una clave EVP va a ser necesario hacer una llamada a EVP_PKEY_new(). Esta función devuelve NULL en caso de producirse un error. Veamos un ejemplo.




Ejemplo 1. Reserva y liberación de memoria.

Pues bien, este ejemplo 1 se limita únicamente a reservar memoria para una clave EVP y posteriormente liberarla.

No es posible generar claves con evp. Para instanciar una clave debemos hacer uso de funciones de otras librerías (PEM, RSA, DH...) y posteriormente asignarla con la función EVP_PKEY_assign(), que devuelve 0 en caso de producirse un error. Veamos un ejemplo:




Ejemplo 2. Utilización de EVP_PKEY_assign().

El ejemplo 2 genera un par de claves (pública y privada) de 1024 bits con una función propia de la librería RSA y, posteriormente, se la asigna a la clave EVP que hemos reservado previamente. EVP_PKEY_assign() toma tres argumentos: la clave EVP, el tipo de clave que le vamos a asignar y un puntero a carácter con la clave generada. Para especificar el tipo, se proveen tres macros:

  • EVP_PKEY_RSA. Para claves RSA
  • EVP_PKEY_DSA. Para claves DSA
  • EVP_PKEY_DH. Para claves del protocolo Diffie-Hellman

Existe una forma indirecta y más sencilla de hacer todo esto. Utilizando las macros siguientes:

  • EVP_PKEY_assign_RSA
  • EVP_PKEY_assign_DSA
  • EVP_PKEY_assign_DH

Todas ellas toman como primer argumento la clave EVP que acabará con la clave y como segundo argumento la clave a asignar, que será del mismo tipo especificado en el nombre de la clave, es decir: RSA, DSA y DH respectivamente. Veamos como nos queda el ejemplo 2 utilizando la macro correspondiente.




Ejemplo 3. Utilizamos EVP_PKEY_assign_RSA() para asignar una clave.

Por último, en determinados casos puede sernos útil saber el tamaño de la clave que alberga una clave EVP (por ejemplo cuando queremos firmar un mensaje, la longitud de la firma va a ser como mucho la longitud de la clave en bytes). Para ello disponemos de la funcion EVP_PKEY_size().




Ejemplo 4. Utilización de la función EVP_size().

Este ejemplo lee un clave en formato PEM guardada en el fichero especificado por el primer argumento e imprime su tamaño en bytes. Como se puede apreciar en este caso no ha sido necesario reservar memoria para la clave. Y con esto creo que es suficiente para entender el resto del tutorial cuando nos manejemos con los algoritmos asimétricos.


Claves simétricas

En lo que a claves para algoritmos simétricos se refiere se nos presentan dos alternativas: utilizar como clave números aleatorios generados mediante la librería RAND ó bien derivar una clave a partir de una password introducida por el usuario. Esta última forma está basada en el PKCS#5 y hace uso de las PBE routines, que trataremos en un apartado propio. Aquí veremos algún ejemplo de generación de claves como números aleatorios.

Esta forma es interesante en lo que a generación de claves de sesión se refiere. Sin embargo, OpenSSL implementa una serie de funciones que automatizan todo el proceso de generación de claves de sesión y encriptación de las mismas en las envelope routines.

No vamos a explicar todo el proceso de alimentación del generador de números aleatorios. Para todo este tema puede consultar el tutorial sobre OpenSSL/RAND. Vamos a limitarnos a ver algún ejemplo y comentarlo.




Ejemplo 5. Generamos una clave para un algoritmo simétrico con longitud de clave fija.

En primer lugar cabe explicar algunas estructuras y funciones nuevas que han aparecido. La estructura de datos EVP_CIPHER se utiliza para identificar los algoritmos simétricos a utilizar. En la parte de cifrado simétrico utilizaremos ampliamente esta estructura y, además, mostraremos cómo obtener cualquier algoritmo que esté implementado (aunque ya hemos dado alguna pista :-P). En particular, la estructura nos es de interés para obtener la longitud de la clave del algoritmo así como la del vector de inicialización. Esto se hace a través de las macros (respectivamente):

  • EVP_CIPHER_key_length()
  • EVP_CIPHER_iv_length()

Ambas toman como argumento un puntero a la estructura EVP_CIPHER y devuelven el valor en bytes.

Existen en OpenSSL algoritmos de longitud de clave variable (RC4 por ejemplo). A dichos algoritmos se les asigna una longitud de clave por defecto (128 bits para el caso del RC4) que es posible obtener mediante la macro descrita anteriormente. Sin embargo EVP_CIPHER_key_length() no es válida si establecemos la longitud de la clave distinta a la de por defecto. En la parte de cifrado simétrico aprenderemos a establecer la longitud de la clave y a cómo obtener la longitud de la misma una vez establecida.


Firma/Verificación

EVP permite abstraer los procesos de firma y verificación. El usuario no tendrá que calcular el resumen del texto que desea firmar. Lo veremos en los dos apartados siguientes:


La firma

En primer lugar vamos a aprender a firmar. Veamos cuales son las funciones de inicialización, actualización y finalización:

  • void EVP_SignInit (EVP_MD_CTX *ctx, const EVP_MD *type)
  • void EVP_SignUpdate (EVP_MD_CTX *ctx, const void *d, unsigned int cnt)
  • int EVP_SignFinal (EVP_MD_CTX *ctx, unsigned char *sig, unsigned int *s, EVP_PKEY *pkey)

Vamos a entrar en calor.




Ejemplo 1. Firma una cadena de caracteres.

El ejemplo firma la cadena de caracteres apuntada por mensaje. Uno se da cuenta que no es necesario calcular explícitamente el message digest de mensaje (de hecho en la versión actual EVP_SignInit no hace más que una llamada a EVP_DigestInit).

Por lo que respecta a la inicialización (EVP_SignInit()), únicamente se especifica la función hash que se utilizará. Le remito a la parte de message digest para obtener una tabla con las funciones hash disponibles.

Por lo que respecta a la actualización ( EVP_SignUpdate() ), el segundo argumento apunta a un buffer en el que residen los datos que añadiremos a la firma. En nuestro caso, sólo el buffer mensaje. En el tercer argumento especificamos el número de bytes a tomar del buffer. Podemos llamar repetidas veces a EVP_SignUpdate() para añadir nuevos datos al contexto de la firma.

En cuanto a la finalización ( EVP_SignFinal() ) cabe hacer algunas puntualizaciones primero. Esta función va a devolvernos en el segundo argumento la firma, por lo que nuestro primer problema reside en averiguar qué cantidad de memoria va a ser necesaria para albergarla. Pues bien, a lo sumo EVP_PKEY_size() bytes de memoria (consulte el apartado Las claves en EVP).

EVP_SignFinal() devuelve en el tercer parámetro (salvo que este sea NULL) el tamaño en bytes de la firma. El último parámetro contiene la clave privada con la que firmaremos el mensaje. Devuelve 1 si la operación se ha llevado a cabo con éxito o 0 en caso de fallo.

Veamos otro ejemplo. Ahora vamos a firmar la información que nos llegue por la entrada estándar. Para ello leeremos la clave privada de un fichero en formato PEM. Puede generar usted mismo una clave rsa de 1024 bits con el comando:

openssl genrsa -out clave.pem 1024




Ejemplo 2. Firmamos la entrada estándar.

Puede encontrar información relativa a OpenSSL/PEM en el tutorial NISU de Mario Prats y Rafa Forcada. No se inquiete!! No es necesario que lo aprenda ahora. Vamos ahora a verificar la firma...


La verificación

El proceso de verificación posee de sus propias funciones de inicialización, actualización y finalización. Son las siguientes:

  • void EVP_VerifyInit(EVP_MD_CTX *ctx, const EVP_MD *type);
  • void EVP_VerifyUpdate(EVP_MD_CTX *ctx, const void *d, unsigned int cnt);
  • int EVP_VerifyFinal (EVP_MD_CTX *ctx, unsigned char *sigbuf, unsigned int siglen, EVP_PKEY *pkey);

Los prototipos son prácticamente idénticos a los de la firma. Describamos las funciones antes de ver un ejemplo.

La función de inicialización (EVP_VerifyInit()) inicializa el contexto con la función hash a utilizar. Recuerde la relación existente entre los algoritmos de clave pública y las funciones hash, ya que esta suele ser una fuente importante de problemas.

La función de actualización toma como entrada la información en claro (d) que va a verificarse. Básicamente se limita a calcular el message digest de la misma. Como viene siendo habitual, EVP_VerifyUpdate() puede llamarse repetidas para procesar la información de entrada por bloques.

La función de finalización, es la encargada de emitir el veredicto. Como parámetros toma la firma (sigbuf), la longitud de la firma (siglen) y la clave pública correspondiente a la clave privada con que se firma (pkey). Los valores devueltos por EVP_VerifyFinal() son 1 si la firma es correcta, 0 si no lo es y -1 en caso de error.

Muy bien, vamos a verificar una firma y para ello nos basaremos en el ejemplo 2. Además leeremos la clave pública correspondiente a la clave privada que firmó, de un certificado. Puede generarse un certificado autofirmado y una clave utilizando el siguiente comando:

openssl req -new -x509 -out cert.pem -newkey rsa:1024 -keyout clave.pem -nodes




Ejemplo 3. Verificación de firma.


Cifrado simétrico

Las rutinas de encriptación proveen una interfaz a los algoritmos de encriptación de clave privada implementados en OpenSSL. Gracias a ella, podremos abstraer las particularidades de cada implementación y utilizar de forma muy similar (prácticamente idéntica), por ejemplo, un algoritmo DES y un IDEA. Para esta parte supondremos que usted comprende el funcionamiento de los algoritmos de clave simétrica, así como los modos de cifrado (CBC, ECB, CFB, OFB) de los algortimos de encriptación por bloques.

Si no lo ha hecho aún, échele un vistazo a la parte de claves simétricas del tutorial. Puesto que no se hará incapié en temas como la generación de las claves, etc. Trataremos esta parte del tutorial en tres subapartados:


La encriptación

Muy bien, lo primero de todo será enumerar las funciones de inicialización, actualización y finalización:

  • int EVP_EncryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, unsigned char *key, unsigned char *iv);
  • int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl);
  • int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

Todas ellas devuelven 0 si se produce un error y un 1 en caso contrario.Vamos a por nuestro primer ejemplo:




Ejemplo 1. Inicializamos un contexto de encriptación

Este ejemplo es muy simple. Nuestro único objetivo por el momento es inicializar un contexto de encriptación. Como siempre esto lo hacemos con una función de inicialización: EVP_EncryptInit() . Ésta toma como argumentos el contexto a inicializar ( ctx ), el algoritmo que se utilizará para la encriptación ( type ), la clave (key ) y un vector de inicialización ( iv ).

Centremos la atención en el segundo parámetro. Se introduce una estructura de datos que no habíamos visto hasta ahora: la EVP_CIPHER. Esta estructura de datos se utiliza para identificar los parámetros característicos de un algoritmo simétrico: tamaño de la clave, tamaño del vector de inicialización, tamaño de bloque, el NID del algortimo, etc. Como puede intuirse no es el programador el encargado de rellenar cada uno de los campos de la estructura. Las siguientes funciones se utilizan para obtener punteros a EVP_CIPHER convenientemente inicializados. En el ejemplo 1 utilizamos la función EVP_ede_des3_cbc() para utilizar triple des con tres claves en modo cbc. La siguiente tabla muestra las funciones que podemos utilizar y el algoritmo que devuelven:

Función Algoritmo
EVP_enc_null(void) Este cifrador no hace nada
EVP_des_cbc(void), EVP_des_ecb(void), EVP_des_cfb(void), EVP_des_ofb(void) El algoritmo DES en modos CBC, ECB, CFB y OFB respectivamente.
EVP_des_ede_cbc(void), EVP_des_ede(void), EVP_des_ede_ofb(void), EVP_des_ede_cfb(void) Triple DES con dos claves en modos CBC, ECB, OFB y CFB respectivamente
EVP_des_ede3_cbc(void), EVP_des_ede3(void), EVP_des_ede3_cfb(void), EVP_des_ede3_ofb(void) Triple DES con tres claves en modos CBC, ECB, CFB y OFB respectivamente
EVP_desx_cbc(void) El algortimo DESX en modo CBC
EVP_rc4(void) El algoritmo RC4. Por defecto la longitud de clave es de 128 bits
EVP_idea_cbc(void), EVP_idea_ecb(void), EVP_idea_cfb(void), EVP_idea_ofb(void) El algoritmo IDEA en modos CBC, ECB, CFB, OFB y CBC respectivamente
EVP_bf_cbc(void), EVP_bf_ecb(void), EVP_bf_cfb(void), EVP_bf_ofb(void) El algoritmo Blowfish en modos CBC, ECB, CFB y OFB
EVP_cast5_cbc(void), EVP_cast5_ecb(void), EVP_cast5_cfb(void), EVP_cast5_ofb(void) El algoritmo CAST en modos CBC, ECB, CFB y OFB respecticamente
EVP_rc5_32_12_16_cbc(void), EVP_rc5_32_12_16(void), EVP_rc5_32_12_16_cfb(void), EVP_rc5_32_12_16_ofb(void) El algoritmo RC5 en modos CBC, ECB, CFB y OFB
EVP_rc2_cbc(void), EVP_rc2_ecb(void), EVP_rc2_cfb(void), EVP_rc2_ofb(void) El algoritmo RC2 en modos CBC, ECB, CFB y OFB. Por defecto utiliza una longitud de clave y de clave efectiva de 128 bits
Tabla 1. Algoritmos de encriptación soportados.

Aunque se pueda, no es muy aconsejable acceder directamente a los campos de una EVP_CIPHER (donde hoy pongo trigo mañana digo Rodrigo :-P). Para esta tarea disponemos de varias macros. En particular, nuestro ejemplo utiliza EVP_CIPHER_key_length(EVP_CIPHER *) y EVP_CIPHER_iv_length(EVP_CIPHER *) para obtener el tamaño en bytes de la clave y del vector de inicialización respectivamente. Según surjan necesidades las iremos viendo.

De acuerdo, vamos a complicarnos la vida (¿qué sería la vida sin complicaciones? :-O). A por otro ejemplo:




Ejemplo 2 . Encripta una cadena de caracteres.

Muy bien. La función de actualización EVP_EncryptUpdate() se utiliza para añadir los datos que queremos ir encriptando. Los parámetros que toma son: el contexto de encriptación (ctx ), los datos de entrada encriptados (out), el tamaño de out ( outl ), la información de entrada que se desea añadir ( in ) y la longitud de la información de entrada ( inl ).

EVP_EncryptUpdate() puede ser llamada en múltiples ocasiones para ir añadiendo nuevos datos a encriptar (in). Los resultados parciales se van devolviendo en out. El problema que se plantea es saber qué cantidad de memoria va a ser ocupada por el buffer out . La solución tiene relación con la forma de trabajar de los algoritmos en bloque (luego veremos que el razonamiento es igualmente aplicable a los cifradores de flujo). Si inl no es múltiplo del tamaño de bloque, la cantidad en bytes que se devolverá en out será: inl - (inl % tam_bloque). Los bytes que restan serán añadidos en subsiguientes llamadas a EVP_EncryptUpdate(). El peor de los casos se presenta cuando inl % tam_bloque = tam_bloque-1 (ver figura 1) por ese motivo para no desbordar el buffer out éste debe apuntar a una zona reservada de inl + tam_bloque - 1 ( la primera vez que se llame a EVP_EncryptUpdate() no ocurre nada, pero la siguiente podríamos desbordarlo. Reservando dicha cantidad de memoria esto no ocurrirá nunca ).

Figura 1. Representación del peor caso posible utilizando EVP_EncryptUpdate().

Lo que hemos visto es aplicable a la función EVP_EncryptUpdate. La cuestión ahora es saber qué cantidad de memoria es necesario reservar si quisiéramos obtener toda la encriptación. Pues bien, a los sumo: tamaño_entrada + tamaño_bloque. Esto es así por el modo en que trabaja EVP_EncryptFinal. Ésta devuelve un último bloque que contiene la información que faltaba encriptada (caso de que la entrada no sea múltiplo del tamaño de bloque) más tantos bytes como falten para completar un bloque. Si faltaran n bytes para completar el último bloque se le añadirían al final tomando cada uno de ellos el valor n. Ésto es lo que se conoce como padding pkcs. El peor caso posible se presenta cuando la información de entrada es múltiplo del tamaño de bloque, en cuyo caso EVP_EncryptFinal devuelve un bloque sólo de padding. La figura 2 muestra tal situación.

Figura 2. Represetación del peor caso posible utilizando EVP_EncryptFinal().

Después de este rollo te estarás preguntando: "¿tengo que aprenderme el tamaño de bloque de cada uno de los algoritmos?" Pues la respuesta es NO. De nuevo echamos mano de la EVP_CIPHER y encontramos la macro EVP_CIPHER_block_size(EVP_CIPHER *) que devuelve el tamaño del bloque en bytes. Cuando le pasamos un cifrador de flujo, devuelve 1 byte con lo que todo el razonamiento que hicimos para la reserva de memoria del buffer encriptado es igualmente aplicable a estos cifradores (RC4 ).

Vamos a tratar de exponer con más claridad el tema este de la reserva de memoria. Modifiquemos el ejemplo 2. Esta vez leeremos datos de la entrada estándar y el resultado lo devolveremos por salida estándar (ojito con el terminal :-P).




Ejemplo 3. Encriptamos la entrada estándard

Se ha escrito una función (encriptar) para encriptar (:-P). Lo cierto es que no hay mucho que se pueda comentar ya, de modo que pasamos a la desencriptación.


La desencriptación

Bueno, al fin pondremos las cosas claras :-P Si ha entendido la parte anterior, no tendrá ningún problema con ésta. Veamos las funciones de inicialización, actualización y finalización.

  • int EVP_DecryptInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, unsigned char *key, unsigned char *iv);
  • int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl);
  • int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm,int *outl);

El significado de los parámetros es prácticamente idéntico a las funciones homólogas de la encriptación, de modo que no nos pararemos a explicarlas de nuevo. En esta ocasión el tamaño del buffer out en la función EVP_DecryptUpdate() deberá ser el tamaño del buffer in + tamaño de bloque del algoritmo, es decir: inl + EVP_CIPHER_block_size( EVP_CIPHER * ).

El proceso de desencriptación elimina los bloques que no son de padding (tras comprobar que es correcto). Por este motivo, en el peor de los casos, el tamaño de la información desencriptada no superará los bytes_entrada - 1 bytes. Sin embargo esto sólo es cierto para algoritmos de cifrado por bloques. Los cifradores de flujo devolverán bytes_entrada.

Para poner punto y final a esta parte ampliaremos el ejemplo 3, añadiendo una función desencriptar cuyo código se muestra a continuación.




Ejemplo 4 . Añadimos la función desencriptar al ejemplo 3.


Cuestiones Avanzadas

Hemos aprendido a encriptar y desencriptar con cualquier algoritmo de clave privada que esté implementado en OpenSSL . Ha llegado el momento de plantearnos algunas cuestiones relacionadas con la modificación de determinados parámetros de los algoritmos como puedan ser longitudes de clave y el número de vueltas del RC5 y RC2. Vamos a por ello.


Establecimiento de la longitud de clave
en algoritmos de clave variable

La mayor parte de los algoritmos implementados son de clave fija, es decir, para cifrar/descrifrar siempre toman como entrada una clave de una longitud determinada. Existen algoritmos, como el RC4 , que admiten longitudes de clave variable. Estos algoritmos vienen con ciertos valores por defecto que se obtienen con la ya estudiada estructura de datos EVP_CIPHER correspondiente. Así, por ejemplo, el valor devuelto por EVP_CIPHER_key_length (EVP_rc4()) sería 16 bytes y corresponde a la longitud de clave por defecto del citado algoritmo. La modificación de los parámetros se realiza a través de la estructura de contexto: EVP_CIPHER_CTX . Veamos cómo.


Las funciones de inicialización (la de encriptación y desencriptación) pueden ser llamadas en varias ocasiones para establecer los parámetros requeridos siempre y cuando la primera llamada especifique el type y las siguientes pongan este parámetro a NULL .

Como dijimos anteriormente, la modificación de los parámetros se realizará a través del contexto. La función que utilizaremos para tal fin se describe a continuación:

  • int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen);

La función devuelve 0 en caso de error y 1 si no lo hay. Como argumentos toma el contexto y la nueva longitud de clave a utilizar en bytes. Todo esto se verá mejor con el siguiente ejemplo.



Ejemplo 5. Establecemos la longitud de clave del RC4.

En general, también existen macros para los contextos que permiten extraer información acerca del mismo. En el ejemplo hemos utilizado EVP_CIPHER_CTX_key_length() y EVP_CIPHER_CTX_block_size() para extraer, respectivamente, el tamaño en bytes de la clave y del tamaño de bloque (obviamente del algoritmo que le asociamos en la función de inicialización).

Queremos recalcar más. si cabe, el hecho de que el establecimiento de parámetros se hace a nivel de contexto. Por este motivo la consulta hecha con EVP_CIPHER_key_length() devolverá la longitud de clave por defecto.


Longitud de clave efectiva y número de vueltas
en el RC2 y número de vueltas en el RC5

A este respecto la sección de ejemplos de la página de manual EVP_EncryptInit(3) es bastante clara y no parece necesario hacer ninguna aclaración al respecto.


Envelope Encryption Routines

La principal desventaja de los algortimos de clave pública es su lentitud. Para subsanar esa deficiencia se combinan con los algoritmos de clave simétrica en lo que se suele denominar envelope encryption/decryption. EVP implementa funciones que permiten tratar este aspecto de la criptografía de manera directa. Lo vemos en dos apartados:


La encriptación

Veamos (una vez más :-P) las funciones de inicialización, actualización y finalización:

  • int EVP_SealInit(EVP_CIPHER_CTX *ctx, EVP_CIPHER *type, unsigned char **ek,int *ekl, unsigned char *iv,EVP_PKEY **pubk, int npubk);
  • int EVP_SealUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl);
  • int EVP_SealFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

La función de inicialización genera una clave de sesión aleatoria para el algoritmo (type) especificado. Dicha clave de sesión se cifra con cada una de las claves públicas que le pasamos en el parámetro pubk, devolviendo el resultado en ek. Así pues ek[0] apuntará a la clave de sesión encriptada con pubk[0], ek[1] con pubk[1] y así sucesivamente. npubk es el número de claves públicas que pasamos en pubk. Veamos un ejemplo:


<!--#include file="ejemplos/sealopen/EVP_seal_e1.c" -->

Ejemplo 1. Inicializamos un contexto de encriptación.

Pese a lo que pueda parecer (ojito porque las páginas man están incorrectas) el parámetro iv es de salida y no de entrada. La función, además de generar una clave de sesión aleatoria genera también un vector de inicialización aleatorio (si procede), que almacena en la zona apuntada por iv.

Es necesario reservar espacio para cada una de las componenetes del vector ek. El tamaño en bytes necesario se puede obtener con la macro EVP_PKEY_size(EVP_CIPHER *).

Como en el caso de EVP_EncryptInit(), que vimos en la parte de cifrado simétrico, EVP_SealInit() puede llamarse repetidas veces. El motivo, el mismo: establecer algunos parámetros variables de los algoritmos (como la longitud de la clave). Para hacer esto el protocolo es el siguiente (atento porque esto no está muy bien documentado en las páginas man):

  1. Llamar a EVP_SealInit() con el parámetro npubk puesto a 0, el contexto que vamos a inicializar y el parámetro type. El resto de parámetros son ignorados.
  2. Establecer los parámetros en cuestión.
  3. Llamar a EVP_SealInit() con type puesto a NULL y el resto de parámetros con los valores correspondientes.

Veamos un ejemplo.


<!--#include file="ejemplos/sealopen/EVP_seal_e2.c" -->

Ejemplo 2. Inicializar un contexto modificando parámetros de algoritmos.

El ejemplo establece la longitud de clave del RC4 a 40 bits. Existe un pequeño problema que no viene documentado pero que viendo el código de la función se aprecia eseguida. EVP_SealInit() devuelve 0 si se produce un error y npubk si todo funciona bien. Sin embargo esto no es así siempre. Cuando npubk es igual a 0 EVP_SealInit() devuelve también cero, sin embargo esto no es necesariamente un indicador de que algo ha ido mal. El ejemplo 2 da buena fe de ello. Nuestro parámetro npubk vale cero porque queremos establecer una longitud de clave distinta. En estos casos la única forma que tenemos de detectar el error es comprobando que la cola de errores asociada al thread de ejecución está vacía. Para ello puede consultar el tutorial sobre OpenSSL/ERR. Realmente no alcanzo a comprender el porqué de ese comportamiento.

En cuanto a las funciones de actualización y finalización tienen el mismo comportamiento que sus homólogas en la parte de cifrado simétrico. Veamos un ejemplo:


<!--#include file="ejemplos/sealopen/EVP_seal_e3.c" -->

Ejemplo 3. Encriptamos una cadena de caracteres.


La desencriptación

Veamos las ya míticas funciones de inicialización, actualización y verificación.

  • int EVP_OpenInit(EVP_CIPHER_CTX *ctx,EVP_CIPHER *type,unsigned char *ek, int ekl,unsigned char *iv,EVP_PKEY *priv);
  • int EVP_OpenUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl, unsigned char *in, int inl);
  • int EVP_OpenFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl);

Nuevamente obsérvese la similitud entre EVP_OpenUpdate() y EVP_OpenFinal() con EVP_DecrypUpdate() y EVP_DecryptFinal(). Su funcionamiento es el mismo de modo que vamos a centrarnos en la función de inicialización.

EVP_OpenInit() toma como parámetros el contexto a inicializar (ctx), el algoritmo que se utilizará para descifrar (type), una clave encriptada con un algoritmo de clave publica (ek), la longitud de ek (ekl), el vector de inicialización (iv) y la clave privada que utilizaremos para obtener (Open) la clave simétrica que tenemos en ek.

Fácil... ¿no? Pues vamos a por otro ejemplo.


<!--#include file="ejemplos/sealopen/EVP_seal_e4.c" -->

Ejemplo 4. Inicializamos un contexto para desencriptación.

El ejemplo 4 es una ampliación del 3. Inicializamos el contexto al mismo tiempo que establecemos la longitud de clave del RC4. El protocolo para hacer esto último ahora es el siguiente:

Para acabar vamos a desencriptar el mensaje con cada una de las claves que han sido encriptadas.


<!--#include file="ejemplos/sealopen/EVP_seal_e5.c" -->

Ejemplo 5. Desencriptamos una cadena de caracteres.


Las PBE routines

Hasta ahora todos nuestros ejemplos de cifrado simétrico han utilizado claves generadas aleatoriamente mediante la librería RAND de OpenSSL. De esta forma, por ejemplo, las envelope encryption routines generan una clave de sesión. Sin embargo, en muchas ocasiones, resulta conveniente cifrar/descifrar una entrada utilizando una password introducida por el usuario, de la cual se derivará una clave mediante una función de derivación de claves.

El tema del Password Based Encryption se aborda en los estándares PKCS#5 y PKCS#12 (éste únicamente describe una función de derivación de claves en el apartado B). Una rápida leida a los mismos ayudará a entender los conceptos que aquí se utilizan. En particular, OpenSSL implementa las recomendaciones del PKCS#5 v. 1.5, PKCS#5 v. 2.0 y PKCS#12. Nosotros nos centraremos en como llevar a cabo las recomendaciones del PKCS#5 v. 2.0. En el apéndice B abordaremos para las otras dos.

Muy bien, entremos en materia. Hasta ahora hemos utilizado la función EVP_EncryptInit() (o EVP_CipherInit() ) para inicializar un contexto de encriptación. Dicha función toma directamente la clave que se utilizará en el proceso de encriptación o desencriptación. Lo evidente sería: ¿por qué no una función que inicialice el contexto utilizando una password ? La evidencia :-P

  • int EVP_PBE_CipherInit (ASN1_OBJECT *pbe_obj, const char *pass, int passlen, ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de);

Describamos los parámetros de la función:

La evidencia deja de serlo cuando vemos los parámetros pbe_obj y param. El primero especifica el algoritmo que se va a utilizar en el cifrado/descifrado y el segundo los parámetros necesarios para la derivación de la clave: salt, iteration count, etc. Ambos parámetros se encapsulan en la estructura de datos X509_ALGOR definida en el header x509.h. Podemos decir que X509_ALGOR es un identificador de algoritmo. Así pues, antes de poder inicializar un contexto debemos iniciailizar un identificador de algoritmo. ¿Cómo? Con la función siguiente.

  • X509_ALGOR * PKCS5_pbe2_set (const EVP_CIPHER *cipher, int iter, unsigned char *salt, int saltlen);

Describamos los parámetros:

Es necesario alimentar el generador de números aleatorios. Puede consultar el tutorial sobre OpenSSL/RAND para ver cómo. Ya está bien de rollos, ¿no?


<!--#include file="ejemplos/pbe/EVP_pbe_e1.c" -->

Ejemplo 1. Inicializamos un contexto para encriptación en base al PKCS#5 v. 2.0.

Este ejemplo se limita a inicializar un contexto de encriptación para encriptar. OpenSSL utiliza varias tablas internas de algoritmos (de encriptación y funciones hash). Dicha tabla debe ser inicializada por el programador utilizando la función que estamos tratando. El motivo es muy simple: ciertas funciones de OpenSSL identifican los algoritmos a utilizar por nombre buscando en las tablas. Si no están inicializadas obtendremos un error (un error muy frecuente, por cierto).

Resaltemos dos puntos:

Lo importante del ejemplo anterior es que comprenda cómo obtener los parámetros pbe_obj y param de la función de inicialización EVP_PBE_CipherInit() puesto que, como ya dijimos, el proceso de encriptación es idéntico al caso de cifrado simétrico. Veámoslo con otro ejemplo.


<!--#include file="ejemplos/pbe/EVP_pbe_e2.c" -->

Ejemplo 2. Encriptamos una cadena de caracteres.

Fácil, ¿no? ¿Desencriptamos? Vamos allá...


<!--#include file="ejemplos/pbe/EVP_pbe_e3.c" -->

Ejemplo 3. Desencriptamos la cadena de caracteres.


Apéndice A. La obsoleta EVP_BytesToKey()

Lo primero de todo: EVP_BytesToKey() está obsoleta. Esta función se implementó en SSLeay para derivar claves a partir de passwords introducidas por el usuario. Pese a que la función sigue formando parte de las distribuciones de la librería (incluso muchas de las aplicaciones del intérprete la utilizan) las PBE routines deben utilizarse en su lugar.

Veamos el prototipo:

  • int EVP_BytesToKey (const EVP_CIPHER *type, EVP_MD *md, const unsigned char *salt, const unsigned char *data, int datal, int count, unsigned char *key, unsigned char *iv)

Esta función está basada en el PKCS#5 (Password-Based Cryptography Standard). Si se siente con fuerzas puede pegarle una leída, pero tampoco es esencial para comprender el funcionamiento de la función.

La idea es generar una clave (key) y un vector de inicialización (iv) si procede, a partir de unos datos de entrada (data), de una función hash (md) y, opcionalmente, de un salt (un vector de 8 bytes).

EVP_BytesToKey() devuelve siempre la longitud de la clave correspondiente al algoritmo especificado en type. Además, si data es NULL, devuelve este valor sin realizar ninguna otra operación. Por otro lado, el parámetro salt es opcional. Veamo, a nivel de curiosidad, el algoritmo de la función. Pero antes un inciso sobre la notación empleada:


md_buf = H (data + salt)

MIENTRAS cierto HACER

   PARA i = 1 HASTA count-1 HACER
      md_buf = H (md_buf)
   FINPARA

   SI key no completa ENTONCES
      key = bytes de md_buf
   FINSI

   SI iv no completo ENTONCES
      iv = bytes de md_buf
   FINSI

   SI iv Y key completos ENTONCES
      SALIR del MIENTRAS
   SINO
      md_buf = H (md_buf + data + salt)
   FINSI

FIN MIENTRAS

Algoritmo 1. Algoritmo de la función EPV_BytesToKey().

A la vista del algoritmo uno puede entender perfectamente los parámetros que se pasan a la función. Como se puede apreciar, la clave y el vector de inicialización se crean calculando repetidas veces la función hash que se especifica como parámetro de entrada hasta que tenemos el número de bytes suficientes tanto en key como en iv. Obviamente si el parámetro salt es NULL, no se tiene en cuenta, siendo el parámetro data fundamental para la función.


<!--#include file="ejemplos/apendice_a/EVP_apendice_a_e1.c" -->

Ejemplo 1. Generamos clave y vector de inicialización para triple DES.

Este ejemplo genera una clave y un vector de inicialización para el algoritmo triple DES en modo ofb. Si recuperara el valor devuelto por EVP_BytesToKey() comprobará que la longitud de dicha clave es de 24 bytes. Con esto queremos decir que no va a ser necesario realizar tres llamadas a EVP_BytesToKey() para obtener las tres claves utilizadas por el algoritmo puesto que estas ya se han introducido en el buffer clave.

Lo que hemos visto únicamente nos va a permitir derivar claves para algoritmos con longitud de clave fija y para los algoritmos de clave variable únicamente para las longitudes de clave por defecto.


Apéndice B. PBE según PKCS#5 v 1.5 y PKCS#12

En el apartado de las PBE routines abordamos el tema bajo las recomendaciones de la versión 2.0 del estándar PKCS#5. Ese es el modo recomendado para las nuevas aplicaciones. También comentamos que el PKCS#12 describe una función de derivación de claves, que se encuentra implementada en OpenSSL. Lo que vamos a ver ahora es cómo seguir las recomendaciones de estos dos últimos estándares. Como veremos la cuestión no difiere mucho de lo que ya dijimos.

La función de inicialización que utilizaremos será la misma, lo único que va a cambiar es la obtención del identificador de algoritmo. Ésta es la función que vamos a utilizar:

  • X509_ALGOR *PKCS5_pbe_set(int alg, int iter, unsigned char *salt, int saltlen);

Describamos los parámetros.

Vamos a ver un ejemplo:


<!--#include file="ejemplos/apendice_b/EVP_apendice_b_e1.c" -->

Ejemplo 1. Inicializa un contexto para PKCS#5 v.1.5.

En esta ocasión utilizamos un NID que ya identifica qué algoritmo vamos a utilizar. Los NIDs de los algoritmos compatibles con el PKCS#5 v. 1.5 se muestran en la tabla siguiente:

NID Descripción
NID_pbeWithMD5AndDES_CBC DES en modo CBC
NID_pbeWithMD2AndDES_CBC DES en modo CBC
NID_pbeWithSHA1AndDES_CBC DES en modo CBC
NID_pbeWithMD5AndRC2_CBC RC2 en modo CBC
NID_pbeWithMD2AndRC2_CBC RC2 en modo CBC
NID_pbeWithSHA1AndRC2_CBC RC2 en modo CBC
Tabla 1. NIDs compatibles con el PKCS#5 v.1.5.

Observando la tabla uno se da cuenta enseguida de la debilidad de la recomendación. Es por este motivo que únicamente debe ser usada por motivos de compatibilidad con otras aplicaciones.

Los NIDs correspondientes a la función de derivación de claves del pkcs#12 se muestran en la tabla 2.

NID Descripción
NID_pbe_WithSHA1And128BitRC4 RC4 con 128 bits de clave
NID_pbe_WithSHA1And40BitRC4 RC4 con 40 bits de clave
NID_pbe_WithSHA1And3_Key_TripleDES_CBC Triple DES con tres claves en modo CBC
NID_pbe_WithSHA1And2_Key_TripleDES_CBC Triple DES con dos claves en modo CBC
NID_pbe_WithSHA1And128BitRC2_CBC RC2 en modo CBC con
NID_pbe_WithSHA1And40BitRC2_CBC RC2 en modo CBC con
Tabla 2. NIDs correspondientes a la recomendación PKCS#12

OpenSSL utiliza un lista que relaciona los NIDs con su respectiva función de derivación de claves (entre otras cosas). De modo que la utilización de una u otra recomendación es transparente al programador. Veamos un ejemplo:


<!--#include file="ejemplos/apendice_b/EVP_apendice_b_e2.c" -->

Ejemplo 2. Inicializa un contexto para PKCS#12.


Apéndice C. Como pedir una password a un usuario

Nuestros ejemplos de PBE utilizaban una cadena de caracteres fija como password. Generalmente la fuente de la password es el usuario. Por este motivo, EVP proporciona tres funciones que permiten gestionar un prompt. Son las siguientes:

  • int EVP_read_pw_string(char *buf,int length,const char *prompt,int verify);
  • void EVP_set_pw_prompt(char *prompt);
  • char *EVP_get_pw_prompt(void);

La de mayor utilidad es la primera. EVP_read_pw_string() permite leer una password que almacena en un buffer (buf) de tamaño length sin eco (no aparece en pantalla lo que se escribe). Introduce un '\0' al final del password introducido. El parámetro prompt es el mensaje que recibirá el usuario (por ejemplo: "Introduzca una password: ") y verify permitirá verificar la password introducida. Vamos a ver un ejemplo.




Ejemplo 1. Petición de una password sin verificación.

La función devuelve 0 si todo fue correcto, 1 si hubo un error por parte del usuario o del programador y -1 en caso de un fallo en el sistema. Este último error puede producirse cuando no se ha compilado OpenSSL con soporte para DES. Esto ocurre porque la función simplemente se limita a llamar a des_read_pw_string(). Veamos otro ejemplo:




Ejemplo 2. Petición de una password con verificación.

Las otras dos funciones se utilizan para establecer un prompt. La librería utiliza un puntero estático para almacenar una cadena de caracteres que se imprimirá si prompt es NULL en la función EVP_read_pw_string(). Así pues, EVP_set_pw_string() se utiliza para hacer apuntar el puntero a una cadena de caracteres adecuada y EVP_get_pw_string() obtiene dicho puntero. Si intentamos usar el puntero sin inicializarlo previamente obtendremos un error. Veamos el último ejemplo:




Ejemplo 3. Ejemplo de utilización de EVP_set_pw_string() y EVP_get_pw_string().


Apéndice D. PKCS#8 y EVP

Buena parte de las recomendaciones del PKCS#8 se encuentran implementadas en el directorio de EVP. Sin embargo no puede decirse que forme parte de esta librería puesto que las definiciones de las funciones que vamos a ver en este apéndice no se encuentran definidas en evp.h. De todas formas parece interesante que lo veamos.

Decir que OpenSSL implementa únicamente lo que el estándar define como Private-key information syntax y no la parte de Encrypted private-key information syntax. La estructura de datos que define la sintaxis implementada es la PKCS8_PRIV_KEY_INFO. Nosotros aprenderemos a inicializar simplemente una estructura de este tipo con una clave EVP y viceversa. Bueno, pues vamos a por lo primero. La función que vamos a utilizar es la siguiente:

  • PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8 (EVP_PKEY *pkey);

El ejemplo 1 habla por sí solo...




Ejemplo 1. Inicializar un objeto pkcs8 mediante una clave EVP.

No es difícil imaginar cómo obtener la clave de un objeto pkcs#8. La función es la siguiente:

  • EVP_PKEY *EVP_PKCS82EVP (PKCS8_PRIV_KEY_INFO *p8);

Pues nada, veamos cómo obtener la clave privada almacenada en un objeto pkcs#8...




Ejemplo 2. Obtener una clave privada almacenada en un objeto pkcs8.


Apéndice D. Lista de ejemplos

Una primera toma de contacto

Message Digests

Las claves en EVP

Funciones de Firma/Verificación

Cifrado simétrico

Envelope Encryption Routines

Las PBE routines

Apéndice A. La obsoleta EVP_BytesToKey()

Apéndice B. PBE según PKCS#5 v 1.5 y PKCS#12

Apéndice C. Como pedir una password a un usuario

Apéndice D. PKCS#8 y EVP