Implementación del Advanced Encryption Standard Rijndael en C Jorge Alfonso Briones Garcı́a Sección Computación Departamento de Ingenierı́a Eléctrica Centro de Investigación y de Estudios Avanzados del IPN Curso: Códigos y Criptografı́a Profr. Dr. Francisco Rodrı́guez Henrı́quez México, D.F. Agosto 5, 2002 Contenido 1.1 Rijndael . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.2 Campos finitos binarios GF (2m ) . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.2.1 GF (28 ) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.1 Aspectos de implementación . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2 La librerı́a de la implementación . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3 Implementación del algoritmo . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.3.1 Definición de estructuras . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.3.2 Implementación de los bloques básicos . . . . . . . . . . . . . . . . . 16 2.3.3 Implementación del algoritmo . . . . . . . . . . . . . . . . . . . . . . 21 2.3.4 Implementación de funciones de utilerı́a . . . . . . . . . . . . . . . . 23 2.3.5 Implementación de programas de prueba y aplicaciones . . . . . . . 25 Pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.1.1 Pruebas con vectores de NIST . . . . . . . . . . . . . . . . . . . . . . 30 3.1.2 Pruebas de eficiencia . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3.1.3 Pruebas cifrando archivos . . . . . . . . . . . . . . . . . . . . . . . . 36 3.1 1 Resumen Rijndael es un método criptográfico que ha cobrado una importancia significativa, ya que ha reemplazado a DES, y se ha convertido en un estándar (AES). El siguiente documento surge como parte del proyecto de implementación de Rijndael (AES) en C, el documento abarca desde la explicación del algoritmo, una breve introducción a la aritmética de campos finitos binarios, se presentan algunos detalles sobre la implementación en C, se ilustra como usar el API para incorporar AES a una aplicación, se muestran las pruebas realizadas a la implementación, finalmente explicamos la construcción de una mini-aplicación que permite el cifrado de archivos. Introducción Rijndael es el método criptográfico que vino a substituir a DES (Data Encryption Standard) el cual era uno de los algoritmos más usados en el mundo, y que poco a poco es desplazado por Rijndael. El surgimiento de Rijndael es interesante, todo comienza en 1997, cuando el National Institute of Standars and Technology (NIST) hacen un concurso para reemplazar a DES. Entre los requerimientos estaba que el nuevo algoritmo deberı́a de soportar llaves de tamaño de 128, 192, y 256 bits, deberı́a operar en bloques de 128 bits, también deberı́a de poder trabajar en un gran variedad de hardware, por ejemplo, procesadores de 8-bits que puedan ser usados en tarjetas inteligentes (smart cards), en arquitecturas de 32 bits usadas comunmente en computadoras personales, de igual manera fue importante la velocidad y la fortaleza critpográfica [1]. Los cinco finalistas resultantes fueron: MARS (IBM), RC6 (RSA), Rijndael (Joan Daemen y Vincent Rijmen),Serpent (Ross Anderson, Eli Biham, y Lars Knudsen), Twofish (Bruce Schneier, John Kelsey, Doug Whiting, David Wagner, Chris Hall, y Niels Fergunson) Finalmente, en octubre 2 de 2002, NIST anuncio que el método criptográfico ganador de la competencia de AES (Advanced Encryption Standard) era Rijndael. Rijndael es el método que se escogio para su implementación, y aunque oficialmente es un estándar para el gobierno de los Estados Unidos, se espera que también sea adoptado por otros gobiernos. Este hecho fue una de las motivaciones más fuertes del proyecto. El proyecto realizará la implementación del algoritmo, de ésta resultará una librerı́a la que después va a ser usada para el desarrollo de la aplicación de prueba. Considero de gran importancia el que surga una librerı́a a partir de la implementación de Rijndael, ya que existen muchas posibilidades para que en aplicaciones posteriores se reuse ésta, y se añada seguridad a las aplicaciones. Las fases del proyecto se organizaron de la siguiente manera: 1 1. Investigación y recolección de información acerca de AES. 2. Comprensión y estudio del algoritmo. 3. Implementación de las operaciones básicas del algoritmo. 4. Implementación del algoritmo. 5. Desarrollo de la aplicación de prueba usando la librerı́a resultante de la implementación. 6. Documentación del proyecto y de la librerı́a. 7. Pruebas de la implementación y de la librerı́a. Evidentemente algunas fases se realizan durante todo el proyecto, entre ellas está la primera que es la investigación y recolección de información, y la fase de documentación. El documento está organizado de la siguiente manera, en la siguiente sección explicamos el problema que deseamos atacar, en la sección posterior se explica algunos detalles de implementación del algoritmo, posteriormente se explica la librerı́a de la implementación, luego algunos lineamientos de la aplicación de prueba y finalmente como se realizarán las pruebas. 2 Análisis del Problema De forma general el objetivo principal del proyecto es realizar la implementación del método criptográfico en C, pero de forma particular el proyecto al final debe de cumplir con ciertos requerimientos que hemos definido como: • Generación de un documento general que explique la implementación • Generación de una librerı́a en C que tenga funciones que permita a otras aplicaciones contar con el método criptográfico Rijndael • Generación de una aplicación de prueba que muestre el uso de la librerı́a generada. • Establecer de alguna manera pruebas que permitan medir la eficiencia de la implementación Considero de importancia la librerı́a ya que va a permitir que otras aplicaciones usen ésta y cifren la información que están procesando. Para poder cumplir con este objetivo uno de los primeros pasos que debemos llevar a cabo es la comprensión del algoritmo, ya que esto nos permite identificar, sus principales bloques de construcción, las herramientas matemáticas que necesitan ser implementadas y de igual manera también nos permite definir una estrategia de diseño para la librerı́a. 1.1 Rijndael Rijndael es un método criptográfico de cifrado de bloque, en Rijndael tanto la longitud del bloque como la longitud de la llave son variables. Ellos pueden ser variados de manera independiente entre 128 y 256 bits en incrementos de 32 bits. Sin embargo, NIST solo va a estandarizar las versiones con un bloque de entrada de longitud de 128 bits y una llave de longitud de 128,192 y 256 bits [2]. 3 Rijndael fue diseñado también para ofrecer resistencia al criptoanálisis lineal y diferencial. En la estrategia, las rondas de transformación son dividas en diferetes componentes, cada uno con su propia funcionalidad. Definimos el estado del cifrado de bloque como el resultado intermedio del proceso de encriptado. El estado es representado por un arreglo rectangular de bytes con 4 renglones. El número de columnas está determinado por la longitud del bloque, por ejemplo, si el bloque es de longitud N , el numero de columnas Nb es igual a N/32. El estado es inicializado con el texto claro en el órden a0,0 , a1,0 , a2,0 , a3,0 , a0,1 , a1,1 , ..., como se muestra a continuación: a= a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 a2,1 a3,1 a0,2 a1,2 a2,2 a3,2 a0,3 a1,3 a2,3 a3,3 a0,4 a1,4 a2,4 a3,4 a0,5 a1,5 a2,5 a3,5 {z | } Nb columns En el caso anterior Nb = 6 dado que la longitud del bloque es N = 192. La ronda de transformación es construida de componentes de transformación que operan sobre el estado. Finalmente, al final de la operación de cifrado, el texto cifrado es leido del estado tomando los bytes de estado en el mismo órden. La llave de cifrado es similar al arreglo rectangular con 4 renglones. El número de columnas de la llave de cifrado es igual a la longitud de la llave divida por 32, como se muestra a continuación: a= k0,0 k1,0 k2,0 k3,0 | k0,1 k1,1 k2,1 k3,1 k0,2 k1,2 k2,2 k3,2 k0,3 k1,3 k2,3 k3,3 {z } Nk columns Algunas veces la llave de cifrado se ve como un arreglo lineal de palabras de 4 bytes. El número de rondas es denotado por Nr y depende de los valores Nb y Nk como se muestra a continuación: Nk Nb 4 6 4 10 12 6 12 12 8 14 14 4 8 14 14 14 Las rondas de transformación están compuestas por 4 diferentes componentes de transformación, los cuales mostramos a continuación: Round(State,RoundKey){ ByteSub(State); ShiftRow(State); MixColumn(State); AddRoundKey(State,RoundKey) } La ronda final del cifrado es ligeramente diferente y está definida como: FinalRound(State,RoundKey){ ByteSub(State); ShiftRow(State); AddRoundKey(State,RoundKey); } La transformación ByteSub es una substitución de bytes no lineal, operando sobre cada uno de los bytes de estados independientemente. La transformación usa la misma tabla de substitución (S-Box) para cada byte. La caja S-box ha sido seleccionada de tal manera que Rijndael sea resistente contra ataques lineales y diferenciales, asi como ataques de interpolación. Esto asegura que los requerimientos tales como tener una pequeña correlación entre los bits de entrada y bits de salida y del hecho de que la salida no puede ser representada como una función matemática de la entrada. En la transformación ShiftRow, las últimas tres columnas del estado se les realizan corrimientos cı́clicos con diferentes offsets. El renglón 1 es desplazado C1 bytes, renglón 2 se desplaza C2 bytes, y el renglón 3 se desplaza C3 bytes. Los offsets de los desplazamientos o corrimientos dependen de la longitud del bloque Nb . Los diferentes valores se especifican en la siguiente tabla: Nb 4 6 8 C1 1 1 1 C2 2 2 3 Un ejemplo de ShiftRow serı́a: 5 C3 3 3 4 00 11 22 33 44 88 CC 55 99 DD 66 AA EE 77 BB F F → 00 44 88 CC 55 99 DD 11 AA EE 22 66 F F 33 77 BB En la transformación MixColumn, las Nb columnas del estado son transformadas por una multiplicación con una matriz fija. b0,i b1,i b2,i b3,i = 2 1 1 3 3 2 1 1 1 3 2 1 1 1 3 2 × a0,i a1,i a2,i a3,i , 0 ≤ i ≤ Nb Las operaciones son ejecutadas en el campo finito binario GF (28 ). La adición es un XOR y la multiplicación es diferente a la multiplicación de enteros. Los coeficientes de la matriz están basados en un código lineal con una distancia máxima entre las palabras de código. Esto asegura que los bytes de cada columna sean mezclados adecuadamente, y esta operación junto con la operación ShiftRow, asegura que después de unas rondas, todos los bits de salida dependan de todos los bits de entrada. El segundo criterio de diseño para los coeficientes era la posibilidad de una implementación eficiente. En la operación de AddRoundKey la llave de ronda es sumada al estado simplemente con un XOR. La longitud de la llave de ronda es igual a la longitud del bloque. Las llaves de ronda son derivadas de la llave de cifrado por medio del key schedule. Este consiste de dos componentes: la expansión de la llave y la selección de la llave de ronda. Los principios básicos son los siguientes: • El número total de los bits de la llave de ronda es igual a la longitud del bloque multiplicada por el número de rondas más 1. (Por ejemplo, para un bloque de longitud de 128 bits y 10 rondas, 128 × (10 + 1) = 1408 bits de la llave de ronda ). • La llave de cifrado es expandida para formar una llave expandida. • Las llaves de ronda son tomadas de la llave de expansión en la siguiente manera: La primera llave de ronda consiste de las primeras Nb palabras, la segunda de las siguientes Nb palabras y asi sucesivamente. En el siguiente pseudocódigo podemos ver el procedimiento de expansión de la llave [2]: 6 KeyExpansion(CipherKey,W){ for(i=0;i < Nk ;i++)W[i]=CipherKey[i]; for(j=Nk ;j< Nb (Nr + 1);j+=Nk ){ W[j]=W[j-Nk ] ⊕ SubByte(Rot(W[j-1])) ⊕ Rcon[j=Nk ]; if(Nk £ 6 ){ for(i=1; i < Nk ;i++) W[i+j]=W[i+j-Nk ] ⊕ W[i+j-1]; } else{ for(i=1; i < 4; i++) W[i+j]=W[i+j-Nk ] ⊕ W[i+j-1]; W[j+4]=W[j+4-Nk ] ⊕ SubByte(W[j+3]); for(i=5; i < Nk ; i++) W[i+j]=W[i+j-Nk ] ⊕ W[i+j-1]; } } } El método criptográfico consiste de la suma inicial de la llave de ronda, seguida de Nr − 1 rondas, y una ronda final, como en el siguiente pseudocódigo: Rijndael(State,CipherKey){ KeyExpansion(CipherKey,ExpandedKey); AddRoundKey(State,ExpandedKey); for(i=1; i < Nr ; i++)Round(State,ExpandedKey + Nbi); FinalRound(State,ExpandedKey+NbNr); } Un diagrama muy útil para entender el cifrado es el siguiente: 7 Para realizar el decifrado, es útil darnos cuenta de que las transformaciones ByteSub, ShiftRow, MixColumn, AddRoundKey son invertibles: 1. El inverso de ByteSub es otra tabla de búsqueda llamada InvByteSub (IBS). 2. El inverso de ShiftRow se obtiene al realizar un corrimiento a la derecha del renglón en vez que a la izquierda, esto lo realizamos en InvShiftRow (ISR). 3. El inverso de MixColumn existe por que la matriz usada en MixColumn es invertible. La matriz de InvMixColumn (IMC) es la matriz. 00001110 00001001 00001101 00001011 00001011 00001110 00001001 00001101 00001101 00001011 00001110 00001001 00001001 00001101 00001011 00001110 4. AddRoundKey(IARK) es su propia inversa. El proceso de decifrado lo podemos ver como: 1. ARK, usando la llave de ronda 10. 2. Nueve rondas de IBS,ISR,IMC,IARK, usando las llaves de ronda de 9 a 1. 3. Una ronda final: IBS,ISR,ARK,usando la llave de ronda 0. Hemos explicado de manera breve el algoritmo, algunas de las siguientes referencias explican de manera más amplia al mismo [1] [2] [3] [4], sobre todo la propuesta de AES [3] por los creadores del mismo es muy completa. 1.2 Campos finitos binarios GF (2m) En la sección anterior explicamos el algoritmo de Rijndael, algunas de las operaciones de éste algoritmo se realizan en el campo finito binario GF (28 ), por lo cual es importante dar una introducción a éstos. Un Campo es un conjunto de elementos con las operaciones de suma y multiplicación. Donde deben de cumplirse ciertas propiedades como: El resultado al realizar alguna operacion con cualquiera dos elementos del campo resultará en un elemento que también está en el campo. Debe de haber un elemento cero (0), tal que cuando se sume a algún elemento del 8 campo resulte el mismo elemento. También debe de haber un elemento idéntico (1) tal que cuando multipliquemos cualquier elemento por éste resulte el mismo elemento. También es un requisito que tenga inversos multiplicativos y aditivos para cualquier número (excepto el cero que no tiene inverso multiplicativo), debe de cumplir con las leyes de conmutatividad, asociatividad y la distributiva. [5]. Ejemplos de campos son: números reales, números complejos, números racionales y los enteros módulo un primo. Por ejemplo, consideremos el siguiente campo con 4 elementos: GF (4) = {0, 1, w, w2 }, con las siguientes leyes: 1. 0 + x = x para toda x 2. x + x = 0 para toda x 3. 1 ∗ x = x para toda x 4. w + 1 = w2 5. Adición y multiplicación son conmutativas, asociativas, y cumplen la ley distributiva x(y + z) = xy + xz para toda x, y, z Luego dado que w3 = w ∗ w2 = w ∗ (1 + w) = w + w2 = w + (1 + w) = 1, observamos que w2 es el inverso multiplicativo de w. De ahı́ que cada elemento no cero de GF (4) tenga un inverso multiplicativo. En general, un Campo es un conjunto que tiene elementos 0 y 1 (con 1 6= 0 ) y satisfacen: 1. Tienen operaciones de multiplicación y adición que satisfacen (1),(3),(5). 2. Cada elemento tiene un inverso aditivo. 3. Cada elemento no cero tiene un inverso multiplicativo. 9 Para cada potencia de un primo pn , hay exactamente un campo finito con pn elementos. El campo con pn elementos es llamado GF (pn ), GF deriva de “Galois field”, el cual quedo para honrar la matemático Frances Evariste Galois (1811-1832). Otra manera de producir el campo GF (4) es considerando Z2 [X] como el conjunto de todos los polinomios cuyos coeficientes son enteros módulo 2. Por ejemplo, 1 + X 3 + X 6 y X están en el conjunto, como también los polinomios constantes 0 y 1. Se puede sumar, restar y multiplicar en este conjunto, siempre y cuando trabajemos con los coeficientes módulo 2. Por ejemplo, (X 3 + X + 1)(X + 1) = X 4 + X 3 + X 2 + 1 Hay que notar que el término 2X desparece módulo 2. Una propiedad importante es que podemos realizar la división con residuo, tal como lo hacemos en los enteros. Por ejemplo la división de X 4 + X 3 + 1 entre X 2 + X + 1, podemos realizar las operaciones y al final queda que: X 4 + X 3 + 1 = (X 2 + 1)(X 2 + X + 1) + X y esto lo podemos escribir como: X 4 + X 3 + 1 ≡ X (mod X 2 + X + 1) Un método general para construir un campo finito con pn elementos donde p es primo y n ≥ 1 es el siguiente: 1. Zp [X] es el conjunto de polinomios con coeficientes modulo p. 2. Escoga P (X) un polinomio irreducible módulo p de grado n 3. Sea GF (pn ) igual a Zp [X] mod P (X). Entonces GF (pn ) es un campo con pn elementos. Para cada n, hay un polinomio irreducible mod p de grado n, asi la construcción produce campos con pn elementos para cada n ≥ 1 [1]. 1.2.1 GF (28 ) Rijndael trabaja mod el polinomio irreducible X 8 + X 4 + X 3 + X + 1. Cada elemento puede ser representado de forma única por: 10 b7 X 7 + b6 X 6 + b5 X 5 + b4 X 4 + b3 X 3 + b2 X 2 + b1 X + b + 0 donde cada bi es 0 o 1. Los 8 bits b7 b6 b5 b4 b3 b2 b1 b0 representan un byte, ası́ la representación de los elementos de GF (28 ) es en bytes de 8-bits. Por ejemplo, el polinomio X 7 +X 6 +X 3 +X +1 se convierte en 11001011. La suma en GF (28 ) es un XOR de los bits: (X 7 + X 6 + X 3 + X + 1) + (X 4 + X 3 + 1) → 11001011 ⊕ 00011001 = 11010010 → X7 + X6 + X4 + X La multiplicación es un poco más complicada y no tiene una interpretación sencilla. Y esto se debe a que estamos trabajando mod el polinomio X 8 + X 4 + X 3 + X + 1, el cual puede ser representado por los 9 bits 100011011. Por ejemplo, realizemos la multiplicación de X 7 + X 6 + X 3 + X + 1 por X: (X 7 + X 6 + X 3 + X + 1)(X) = X 8 + X 7 + X 7 + X 2 + X = (X 7 + X 3 + X 2 + 1) + (X 8 + X 4 + X 3 + X + 1) ≡ X 7 + X 3 + X 2 + 1 (mod X 8 + X 4 + X 3 + X + 1) La misma operación en bits es: 11001011 → 110010110 → 110010110 ⊕ 100011011 = 010001101, En general, podemos multiplicar por X con el siguiente algoritmo: 1. Corrimiento hacia la izquierda y apendizamos un 0 como el último bit. 2. Si el primer bit es 0, pare. 3. Si el primer bit es 1, XOR con 100011011. De lo anterior podemos inferir que las operaciones de suma y multiplicación se pueden implementar eficientemente en GF (28 ). Suma y multiplicación son las principales operaciones en GF (28 ) que vamos a utilizar en la implementación de Rijndael, de ahı́ la importancia de este breve sección, para más información sobre campos finitos revisar las siguientes referencias [6] [5] [1] . 11 Implementación La metodologı́a que vamos a usar para el desarrollo del proyecto, la explicamos a continuacón: 1. Investigación y recolección de información acerca de AES. 2. Comprensión y estudio del algoritmo. 3. Implementación de las operaciones básicas del algoritmo. 4. Implementación del algoritmo. 5. Desarrollo de la aplicación de prueba usando la librerı́a resultante de la implementación. 6. Documentación del proyecto y de la librerı́a. 7. Pruebas de la implementación y de la librerı́a. 2.1 Aspectos de implementación Rijndael va a ser programado implementando primero todas las diferentes transformaciones y luego con éstos modulos se implementará el algoritmo. Ya implementado el algoritmo se desarrollará la aplicación. Con respecto a las pruebas, generalmente la metodologı́a preferida es realizar las pruebas de cada una de las unidades que se van programando y al final una prueba integral con todas las unidades trabajando en conjunto, estas pruebas iniciales generalmente sirven para garantizar que el algoritmo se haya implementado correctamente, luego vienen las pruebas de eficiencia donde principalmente la plataforma a usar para realizar éstas es Linux y las comparaciones se harán tomando la información de [7]. Una de las ideas que también tenemos para la implementación es hacer uso de las directivas del preprocesador de C para poder definir los tamaños tanto del bloque como de la llave en tiempo de compilación, de igual forma lo haremos para los casos, como por ejemplo, para la 12 caja-S, ya sea que deseemos usarla por tabla o por la generación de la misma en tiempo de ejecución. Para el proceso de implementación es importante recalcar que algunos documentos de Rijndael como [2] mecionan que las operaciones de AddRoundKey, ByteSub y RowShift se pueden combinar eficientemente y ejecutadas serialmente por estado. Para la implementación de MixColumn se requiere la multiplicación en el campo finito binario GF (28 ), al igual que en el caso anterior, los autores del algoritmo mencionan que los coeficientes del polinomio irreducible han sido influenciados por consideraciones de implementación. Para la implementación ellos mencionan que el uso de instrucciones condicionales puede causar debilidad con respecto a los ataques de tiempo (timing attaks), y esto se debe a que el tiempo de ejecución de cada una de las operaciones dependen del valor de entrada. Ellos indican que timing attack puede ser contrarestado insertando operaciones NOP adicionales para igualar los tiempos de cada una de las operaciones, pero esto probablemente introducira debilidad con respecto a la análisis de potencias. El uso de una tabla contraresta efectivamente cada uno de los ataques anteriores. También mencionan que las operaciones ByteSub, ShiftRow, y MixColumn pueden ser ejecutadas con 4 tablas y tres operaciones XOR por columna. 2.2 La librerı́a de la implementación La libreria de la implementación estará conformada por los siguientes bloques de procedimientos: • Procedimientos relacionados con los bloques básicos del algoritmo (componentes de transformación). • Procedimiento relacionado con la implementación del algoritmo • Procedimientos de utileria Dentro de las funciones de utileria el proposito es desarrollar funciones que faciliten a los desarrolladores el uso de la librerı́a. 2.3 Implementación del algoritmo La implementación la podemos dividir en fases como se habı́a comentado anteriormente: 13 • Definición de estructuras y constantes. • Implementación de los bloques básicos del algoritmo • Implementación del algoritmo • Implementación de funciones de utilerı́a • Implementación de programas de prueba y aplicaciones Esta sección la dividiremos en subsección que ilustren cada una de estas fases. 2.3.1 Definición de estructuras La definición de las estructuras la podemos ver en el siguiente archivo de encabezado RjndlDefs.h: #ifndef _RJNDLDEFS_H_ #define _RJNDLDEFS_H_ #define RJNDL_IRRED_PLNM 0x011B /* x^8 + x^4 + x^3 + x + 1 */ #ifdef RJNDL_128BLK #define RJNDL_N #endif 128 /*Block length*/ #ifdef RJNDL_192BLK #define RJNDL_N #endif 192 /*Block length*/ #ifdef RJNDL_256BLK #define RJNDL_N #endif 256 /*Block length*/ #define RJNDL_N_b #define RJNDL_N_lb (RJNDL_N/32) (RJNDL_N/8) /*Number of columns*/ /*Number of lineal blocks*/ #ifdef RJNDL_128KEY #define RJNDL_K #endif 128 /*Key length*/ 14 #ifdef RJNDL_192KEY #define RJNDL_K #endif 192 #ifdef RJNDL_256KEY #define RJNDL_K #endif 256 /*Key length*/ #define RJNDL_N_k #define RJNDL_N_lk #define RJNDL_ROWS (RJNDL_K/32) (RJNDL_K/8) 4 /*Number of columns (cipher key)*/ /*Number of lineal key blocks*/ /*Number of rows*/ /*Key length*/ /*GFbits (Type) *An element of GF(2^8) *8-bit (1 byte) representation. */ typedef unsigned char GFBits; struct Rjndl{ GFBits a[RJNDL_ROWS][RJNDL_N_b]; //State GFBits k[RJNDL_ROWS][RJNDL_N_k]; //Cipher key GFBits *ek[RJNDL_ROWS]; //Expanded Key int rounds; //Number of rounds int crntRound; //Current Round int ncek; //Number of columns for expanded Key }; typedef struct Rjndl Rjndl; #endif /* _RJNDLDEFS_H_ */ Para la implementación se definio un nuevo tipo que es GFBits y Rjndl, el primero identifica a un byte el cual nos sirve para representar elementos del campo GF (28 ) y el segundo es una estructura formada por a que es el estado del algoritmo, k corresponde a la llave de cifrado, ek corresponde a la llave de cifrado expandida, también cuenta con la variable rounds que contiene el número de rondas que debe de llevar a cabo el algoritmo en base al tamaño de la llave y del bloque, luego tenemos una variable crntRound que sirve para indicarnos la ronda en la que se encuentra el algoritmo, y una variable ncek que indica el número de columnas que tendrá la llave expandida y que de igual manera esta determinada por el tamaño del 15 bloque y de la llave. En la parte superior del encabezado vemos algunas definiciones de constantes como: • RJNDL IRRED PLNM Polinomio irreducible de Rijndael • RJNDL N Longitud del bloque • RJNDL K La longitud de la llave • RJNDL N b Número de columnas de la matriz del bloque. • RJNDL N lb Número de elementos del bloque si es un arreglo lineal. • RJNDL N k Número de columnas de la matriz de la llave. • RJNDL N lk Número de elementos del bloque si es un arreglo lineal. Como también podemos observar éstas están encerradas entre directivas del compilador, de tal manera que estas variables van a ser definidas en tiempo de compilación, nuestra implementación es flexible en el sentido que podemos manejar bloques y llaves de tamaños 128,192,256. Unicamente tenemos que indicar el tamaño que deseemos en el archivo Makefile que está asociado a este proyecto. 2.3.2 Implementación de los bloques básicos Dentro de los bloques básicos podemos identificar a todas aquellas funciones que permitan implementar los bloques básicos del algoritmo, como por ejemplo, funciones que permitan realizar la suma, multiplicación en el campo finito binario GF (28 ). En el siguiente archivo de encabezado RjndlBase.h mostramos algunas: /*GF Operations *All the operations are done in GF(2^8) */ /*rjndlGFAdd *GF(2^8) addition *r=a+b */ 16 GFBits rjndlGFAdd(GFBits a, GFBits b); /*rjndlGFLogMul *GF(2^8) multiplication *using logarithms and power tables *r = a*b */ GFBits rjndlGFLogMul(GFBits a, GFBits b); /*rjndlGFMulInv *GF(2^8) multiplicative inverse *using logarithms and power tables *r = a^{-1} */ GFBits rjndlGFMulInv(GFBits a); /*rjndlGFMulby2 *r=a*X */ GFBits rjndlGFMulby2(GFBits a); /*rjndlGFMulby3 *r=a*(X+1) *Since 3*x=(2^1)*x=(2*x)^(1*x) *multiplication by 3 can be done by multiplying with 2 *then XORIng the argument */ GFBits rjndlGFMulby3(GFBits a); /*rjndlGFMulby9 */ GFBits rjndlGFMulby9(GFBits a); /*rjndlGFMulbyB */ GFBits rjndlGFMulbyB(GFBits a); /*rjndlGFMulbyD */ GFBits rjndlGFMulbyD(GFBits a); /*rjndlGFMulbyE */ GFBits rjndlGFMulbyE(GFBits a); 17 /*rjndlGFHighBits *Return the 4 high bits of a */ GFBits rjndlGFHighBits(GFBits a); /*rjndlGFLowBits *Return the 4 low bits of a */ GFBits rjndlGFLowBits (GFBits a); /*rjndlShiftOffsets *Return the shift offsets for different block *lengths */ GFBits rjndlShiftOffsets(int row); /*rjndlNmbrOfRnds *Number of rounds * *The block and key length are taken from the *RJNDL_N_b and RJNDL_N_k */ GFBits rjndlNmbrOfRnds(void); En este encabezado encontramos funciones como rjndlAdd que permite realizar la suma en GF (28 ) y algunas otras como multiplicación en GF (28 ), es relevante tomar en cuenta que si revisamos el código de la implementación también encontraremos directivas del compilador, esto es útil ya que algunas como la multiplicación se implementarón de forma algorı́tmica y otra haciendo acceso a las tablas de logaritmos. En tiempo de compilación se decide si se usa la multiplicación haciendo acceso a tablas o la algorı́tmica, la directiva es: RJNDL OPTMUL y debe ser modificada en el Makefile. Esto nos fue útil para evaluar los tiempos de la implementación variando algunos paramétros como la multiplicación. Una vez que contamos con la implementación de cada una de las funciones anteriores se implementaron los bloques del algoritmo, a continuación mostramos su archivo de encabezado: /*rjndlByteSub *ByteSub transformation */ 18 GFBits rjndlByteSub(Rjndl *rjn); /*rjndlMByteSub *Long Byte sub calculation using sMtrx instead of lookup table sbox */ GFBits rjndlMByteSub(GFBits p); /*rjndlIMByteSub *Long Inv Byte sub calculation using isMtrx instead of lookup table isbox */ GFBits rjndlMByteSub(GFBits p); /*rjndlInvByteSub *ByteSub transformation */ GFBits rjndlInvByteSub(Rjndl *rjn); /*rjndlShiftRow *ByteSub transformation */ GFBits rjndlShiftRow(Rjndl *rjn); /*rjndlInvShiftRow *ByteSub transformation */ GFBits rjndlInvShiftRow(Rjndl *rjn); /*rjndlMixColumn *MixColumn transformation */ GFBits rjndlMixColumn(Rjndl *rjn); /*rjndlMixColumn *InvMixColumn transformation */ GFBits rjndlInvMixColumn(Rjndl *rjn); /*AddRoundKey *RoundKeyAddition transformation */ GFBits rjndlAddRoundKey(Rjndl *rjn); /*InvAddRoundKey *Inverse Round Key */ 19 GFBits rjndlInvAddRoundKey(Rjndl *rjn); /*rjndlKeyExpansion *KeyExpansion */ GFBits rjndlKeyExpansion(Rjndl *rjn); /*rjndlRound *A Rijndael Round */ GFBits rjndlRound(Rjndl *rjn); /*rjndlInvRound *A Rijndael Inverse Round */ GFBits rjndlInvRound(Rjndl *rjn); /*rjndlFinalRound *A Rijndael Final Round */ GFBits rjndlFinalRound( Rjndl *rjn); /*rjndlInvFinalRound *A Rijndael Inverse Final Round */ GFBits rjndlInvFinalRound( Rjndl *rjn); /*rjndlRijndael *Rijndael Encryption algorithm */ GFBits rjndlRijndael(Rjndl *rjn); En el archivo de encabezado anterior podemos identificar la implementación de los bloques para el proceso de cifrado como: • rjndlKeyExpansion(Rjndl *rjn); • rjndlAddRoundKey(Rjndl *rjn); • rjndlByteSub(Rjndl *rjn); • rjndlShiftRow(Rjndl *rjn); 20 • rjndlMixColumn(Rjndl *rjn); Todas reciben como parámetro una varible de tipo Rjndl y la cual como se explico anteriormente sirve para guardar el estado del algoritmo. Luego para el proceso de descifrado tenemos: • rjndlKeyExpansion(Rjndl *rjn); • rjndlInvAddRoundKey(Rjndl *rjn); • rjndlInvByteSub(Rjndl *rjn); • rjndlInvShiftRow(Rjndl *rjn); • rjndlInvMixColumn(Rjndl *rjn); Luego a partir de ellas se formarón las siguientes : • rjndlRound(Rjndl *rjn); • rjndlFinalRound( Rjndl *rjn); • rjndlInvRound(Rjndl *rjn); • rjndlInvFinalRoundRjndl *rjn); Las cuales como vamos a ver en la sección que sigue sirven para la implementación del algoritmo. En esta parte también dentro del código se definieron directivas del compilador que sirve para decidir si el proceso de ByteSub lo hacemos usando las tablas o si lo hacemos multiplicando por la matriz y sumando el vector. Para habilitar el proceso de ByteSub usando tablas lo hacemos indicando la directiva RJNDL OPTBS en el Makefile y recordemos que esto lo definimos en el momento de la compilación. 2.3.3 Implementación del algoritmo La definición de las funciones del algoritmo los encontramos en el siguiente archivo de encabezado Rjndl.h: 21 /*rjndlInit *Initialization *The initialization is based upon the constants */ GFBits rjndlInit(Rjndl *rjn); /* rjndlFree * Free the memory that was allocated for the * expanded key mainly. */ GFBits rjndlFree(Rjndl *rjn); /*rjndlInitWithKeyAndKeyExpansion *Initialization *Set up the key and will call the function key expansion. */ GFBits rjndlInitWithKeyAndKeyExpansion(Rjndl *rjn, GFBits cipherKey[RJNDL_N_lk]); /*rjndlRijndael *Rijndael Encryption algorithm */ GFBits rjndlRijndael(Rjndl *rjn); /*rjndlInvRijndael *Rijndael Decryption algorithm */ GFBits rjndlInvRijndael(Rjndl *rjn); Una vez que se implementaron todas las funciones básicas, el código del algoritmo es muy sencillo como se muestra a continuación: GFBits rjndlRijndael(Rjndl *rjn) { int i=0; rjn->crntRound=0; rjndlAddRoundKey(rjn); #ifdef jdebug rjndlPrintState(rjn->a," %x ","InitialRound->AddRoundKey\n-----\n"); #endif 22 for(i=1; i < rjn->rounds ; i++){ rjndlRound(rjn); } rjndlFinalRound(rjn); return 0; } GFBits rjndlInvRijndael(Rjndl *rjn) { int i; rjn->crntRound=rjn->rounds; rjndlInvFinalRound(rjn); #ifdef jdebug rjndlPrintState(rjn->a," %x ","InvInitialRound->InvFinal\n-----\n"); #endif for(i= rjn->rounds -1 ; i > 0; i--){ rjndlInvRound(rjn); } rjndlInvAddRoundKey(rjn); return 0; } En el código anterior como se puede observar se definio una directiva del compilador con el nombre jdebug, la cual es útil cuando deseamos que todos los pasos del algoritmo se muestren el pantalla, sobre todo la matriz de estado, al igual que en las directivas anteriores se define en tiempo de compilación con la opción jdebug en el archivo Makefile. El código del algoritmo podemos observar el uso de las variables de la estructura Rjndl como rjn->crntRound De igual manera hay que resaltar las funciones de inicialización , ya que son necesarias para el buen funcionamiento del algoritmo. 2.3.4 Implementación de funciones de utilerı́a Dentro de las funciones de utilerı́a se construyeron las siguientes: /*rjndlSetPlainBlock *Set the plain text block * *Note: *The first element pblk[0] will be in the rjn->a[0][0] *The second element pblk[1] will be in the rjn->a[1][0] 23 *... *The last element pblk[RJNDL_N_lb-1] will be in the rjn->[RJNDL_N_b-1][RJNDL_N_b-1] */ GFBits rjndlSetPlainBlock(Rjndl *rjn,GFBits pblk[RJNDL_N_lb]); /* rjndlSetKeyAndKeyExpansion * Set up the key and call Key Expansion */ GFBits rjndlSetKeyAndKeyExpansion(Rjndl *rjn, GFBits cklk[RJNDL_N_lk]); /* rjndlGetStateArray * Returns the state in form of an array * You must deallocate the array with free */ GFBits * rjndlGetStateArray(Rjndl *rjn ); /*rjndlEncFile *Encripts a file. *Return -1 if something is wrong */ /* rjndlGetStateIntoArray * Returns the state in form of an array * You must deallocate the array with free */ GFBits * rjndlGetStateIntoArray(Rjndl *rjn, GFBits rsl[RJNDL_N_lb] ); int rjndlEncFile(char *source, char *dest, GFBits ck[RJNDL_N_lk]); /*rjndlDecFile *Decripts a file. *Return -1 if something is wrong */ int rjndlDecFile(char *source, char *dest, GFBits ck[RJNDL_N_lk]); • rjndlSetPlainBlock(Rjndl *rjn,GFBits pblk[RJNDL N lb]); Sirve para definir el bloque inicial para el proceso de cifrado o descifrado • rjndlSetKeyAndKeyExpansion(Rjndl *rjn, GFBits cklk[RJNDL N lk]); Permite definir o cambiar la llave de cifrado o descifrado, además realiza la invocación al proceso que expande la llave. • rjndlGetStateArray(Rjndl *rjn ); Regresa el apuntador a un arreglo que contiene el estado en forma de arreglo lineal, es necesario liberar el espacio del apuntador una vez que ya no se necesita. 24 • rjndlGetStateIntoArray(Rjndl *rjn, GFBits rsl[RJNDL N lb] ); Regresa la información del estado en el arreglo que es pasado como parámetro, recordemos que el arreglo es lineal. • rjndlEncFile(char *source, char *dest, GFBits ck[RJNDL N lk]); Permite encriptar un archivo usando Rijndael, únicamente es necesario pasarle la ruta absoluta del archivo origen, el destino y la llave de cifrado • rjndlDecFile(char *source, char *dest, GFBits ck[RJNDL N lk]);Permite desencriptar un archivo usando Rijndael, únicamente es necesario pasarle la ruta absoluta del archivo cifrado, el destino y la llave de descifrado 2.3.5 Implementación de programas de prueba y aplicaciones Un programa de prueba muy sencillo que ilustra la prueba del API es el siguiente: #include "../rjndl/Rjndl.h" struct nist128{ GFBits keys[RJNDL_N_lk]; }; struct nist128 ck[]= { {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}, }; int main(int argc, char *argv[]) { Rjndl rjn,rjn1; int i; GFBits *cphrtxt; GFBits pb[RJNDL_N_lb]= {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}; rjndlInit(&rjn); rjndlSetKeyAndKeyExpansion(&rjn,ck[0].keys); rjndlSetPlainBlock(&rjn,pb); rjndlPrintInfo(&rjn); rjndlPrintState(rjn.a," %x ","plain text\n----\n"); rjndlPrintKey(rjn.k," %x ","key \n----\n"); rjndlRijndael(&rjn); rjndlPrintState(rjn.a," %x ","cipher text\n----\n"); cphrtxt=rjndlGetStateArray(&rjn); 25 rjndlInit(&rjn1); rjndlSetKeyAndKeyExpansion(&rjn1,ck[0].keys); rjndlSetPlainBlock(&rjn1,cphrtxt); rjndlPrintInfo(&rjn1); rjndlPrintState(rjn1.a," %x ","cipher text\n----\n"); rjndlInvRijndael(&rjn1); rjndlPrintState(rjn1.a," %x ","plain text\n----\n"); free(cphrtxt); rjndlFree(&rjn); rjndlFree(&rjn1); return 0; } La primera lı́nea incluye el encabezado Rjndl.h el cual contiene todas las definiciones y es necesario incluir en cada programa que haga uso de la librerı́a. Posteriormente definimos la llave de cifrado la cual va estar representada por el elemento ck[0].keys, luego en la función main se realizan las siguientes actividades para realizar el cifrado: • Definir variables del tipo Rjndl rjn, rjn1 • Definir el bloque a cifrar variable pb • Inicializar la varibale rjn • Asignar la llave y expandirla • Definir el bloque de cifrado • Imprimir información • Invocar al algoritmo de Rijndael • Mandar el bloque cifrado a un arreglo lineal y las siguientes para realizar el descifrado: • Inicializar la variable rjn1 26 • Asignar la llave y expandirla • Imprimir información • Invocar al algoritmo de Rijndael para cifrar • Imprimir el texto descifrado Las últimas lineas son necesarias para evitar problemas con la memoria. Para compilar este programa lo hacemos con: make nist128iv una vez compilado lo ejecutamos con: nist128iv y la salida generada es la siguiente: Rijndael (Jabg Implementation 2002) Block Length: 128 Key Lenght: 128 Nb: 4 , Nlb: 16 Nk: 4 , Nlk: 16 Nr: 10 Number of columns for expanded key 44 Current round 0 -------------------------------------plain text ---0 4 8 c 1 5 9 d 2 6 a e 3 7 b f ----key ---0 4 8 c 1 5 9 d 2 6 a e 27 3 7 b f ----cipher text ---a 41 f1 c6 94 6e c3 53 b f0 94 ea b5 45 58 5a ----Rijndael (Jabg Implementation 2002) Block Length: 128 Key Lenght: 128 Nb: 4 , Nlb: 16 Nk: 4 , Nlk: 16 Nr: 10 Number of columns for expanded key 44 Current round 0 -------------------------------------cipher text ---a 41 f1 c6 94 6e c3 53 b f0 94 ea b5 45 58 5a ----plain text ---0 4 8 c 1 5 9 d 2 6 a e 3 7 b f ----- Los datos de prueba de este programa corresponde a los vectores de prueba definidos por NIST (ecb iv.txt). Otro programa aun más sencillo que el anterior es el que permite cifrar archivos, el código es el siguiente: #include "../rjndl/Rjndl.h" #include <stdio.h> 28 int main(int argc, char *argv[]) { char *src="gm.jpg"; char *dst="gmc.jpg"; GFBits key[RJNDL_N_lk]= {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F}; if( rjndlEncFile(src,dst,key) == -1) printf("Error !\n"); return 0; } En donde prácticamente los pasos para cifrar un archivo usando AES es: • Definir la llave de cifrado • Invocar a la función rjndlEncFile con los nombres de los archivos y con la llave cifrado. 29 Pruebas 3.1 Pruebas La etapa de pruebas es muy relevante y entre los objetivos primordiales tenemos: • Probar la implementación con los vectores de NIST. • Evaluar la eficiencia de la implementación. • Pruebas cifrando archivos A continuación explicamos cada una de estas pruebas. 3.1.1 Pruebas con vectores de NIST Para la implementación de las pruebas con los vectores de NIST se realizaron programas para probar estos vectores, el archivo de NIST que se uso como referencia fue ecb vk.txt. Las pruebas se realizaron para llaves variables de 128,192,y 256, los programas son nist 128.c nist 192.c nist 256. El programa nist 128 mostramos su código a continuación: #include "../rjndl/Rjndl.h" struct nist128{ GFBits keys[RJNDL_N_lk]; }; 30 struct nist128 ck[]= { {0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} }; int main(int argc, char *argv[]) { Rjndl rjn,rjn1; int i; GFBits pb[RJNDL_N_lb]= {0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00}; rjndlInit(&rjn); for(i=0 ; i < 10; i++){ rjndlSetKeyAndKeyExpansion(&rjn,ck[i].keys); rjndlSetPlainBlock(&rjn,pb); rjndlPrintInfo(&rjn); rjndlPrintState(rjn.a," %x ","plain text\n----\n"); rjndlPrintKey(rjn.k," %x ","key \n----\n"); rjndlRijndael(&rjn); rjndlPrintState(rjn.a," %x ","cipher text\n----\n"); } rjndlFree(&rjn); return 0; } En este programa mostramos los primeros vectores de NIST. Después de compilar el programa y ejecutarlo se obtiene la siguiente salida: 31 Rijndael (Jabg Implementation 2002) Block Length: 128 Key Lenght: 128 Nb: 4 , Nlb: 16 Nk: 4 , Nlk: 16 Nr: 10 Number of columns for expanded key 44 Current round 0 -------------------------------------plain text ---0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ----key ---80 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ----cipher text ---e c6 45 14 dd 21 5b 18 33 e5 d8 be d3 46 ba c8 ----- En donde el texto cifrado corresponde a lo especificado por NIST: I=1 KEY=80000000000000000000000000000000 CT=0EDD33D3C621E546455BD8BA1418BEC8 De la misma manera se realizo para las llaves de 128,192,256, resultando las pruebas exitosas con los vectores probados. 32 3.1.2 Pruebas de eficiencia Para las pruebas de eficiencia se crearon los programas: • tm128.c • tm192.c • tm256.c El programa tm128.c se muestra a continuación: #include "../rjndl/Rjndl.h" #include <time.h> struct nist128{ GFBits keys[RJNDL_N_lk]; }; struct nist128 ck[]= { {0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, }; int main(int argc, char *argv[]) { Rjndl rjn,rjn1; int i; time_t t1,t2; GFBits pb[RJNDL_N_lb]= {0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00 ,0x00,0x00,0x00,0x00}; rjndlInit(&rjn); rjndlSetKeyAndKeyExpansion(&rjn,ck[0].keys); time(&t1); for(i=0 ; i < 8192 ; i++){ rjndlSetPlainBlock(&rjn,pb); //rjndlPrintInfo(&rjn); //rjndlPrintState(rjn.a," %x ","plain text\n----\n"); 33 //rjndlPrintKey(rjn.k," %x ","key \n----\n"); rjndlRijndael(&rjn); //rjndlPrintState(rjn.a," %x ","cipher text\n----\n"); } time(&t2); t2-=t1; printf("El tiempo es %02i:%02i\n",(int)t2/60,(int)t2%60); rjndlFree(&rjn); return 0; } Las pruebas se realizaron con los siguientes números de bloques: • 8192 (8192 * 128 bits )= 1048576 bits • 16384 (16384 * 128 bits)= 2097512 bits • 32768 (32768 * 128 bits)= 4194304 bits • 65536 (65536 * 128 bits)= 8388608 bits Las gráficas se muestran a continuación: 34 35 3.1.3 Pruebas cifrando archivos Para las pruebas cifrando archivos se utilizaron los programas encF.c y decF.c. El primer programa se encarga de cifrar una imagen gm.jpg y la deja en el archivo gmc.jpg, el segundo programa se encarga de tomar el archivo cifrado y descifrarlo, el archivo descifrado lo deja en gmo.jpg Después de realizar el cifrado se comprobó que los archivos gm.jpg y gmo.jpg son iguales, a continuación se muestra los tamaños de cada uno de los archivos: -rw-r--r--rw-r--r--rw-r--r-- 1 jbriones users 1 jbriones users 1 jbriones users 103521 Aug 103504 Aug 103504 Aug 36 6 6 6 2002 gmc.jpg 2002 gm.jpg 2002 gmo.jpg Resultados Los resultados obtenidos son los siguientes: • Se comprendio, se estudio y se implemento el algoritmo de Rijndael. • Se cuenta con una librerı́a flexible que permite usar Rijndael con llaves y bloques independientes de 128,192, y 256 • Se cuenta con una librerı́a a la cual se le pueden agregar más funciones y algún método de intercambio de llaves. • Se cuenta con una implementación que cumple con los vectores de prueba proporcionados por NIST. 37 Bibliografı́a [1] Lawrence C. Washington. Introduction to Cryptography with Coding Theory. PrenticeHall, 2002. [2] Joan Daemen and Vincent Rijmen. Rijndael: The advanced encryption standard. Dr. Dobb’s Journal, pages 137–139, March 2001. [3] AES proposal: Rijndael. http://www.esat.kuleuven.ac.be/~rijmen/rijndael. [4] Michael Welschenbach. Cryptography in C and C++. Apress, 2001. [5] Finite field arithmetic. http://www.anujseth.com/crypto/ffield.html. [6] Introduction to finite fields. http://www-math.cudenver.edu/~rwcherowi/courses/finflds.html. [7] AES performance table for various plataform. http://www.tml.hut.fi/~helger/aes/table.html. 38