Implementación del Advanced Encryption Standard Rijndael en C

Anuncio
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
Descargar