Saltar al contenido
El bit perdido
Newsletter

Lección 9 de 13

El cuerpo de 256 elementos: la aritmética de los bytes en AES

Cómo sumar, multiplicar y dividir bytes sin salirse de 8 bits: el cuerpo finito de 256 elementos que usa AES, con xtime, el inverso y un ejemplo resuelto.

Al terminar sabrás:

  • Interpretar un byte como un polinomio de grado como máximo 7 con coeficientes 0 y 1
  • Sumar bytes con XOR y multiplicarlos módulo x⁸+x⁴+x³+x+1 con xtime
  • Explicar por qué todo byte no nulo tiene inverso
  • Comprobar un inverso multiplicando

Si sabes qué es un número en binario y sumar módulo 2 (sin llevadas), puedes seguirlo. Lo demás (hexadecimal, XOR, permutaciones, polinomios módulo 2 y cuerpos finitos) se explica sobre la marcha.

Hasta la lección 8 hemos seguido a DES: una red de Feistel de 16 rondas cuya clave de 56 bits se quedó corta. Empezamos ahora la segunda mitad del relato, la de su sucesor, AES. Antes de ver cómo es AES por dentro, hay que aprender un idioma que DES no necesitaba: AES no trata los bytes como simples cadenas de 8 bits, sino como números que se pueden sumar, multiplicar y dividir. En esta lección aprendes esa aritmética. Es la de un cuerpo de 256 elementos, el mismo que usan los códigos Reed-Solomon de los códigos QR, que quizá conozcas del curso de códigos. Eso sí, los QR lo construyen con otro polinomio (x8+x4+x3+x2+1x^8+x^4+x^3+x^2+1), así que allí las tablas de multiplicar de los bytes son distintas.

La idea

Un byte tiene 256 valores posibles. Queremos sumar dos bytes y obtener otro byte, multiplicar dos bytes y obtener otro byte, y poder dividir entre cualquier byte distinto de cero, siempre sin salirnos de 8 bits.

Con los enteros usuales no sale. Si sumas 200+100200 + 100 te pasas de 255. Si haces la aritmética «del reloj» módulo 256, tampoco: 2⋅128=256≡02 \cdot 128 = 256 \equiv 0, y un producto de dos números no nulos que da cero rompe la división (el 22 no tendría inverso). Lo que necesitamos es un cuerpo con 256 elementos. Existe porque 256=28256 = 2^8 es potencia de un primo, y se construye igual que F4\mathbb{F}_4 y F8\mathbb{F}_8 en la lección sobre cuerpos finitos del curso de códigos: con polinomios y un polinomio irreducible.

Repaso Si no has hecho el curso de códigos

Un cuerpo es un conjunto donde puedes sumar, restar, multiplicar y dividir entre todo lo que no sea cero, con las reglas de siempre. Los racionales y los reales lo son. Existen cuerpos con un número finito de elementos, y su tamaño es siempre una potencia de un primo. El de 2 elementos son los bits {0,1}\{0,1\}, con 1+1=01+1=0. Los de 2m2^m elementos se construyen con polinomios de coeficientes 00 y 11, tomando restos al dividir entre un polinomio irreducible (uno que no se puede escribir como producto de polinomios de menor grado), igual que Zp\mathbb{Z}_p toma restos al dividir entre un primo pp.

Un byte es un polinomio

Un byte b7b6…b1b0b_7b_6\dots b_1b_0 se identifica con el polinomio

b7x7+b6x6+⋯+b1x+b0,bi∈{0,1}.b_7x^7 + b_6x^6 + \dots + b_1x + b_0, \qquad b_i \in \{0,1\}.

Hay 28=2562^8 = 256 polinomios de grado como máximo 7 con coeficientes 00 y 11, uno por byte. Por ejemplo, 57\texttt{57} en binario es 0101011101010111, que es el polinomio x6+x4+x2+x+1x^6+x^4+x^2+x+1. Y 83\texttt{83} es 1000001110000011, es decir, x7+x+1x^7+x+1. Recuerda que escribimos los bytes en hexadecimal, así: 57\texttt{57}, 83\texttt{83}. A partir de aquí escribimos el hexadecimal en minúsculas, como hace FIPS 197: d4\texttt{d4} y D4\texttt{D4} son el mismo byte. El byte 01\texttt{01} es el polinomio 11 y el byte 02\texttt{02} es el polinomio xx.

Sumar es hacer XOR

