anterior | índice | siguiente |
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:
Muy bien, lo primero de todo será enumerar las funciones de inicialización, actualización y finalización:
|
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 |
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:
|
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().
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.
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.
|
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.
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.
|
|
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.