Los coeficientes se suman módulo 2. Sumar dos polinomios es sumar coeficiente a coeficiente, sin llevadas: eso es exactamente el XOR bit a bit. Así que

57+83=57⊕83=d4.\texttt{57} + \texttt{83} = \texttt{57} \oplus \texttt{83} = \texttt{d4}.

Compruébalo en binario: 01010111⊕10000011=1101010001010111 \oplus 10000011 = 11010100, que es d4\texttt{d4}. Como 1+1=01+1=0, cada byte es su propio opuesto, así que restar es lo mismo que sumar. Esa es la operación más barata de todas, y AES la usa continuamente (la lección siguiente la llama AddRoundKey).

Multiplicar: módulo un polinomio irreducible

Multiplicar dos polinomios de grado hasta 7 da uno de grado hasta 14, que ya no cabe en un byte. Se arregla tomando el resto al dividir entre un polinomio irreducible de grado 8. AES usa

m(x)=x8+x4+x3+x+1,m(x) = x^8 + x^4 + x^3 + x + 1,

es decir, 100011011100011011 en binario, o 11b\texttt{11b} en hexadecimal. Que sea irreducible es lo que hace que sea un cuerpo (más abajo vemos por qué importa). El cuerpo resultante es

F28=F2[x]/(x8+x4+x3+x+1).\mathbb{F}_{2^8} = \mathbb{F}_2[x]/(x^8+x^4+x^3+x+1).

En la práctica la regla de reducción es una sola: en el cuerpo, m(x)=0m(x) = 0, o sea

x8=x4+x3+x+1=1b.x^8 = x^4 + x^3 + x + 1 = \texttt{1b}.

Cada vez que aparezca un x8x^8, lo cambias por 1b\texttt{1b} (esto es un XOR con 0001101100011011).

xtime: multiplicar por xx

Multiplicar por xx desplaza todos los coeficientes un lugar a la izquierda. Si el byte no tiene el bit 7 activo, el resultado cabe en 8 bits y ya está. Si tiene el bit 7 activo, aparece un x8x^8, que se sustituye por 1b\texttt{1b}. Esta operación se llama xtime:

  • se desplaza el byte un bit a la izquierda (descartando el bit que sale);
  • si el bit que salió era 1, se hace XOR con 1b\texttt{1b}.

Multiplicar por 02\texttt{02} es xtime. Y como cualquier byte es una suma de potencias de xx, toda multiplicación se reduce a xtime repetidos y XOR. Para multiplicar a⋅ba \cdot b: escribe bb como suma de potencias de xx (sus bits), calcula a, xa, x2a,…a, \ xa, \ x^2a, \dots con xtime y suma (XOR) los que correspondan.

El inverso

Como m(x)m(x) es irreducible, todo byte distinto de 00\texttt{00} tiene un inverso: otro byte con el que el producto es 01\texttt{01}. La razón de una línea: si un byte a≠0a \ne 0 y un byte b≠0b \ne 0 dieran producto cero, m(x)m(x) dividiría a a(x)b(x)a(x)b(x), y un polinomio irreducible dividiendo a un producto divide a un factor, imposible porque aa y bb tienen grado menor que 8. Sin productos nulos entre no nulos, multiplicar por aa es una permutación de los 255 bytes no nulos, y alguno sale 01\texttt{01}.

Si el polinomio no fuera irreducible, se rompería. Lo veremos en el ejercicio 3.

No hay una fórmula sencilla para calcular el inverso a mano. Se puede hacer con el algoritmo de Euclides para polinomios, o por tanteo, o con una tabla de 256 entradas. Aquí nos basta con comprobar un inverso dado, multiplicando. En la lección 11 verás que la operación SubBytes de AES calcula el inverso de cada byte (con 00↦00\texttt{00} \mapsto \texttt{00}, porque el cero no tiene inverso) y le aplica después una transformación sencilla.

Ejemplo resuelto

Producto 57⋅83\texttt{57} \cdot \texttt{83}. Primero, la lista de los xtime sucesivos de 57\texttt{57} (es decir, 57⋅xj\texttt{57}\cdot x^j):

jj01234567
57⋅xj\texttt{57}\cdot x^j57\texttt{57}ae\texttt{ae}47\texttt{47}8e\texttt{8e}07\texttt{07}0e\texttt{0e}1c\texttt{1c}38\texttt{38}

Veamos los pasos que tienen reducción. De 57=01010111\texttt{57} = 01010111 a ae=10101110\texttt{ae} = 10101110: el bit 7 era 0, solo desplazamos. De ae=10101110\texttt{ae} = 10101110 a 47\texttt{47}: desplazamos y queda 01011100=5c01011100 = \texttt{5c} con un bit que salió (un 1), así que 5c⊕1b=47\texttt{5c} \oplus \texttt{1b} = \texttt{47}. De 47\texttt{47} a 8e\texttt{8e}: solo desplazar. De 8e=10001110\texttt{8e} = 10001110 a 07\texttt{07}: desplazamos y queda 00011100=1c00011100 = \texttt{1c}, salió un 1, y 1c⊕1b=07\texttt{1c} \oplus \texttt{1b} = \texttt{07}. Los tres siguientes (0e\texttt{0e}, 1c\texttt{1c}, 38\texttt{38}) son solo desplazamientos.

Ahora 83=10000011=x7+x+1\texttt{83} = 10000011 = x^7 + x + 1, así que

57⋅83=57⋅x7⊕57⋅x⊕57⋅1=38⊕ae⊕57.\texttt{57}\cdot\texttt{83} = \texttt{57}\cdot x^7 \oplus \texttt{57}\cdot x \oplus \texttt{57}\cdot 1 = \texttt{38} \oplus \texttt{ae} \oplus \texttt{57}.

Cuenta: 38⊕ae=96\texttt{38} \oplus \texttt{ae} = \texttt{96} y 96⊕57=c1\texttt{96} \oplus \texttt{57} = \texttt{c1}. Resultado:

57⋅83=c1.\texttt{57}\cdot\texttt{83} = \texttt{c1}.

Es el ejemplo que da el propio estándar FIPS 197. Con la misma tabla, 13=x4+x+1\texttt{13} = x^4 + x + 1 da 57⋅13=07⊕ae⊕57=fe\texttt{57}\cdot\texttt{13} = \texttt{07} \oplus \texttt{ae} \oplus \texttt{57} = \texttt{fe}.

Un inverso. Afirmamos que 53−1=ca\texttt{53}^{-1} = \texttt{ca}. Lo comprobamos multiplicando. Primero los xtime de ca\texttt{ca}:

jj0123456
ca⋅xj\texttt{ca}\cdot x^jca\texttt{ca}8f\texttt{8f}05\texttt{05}0a\texttt{0a}14\texttt{14}28\texttt{28}50\texttt{50}

Y 53=01010011=x6+x4+x+1\texttt{53} = 01010011 = x^6 + x^4 + x + 1. Entonces

53⋅ca=50⊕14⊕8f⊕ca.\texttt{53}\cdot\texttt{ca} = \texttt{50} \oplus \texttt{14} \oplus \texttt{8f} \oplus \texttt{ca}.

Agrupando, 50⊕14=44\texttt{50} \oplus \texttt{14} = \texttt{44}, 8f⊕ca=45\texttt{8f} \oplus \texttt{ca} = \texttt{45} y 44⊕45=01\texttt{44} \oplus \texttt{45} = \texttt{01}. Efectivamente, el producto es 01\texttt{01}: ca\texttt{ca} es el inverso de 53\texttt{53}, y se puede dividir por 53\texttt{53} multiplicando por ca\texttt{ca}.

Ejercicios

Ejercicio 1

Calcula b3⊕5e\texttt{b3} \oplus \texttt{5e} y escribe el resultado como polinomio. Después calcula 02⋅b3\texttt{02}\cdot\texttt{b3}, 02⋅80\texttt{02}\cdot\texttt{80} y 02⋅c7\texttt{02}\cdot\texttt{c7}.

Ver la solución

En binario, b3=10110011\texttt{b3} = 10110011 y 5e=01011110\texttt{5e} = 01011110. Su XOR es 11101101=ed11101101 = \texttt{ed}, que es el polinomio x7+x6+x5+x3+x2+1x^7+x^6+x^5+x^3+x^2+1.

Para 02⋅b3\texttt{02}\cdot\texttt{b3}: b3=10110011\texttt{b3} = 10110011 tiene el bit 7 activo, así que desplazamos (01100110=6601100110 = \texttt{66}) y hacemos XOR con 1b\texttt{1b}: 66⊕1b=7d\texttt{66}\oplus\texttt{1b} = \texttt{7d}.

Para 02⋅80\texttt{02}\cdot\texttt{80}: 80=x7\texttt{80} = x^7, así que x⋅x7=x8=1bx\cdot x^7 = x^8 = \texttt{1b}. Por el procedimiento: desplazar da 00\texttt{00}, y 00⊕1b=1b\texttt{00}\oplus\texttt{1b} = \texttt{1b}.

Para 02⋅c7\texttt{02}\cdot\texttt{c7}: c7=11000111\texttt{c7} = 11000111, bit 7 activo; desplazado es 10001110=8e10001110 = \texttt{8e}, y 8e⊕1b=95\texttt{8e}\oplus\texttt{1b} = \texttt{95}.

Ejercicio 2

Calcula 03⋅7a\texttt{03}\cdot\texttt{7a} (nota que 03=x+1\texttt{03} = x+1). Después comprueba que 02⋅8d=01\texttt{02}\cdot\texttt{8d} = \texttt{01}, es decir, que 8d\texttt{8d} es el inverso de 02\texttt{02}.

Ver la solución

Como 03=x+1\texttt{03} = x + 1, tenemos 03⋅a=(x⋅a)⊕a\texttt{03}\cdot a = (x\cdot a) \oplus a. Para a=7a=01111010a = \texttt{7a} = 01111010: el bit 7 es 0, así que 02⋅7a\texttt{02}\cdot\texttt{7a} es solo el desplazamiento, 11110100=f411110100 = \texttt{f4}. Entonces 03⋅7a=f4⊕7a=8e\texttt{03}\cdot\texttt{7a} = \texttt{f4}\oplus\texttt{7a} = \texttt{8e}.

Para el inverso: 8d=10001101\texttt{8d} = 10001101 tiene el bit 7 activo. Desplazado es 00011010=1a00011010 = \texttt{1a}, y 1a⊕1b=01\texttt{1a}\oplus\texttt{1b} = \texttt{01}. Luego 02⋅8d=01\texttt{02}\cdot\texttt{8d} = \texttt{01} y 02−1=8d\texttt{02}^{-1} = \texttt{8d}.

Ejercicio 3

Si en lugar de x8+x4+x3+x+1x^8+x^4+x^3+x+1 se tomara el polinomio x8+1x^8+1 (que en hexadecimal es 101\texttt{101}), ¿seguiría siendo un cuerpo? Demuestra que no, calculando 03⋅ff\texttt{03}\cdot\texttt{ff} con ese módulo.

Ver la solución

Se tiene 03=x+1\texttt{03} = x+1 y ff=x7+x6+⋯+x+1\texttt{ff} = x^7+x^6+\dots+x+1. Entonces

(x+1)(x7+x6+⋯+x+1)=x8+1,(x+1)(x^7+x^6+\dots+x+1) = x^8 + 1,

porque al multiplicar, todos los términos intermedios se cancelan por pares (módulo 2). Con módulo x8+1x^8+1 eso vale 00. Dos bytes no nulos, 03\texttt{03} y ff\texttt{ff}, con producto 00\texttt{00}: es lo mismo que pasaba con 2⋅128≡02\cdot 128 \equiv 0 módulo 256 al principio de la lección. Entonces 03\texttt{03} no tiene inverso y no hay cuerpo. La causa es que x8+1=(x+1)8x^8+1 = (x+1)^8 en F2[x]\mathbb{F}_2[x], que no es irreducible. Con el módulo bueno, 03⋅ff=1a\texttt{03}\cdot\texttt{ff} = \texttt{1a}, distinto de cero.

En resumen

  • AES trata cada byte como un elemento del cuerpo F28=F2[x]/(x8+x4+x3+x+1)\mathbb{F}_{2^8} = \mathbb{F}_2[x]/(x^8+x^4+x^3+x+1): el byte b7…b0b_7\dots b_0 es el polinomio b7x7+⋯+b0b_7x^7+\dots+b_0, y 02\texttt{02} es xx.
  • Sumar es XOR, y restar es lo mismo.
  • Multiplicar por 02\texttt{02} es xtime: desplazar un bit y, si se sale un 1, hacer XOR con 1b\texttt{1b}. Toda multiplicación se reduce a xtime y XOR; por ejemplo, 57⋅83=c1\texttt{57}\cdot\texttt{83} = \texttt{c1}.
  • Como el polinomio es irreducible, todo byte no nulo tiene inverso; por ejemplo, 53−1=ca\texttt{53}^{-1} = \texttt{ca}.

Siguiente lección: AES visto desde arriba: bloques, rondas y estado

Para profundizar