C: reemplace la tabla AES FIPS-197 SubBytes por código de tiempo constante

17

En FIPS-197 (el Estándar de cifrado avanzado , conocido como AES), se utiliza mucho SubBytes, lo que podría implementarse como

unsigned char SubBytes(unsigned char x) {
static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};
return t[x];}

Esta función no es arbitraria; Es un mapeo reversible, que consiste en una inversión en un campo de Galois seguido de una transformación afín. Todos los detalles están en FIPS-197 sección 5.1.1 o aquí sección 4.2.1 (bajo un nombre ligeramente diferente).

Un problema con la implementación como tabla es que se abre a los llamados ataques de sincronización de caché .

Por lo tanto, su misión es idear un reemplazo exacto para la SubBytes()función anterior , que exhibe un comportamiento de tiempo constante; asumiremos que ese es el caso cuando no se usa nada dependiendo de la entrada xde SubBytes:

  • como un índice de matriz,
  • como el control operando de if, while, for, case, o el operador ?:;
  • como cualquier operando de operadores &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • como el operando derecho de los operadores >>, <<, *=, /=, %=, <<=, >>=.

El ganador será uno con el costo más bajo, obtenido a partir del número de operadores ejecutados en la ruta de datos de entrada-dependiente, con un peso de 5 para los operadores unarios -y ~así como para <<1, >>1, +1, -1; peso de 7 para todos los demás operadores, turnos con otros recuentos o adiciones / subscripciones de otras constantes (los tipos de lanzamientos y promociones son gratuitos). Por principio, ese costo no cambia al desenrollar los bucles (si los hay), y es independiente de la entrada x. Como factor decisivo, gana la respuesta con el código más corto después de eliminar espacios en blanco y comentarios.

Planeo designar una entrada como respuesta lo antes posible en el año 2013, UTC. Consideraré las respuestas en los idiomas que conozco, clasificándolas como una traducción directa a C no optimizada para el tamaño.

Disculpas por la omisión inicial de +1y -1en los operadores favorecidos, de lanzamientos gratuitos y promociones, y clasificación de tamaño. Tenga en cuenta que *está prohibido tanto cuando es unario como de multiplicación.

fgrieu
fuente
1
Vale la pena señalar que las búsquedas son gratuitas porque puede incluirlas como constantes.
Peter Taylor
"a principios del año 2013, UTC": ¿no sería la fecha más interesante que la zona horaria?
Paŭlo Ebermann
@ PaŭloEbermann: Mi intención debería estar clara ahora.
fgrieu
Véase también codegolf.stackexchange.com/questions/3766/…
Keith Randall el

Respuestas:

13

Puntuación: 940933926910 , enfoque de torre de campo

public class SBox2
{
    public static void main(String[] args)
    {
        for (int i = 0; i < 256; i++) {
            int s = SubBytes(i);
            System.out.format("%02x  ", s);
            if (i % 16 == 15) System.out.println();
        }
    }

    private static int SubBytes(int x) {
        int fwd;
        fwd  = 0x010001 & -(x & 1); x >>= 1; //   7+5+7+5+ | 24+
        fwd ^= 0x1d010f & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x4f020b & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x450201 & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xce080d & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xa20f0f & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0xc60805 & -(x & 1); x >>= 1; // 7+7+5+7+5+ | 31+
        fwd ^= 0x60070e & -x;                // 7+7+5+     | 19+

        // Running total so far: 229

        int p1;
        {
            int ma = fwd;
            int mb = fwd >> 16;         // 7+         | 7+
            p1  = ma & -(mb&1); ma<<=1; //   7+5+7+5+ | 24+
            p1 ^= ma & -(mb&2); ma<<=1; // 7+7+5+7+5+ | 31+
            p1 ^= ma & -(mb&4); ma<<=1; // 7+7+5+7+5+ | 31+
            p1 ^= ma & -(mb&8);         // 7+7+5+7+   | 26+
            int t = p1 >> 3;            // 7+         | 7+
            p1 ^= (t >> 1) ^ (t & 0xe); // 7+5+7+7+   | 26+
        }

        // Running total so far: 229 + 152 = 381

        int y3, y2, y1, y0;
        {
            int Kinv = (fwd >> 20) ^ p1;     // 7+7+
            int w0 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w1 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w2 = Kinv & 1; Kinv >>= 1;   // 7+5+
            int w3 = Kinv & 1;               // 7+

            int t0 = w1 ^ w0 ^ (w2 & w3);      // 7+7+7+
            int t1 = w2 ^ (w0 | w3);           // 7+7+
            int t2 = t0 ^ t1;                  // 7+

            y3 = t2 ^ (t1 & (w1 & w3));        // 7+7+7+
            y2 = t0 ^ (w0 | t2);               // 7+7+
            y1 = w0 ^ w3 ^ (t1 & t0);          // 7+7+7+
            y0 = w3 ^ (t0 | (w1 ^ (w0 | w2))); // 7+7+7+7


        }

        // Running total so far: 381 + 24*7 + 3*5 = 564

        int p2;
        {
            int ma = fwd;
            p2  = ma & -y0; ma<<=1;       //   7+5+5+ | 17+
            p2 ^= ma & -y1; ma<<=1;       // 7+7+5+5+ | 24+
            p2 ^= ma & -y2; ma<<=1;       // 7+7+5+5+ | 24+
            p2 ^= ma & -y3;               // 7+7+5+   | 19+
            int t = p2 >> 3;              // 7+       | 7+
            p2 ^= (t >> 1) ^ (t & 0xe0e); // 7+5+7+7+ | 26
        }

        // Running total so far: 564 + 117 = 681

        int inv8;
        inv8  =  31 & -(p2 & 1);           //   7+5+7+   | 19+
        inv8 ^= 178 & -(p2 & 2); p2 >>= 2; // 7+7+5+7+7+ | 33+
        inv8 ^= 171 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^=  54 & -(p2 & 2); p2 >>= 6; // 7+7+5+7+7+ | 33+
        inv8 ^= 188 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^=  76 & -(p2 & 2); p2 >>= 2; // 7+7+5+7+7+ | 33+
        inv8 ^= 127 & -(p2 & 1);           // 7+7+5+7+   | 26+
        inv8 ^= 222 & -(p2 & 2);           // 7+7+5+7    | 26+

        return inv8 ^ 0x63;                // 7+         | 7+

        // Grand total: 681 + 229 = 910
    }
}

La estructura es esencialmente la misma que la implementación de Boyar y Peralta: reduzca la inversión en GF (2 ^ 8) a la inversión en GF (2 ^ 4), divídala en un prólogo lineal, un cuerpo no lineal y un epílogo lineal, y luego minimizarlos por separado. Pago algunas penalizaciones por la extracción de bits, pero lo compenso al poder realizar operaciones en paralelo (con un poco de relleno juicioso de los bits de fwd). Con más detalle...

Antecedentes

Como se menciona en la descripción del problema, el cuadro S consiste en una inversión en una implementación particular del campo de Galois GF (2 ^ 8) seguido de una transformación afín. Si sabe lo que ambos significan, omita esta sección.

Una transformación afín (o lineal) es una función f(x)que respeta f(x + y) = f(x) + f(y)y f(a*x) = a*f(x).

Un campo es un conjunto Fde elementos con dos elementos especiales, que llamaremos 0y 1, y dos operadores, que llamaremos +y *, que respeta varias propiedades. En esta sección, se supone que x, yy zson elementos de F.

  • Los elementos de Fforma un grupo abeliano bajo +con 0como la identidad: es decir, x + yes un elemento de F; x + 0 = 0 + x = x; cada uno xtiene un correspondiente -xtal que x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; y x + y= y + x.
  • Los elementos de Fotro que 0forma un grupo abeliano bajo *con 1como la identidad.
  • Multiplicación distribuye sobre la suma: x * (y + z) = (x * y) + (x * z).

Resulta que hay algunas limitaciones bastante severas en los campos finitos:

  • Deben tener una serie de elementos que es un poder de un primo.
  • Son isomórficos con todos los demás campos finitos del mismo tamaño (es decir, en realidad solo hay un campo finito de un tamaño determinado, y cualquier otro es solo un reencadenamiento; llame a ese campo GF (p ^ k) donde pestá el primo y kes el poder) .
  • El grupo multiplicativo F\{0}debajo *es cíclico; es decir, hay al menos un elemento de gtal manera que cada elemento es un poder g.
  • Para potencias mayores que 1 hay una representación como polinomios univariados de orden kdel campo del orden primo. Por ejemplo, GF (2 ^ 8) tiene una representación en términos de polinomios de xmás de GF (2). De hecho, generalmente hay más de una representación. Considere x^7 * xen GF (2 ^ 8); debe ser equivalente a algún polinomio de orden 7, pero ¿cuál? Hay muchas opciones que dan la estructura correcta; AES elige hacer x^8 = x^4 + x^3 + x + 1(el polinomio lexicográficamente más pequeño que funciona).

Entonces, ¿cómo calculamos un inverso en esa representación particular de GF (2 ^ 8)? Es un problema demasiado difícil de abordar directamente, por lo que debemos desglosarlo.

Torres de campo: representando GF (2 ^ 8) en términos de GF (2 ^ 4)

En lugar de representar GF (2 ^ 8) con polinomios de 8 términos sobre GF (2), podemos representarlo con polinomios de 2 términos sobre GF (2 ^ 4). Esta vez tenemos que elegir un polinomio lineal para x^2. Supongamos que elegimos x^2 = px + q. Entonces (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

¿Es más fácil encontrar un inverso en esta representación? Si (cx + d) = (ax + b)^-1obtenemos las ecuaciones simultáneas

  • ad + (b + ap)c = 0
  • bd + (aq)c = 1

Dejar D = [b(b+ap) + a^2 q]y poner c = a * D^-1; d = (b + ap) * D^-1. Entonces podemos hacer un inverso en GF (2 ^ 8) por el costo de una conversión a GF (2 ^ 4), un inverso y algunas adiciones y multiplicaciones en GF (2 ^ 4), y una conversión de regreso. Incluso si hacemos lo inverso por medio de una tabla, hemos reducido el tamaño de la tabla de 256 a 16.

Detalles de implementacion

Para construir una representación de GF (4) podemos elegir entre tres polinomios para reducir x^4:

  • x^4 = x + 1
  • x^4 = x^3 + 1
  • x^4 = x^3 + x^2 + x + 1

La diferencia más importante está en la implementación de la multiplicación. Para cualquiera de los tres (que corresponden a poly3, 9, f) funcionará lo siguiente:

// 14x &, 7x unary -, 3x <<1, 3x >>1, 3x >>3, 6x ^ gives score 226
int mul(int a, int b) {
    // Call current values a = a0, b = b0
    int p = a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x); a = a0 x; b = b0 div x

    p ^= a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x^2); a = a0 x^2; b = b0 div x^2

    p ^= a & -(b & 1);
    a = ((a << 1) ^ (poly & -(a >> 3))) & 15;
    b >>= 1;
    // Now p = a0 * (b0 mod x^3); a = a0 x^3; b = b0 div x^3

    p ^= a & -(b & 1);
    // p = a0 * b0

    return p;
}

Sin embargo, si elegimos poly = 3, podemos manejar el desbordamiento de manera mucho más eficiente porque tiene una estructura agradable: no hay doble desbordamiento, porque las dos entradas son cúbicas y x^6 = x^2 (x + 1)también son cúbicas. Además, podemos guardar los cambios de b: ya que estamos dejando que el desbordamiento dure, a0 x^2no tiene ningún bit establecido correspondiente a xo 1, por lo que podemos enmascararlo con -4 en lugar de -1. El resultado es

// 10x &, 4x unary -, 3x <<1, 1x >>1, 1x >>3, 5x ^ gives score 152
int mul(int a, int b) {
    int p;
    p  = a & -(b & 1); a <<= 1;
    p ^= a & -(b & 2); a <<= 1;
    p ^= a & -(b & 4); a <<= 1;
    p ^= a & -(b & 8);
    // Here p = a0 * b0 but with overflow, which we need to bring back down.

    int t = p >> 3;
    p ^= (t >> 1) ^ (t & 0xe);
    return p & 15;
}

Todavía tenemos que elegir los valores de py qpara la representación de GF (2 ^ 8) sobre GF (2 ^ 4), pero no tenemos muchas restricciones sobre ellos. Lo único que importa es que podemos obtener una función lineal de los bits de nuestra representación original a los bits de la representación de trabajo. Esto es importante por dos razones: en primer lugar, es fácil realizar transformaciones lineales, mientras que una transformación no lineal requeriría una optimización equivalente en dificultad a la optimización de todo el S-box; En segundo lugar, porque podemos obtener algunos beneficios secundarios. Para recapitular la estructura:

GF256 SubBytes(GF256 x) {
    GF16 a, b, t, D, Dinv, c, d;

    (a, b) = f(x); // f is linear

    t = b + a * p;
    D = b * t + a * a * q;
    Dinv = inverse_GF16(D);
    c = a * Dinv;
    d = t * Dinv;

    return finv(c, d); // finv is also linear
}

Si los bits de xson x7 x6 ... x0entonces, dado que la transformación es lineal, obtenemos a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Ajústalo y obtenemos a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2donde se cancelan los términos cruzados (porque en GF (2), 1 + 1 = 0). Por a^2lo tanto , también se puede calcular como una función lineal de x. Podemos aumentar la transformación lineal directa para obtener:

GF256 SubBytes(GF256 x) {
    GF16 a, b, t, a2q, D, Dinv, c, d;

    (a, b, t, a2q) = faug(x);

    D = b * t + a2q;
    Dinv = inverse_GF16(D);
    c = a * Dinv;
    d = t * Dinv;

    return finv(c, d);
}

y tenemos tres multiplicaciones y una suma. También podemos extender el código de multiplicación para hacer las dos multiplicaciones Dinven paralelo. Por lo tanto, nuestro costo total es una transformación lineal hacia adelante, una suma, dos multiplicaciones, una inversa en GF (2 ^ 4) y una transformación lineal hacia atrás. Podemos lanzar la transformación lineal post-inversa del S-box y obtener eso esencialmente gratis.

El cálculo de los coeficientes para las transformaciones lineales no es muy interesante, y tampoco lo es la microoptimización para guardar una máscara aquí y un cambio allí. La parte interesante restante es la optimización deinverse_GF16. Hay 2 ^ 64 funciones diferentes de 4 bits a 4 bits, por lo que una optimización directa requiere mucha memoria y tiempo. Lo que he hecho es considerar 4 funciones de 4 bits a 1 bit, poner un límite al costo total permitido para cualquier función (con un costo máximo de 63 por función puedo enumerar todas las expresiones adecuadas en menos de un minuto), y para cada tupla de funciones, elimine la subexpresión común. Después de 25 minutos de procesamiento, encuentro que el mejor inverso posible con ese límite tiene un costo total de 133 (un promedio de 33.25 por bit de salida, lo cual no es malo teniendo en cuenta que la expresión más barata para cualquier bit individual es 35) .

Todavía estoy experimentando con otros enfoques para minimizar la inversión en GF (2 ^ 4), y un enfoque que construye de abajo hacia arriba en lugar de de arriba hacia abajo ha arrojado una mejora de 133 a 126.

Peter Taylor
fuente
¡Fantástico! ¡Confirmo que funciona! Un detalle: el octavo & 1se puede recortar (especialmente si xes unsigned char; CHAR_BITes 8 en codegolf).
fgrieu
@fgrieu, buen punto.
Peter Taylor
8

Puntuación: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, minimización de Boyar y Peralta

Encontré una nueva técnica de minimización de lógica combinacional con aplicaciones a la criptología de Joan Boyar y René Peralta, que (salvo el formalismo C) hace lo que se requiere. La técnica utilizada para derivar sus ecuaciones está patentada por nada menos que Estados Unidos. Acabo de hacer una traducción C directa de sus ecuaciones , amablemente vinculada aquí .

unsigned char SubBytes_Boyar_Peralta(unsigned char x7){
  unsigned char 
  x6=x7>>1,x5=x6>>1,x4=x5>>1,x3=x4>>1,x2=x3>>1,x1=x2>>1,x0=x1>>1,
  y14=x3^x5,y13=x0^x6,y9=x0^x3,y8=x0^x5,t0=x1^x2,y1=t0^x7,y4=y1^x3,y12=y13^y14,y2=y1^x0,
  y5=y1^x6,y3=y5^y8,t1=x4^y12,y15=t1^x5,y20=t1^x1,y6=y15^x7,y10=y15^t0,y11=y20^y9,y7=x7^y11,
  y17=y10^y11,y19=y10^y8,y16=t0^y11,y21=y13^y16,y18=x0^y16,t2=y12&y15,t3=y3&y6,t4=t3^t2,
  t5=y4&x7,t6=t5^t2,t7=y13&y16,t8=y5&y1,t9=t8^t7,t10=y2&y7,t11=t10^t7,t12=y9&y11,
  t13=y14&y17,t14=t13^t12,t15=y8&y10,t16=t15^t12,t17=t4^t14,t18=t6^t16,t19=t9^t14,
  t20=t11^t16,t21=t17^y20,t22=t18^y19,t23=t19^y21,t24=t20^y18,t25=t21^t22,t26=t21&t23,
  t27=t24^t26,t28=t25&t27,t29=t28^t22,t30=t23^t24,t31=t22^t26,t32=t31&t30,t33=t32^t24,
  t34=t23^t33,t35=t27^t33,t36=t24&t35,t37=t36^t34,t38=t27^t36,t39=t29&t38,t40=t25^t39,
  t41=t40^t37,t42=t29^t33,t43=t29^t40,t44=t33^t37,t45=t42^t41,z0=t44&y15,z1=t37&y6,
  z2=t33&x7,z3=t43&y16,z4=t40&y1,z5=t29&y7,z6=t42&y11,z7=t45&y17,z8=t41&y10,z9=t44&y12,
  z10=t37&y3,z11=t33&y4,z12=t43&y13,z13=t40&y5,z14=t29&y2,z15=t42&y9,z16=t45&y14,z17=t41&y8,
  t46=z15^z16,t47=z10^z11,t48=z5^z13,t49=z9^z10,t50=z2^z12,t51=z2^z5,t52=z7^z8,t53=z0^z3,
  t54=z6^z7,t55=z16^z17,t56=z12^t48,t57=t50^t53,t58=z4^t46,t59=z3^t54,t60=t46^t57,
  t61=z14^t57,t62=t52^t58,t63=t49^t58,t64=z4^t59,t65=t61^t62,t66=z1^t63,s0=t59^t63,
  s6=t56^t62,s7=t48^t60,t67=t64^t65,s3=t53^t66,s4=t51^t66,s5=t47^t65,s1=t64^s3,s2=t55^t67;
  return (((((((s0<<1|s1&1)<<1|s2&1)<<1|s3&1)<<1|s4&1)<<1|s5&1)<<1|s6&1)<<1|s7&1)^0x63;}
fgrieu
fuente
Wow, realmente funciona y muy barato. Al desmontar, en realidad son 144 instrucciones, excluyendo el prólogo, la epilogía y las instrucciones de movimiento.
ugoren
5

Puntuación: 10965

Esto utiliza el mismo principio de desenrollar la búsqueda de matriz. Puede requerir moldes adicionales.

Gracias a ugoren por señalar cómo mejorar is_zero.

// Cost: 255 * (5+7+24+7) = 10965
unsigned char SubBytes(unsigned char x) {
    unsigned char r = 0x63;
    char c = (char)x;
    c--; r ^= is_zero(c) & (0x63^0x7c); // 5+7+24+7 inlining the final xor
    c--; r ^= is_zero(c) & (0x63^0x77); // 5+7+24+7
    // ...
    c--; r ^= is_zero(c) & (0x63^0x16); // 5+7+24+7
    return r;
}

// Cost: 24
// Returns (unsigned char)-1 when input is 0 and 0 otherwise
unsigned char is_zero(char c) {
    // Shifting a signed number right is unspecified, so use unsigned
    unsigned char u;
    c |= -c;               // 7+5+
    u = (unsigned char)c;
    u >>= (CHAR_BITS - 1); // 7+
    c = (char)u;
    // c is 0 if we want -1 and 1 otherwise.
    c--;                   // 5
    return (unsigned char)c;
}
Peter Taylor
fuente
2
Para un número entero c, (c|-c)>>31es 0 para 0 y -1 en caso contrario.
ugoren
@ugoren, en lenguajes sensibles, sí. En C, el desplazamiento a la derecha de un tipo sin signo no está especificado.
Peter Taylor
1
Supongo que te refieres a firmado. Pero este sitio no es realmente famoso por el estricto cumplimiento de las normas. Además, tu c >> 4parece un giro a la derecha firmado para mí. Y si realmente insistes, lo ((unsigned int)(c|-c))>>31es c?1:0.
ugoren
@ugoren, tienes razón, quise decir firmado. Las c >>4obras con o sin extensión de signo. Pero es una buena idea usar un turno sin firmar: se editará cuando llegue a casa y pueda usar una computadora adecuada en lugar de un teléfono. Gracias.
Peter Taylor
3

Puntuación: 9109, enfoque algebraico

Dejaré el enfoque de búsqueda en caso de que alguien pueda mejorarlo drásticamente, pero resulta que es posible un buen enfoque algebraico. Esta implementación encuentra el inverso multiplicativo usando el algoritmo de Euclides . Lo escribí en Java pero, en principio, puede portarse a C: he comentado las partes que cambiarían si desea volver a trabajarlo utilizando solo tipos de 8 bits.

Gracias a ugoren por señalar cómo acortar el is_nonzerocheque en un comentario sobre mi otra respuesta.

public class SBox
{
    public static void main(String[] args)
    {
        for (int i = 0; i < 256; i++) {
            int s = SubBytes(i);
            System.out.format("%02x  ", s);
            if (i % 16 == 15) System.out.println();
        }
    }

    // Total cost: 9109
    public static int SubBytes(int x)
    {
        x = inv_euclid(x); // 9041
        x = affine(x);     // 68
        return x;
    }

    // Total cost: 68
    private static int affine(int s0) {
        int s = s0;
        s ^= (s0 << 1) ^ (s0 >> 7); // 5 + 7
        s ^= (s0 << 2) ^ (s0 >> 6); // 7 + 7
        s ^= (s0 << 3) ^ (s0 >> 5); // 7 + 7
        s ^= (s0 << 4) ^ (s0 >> 4); // 7 + 7
        return (s ^ 0x63) & 0xff;   // 7 + 7
    }

    // Does the inverse in the Galois field for a total cost of 24 + 9010 + 7 = 9041
    private static int inv_euclid(int s) {
        // The first part of handling the special case: cost of 24
        int zeromask = is_nonzero(s);

        // NB the special value of r would complicate the unrolling slightly with unsigned bytes
        int r = 0x11b, a = 0, b = 1;

        // Total cost of loop: 7*(29+233+566+503+28) - 503 = 9010
        for (int depth = 0; depth < 7; depth++) { // 7*(
            // Calculate mask to fake out when we're looping further than necessary: cost 29
            int mask = is_nonzero(s >> 1);

            // Cost: 233
            int ord = polynomial_order(s);

            // This next block does div/rem at a total cost of 6*(24+49) + 69 + 59 = 566
            int quot = 0, rem = r;
            for (int i = 7; i > 1; i--) {                   // 6*(
                int divmask = is_nonzero(ord & (rem >> i)); // 24+7+7
                quot ^= (1 << i) & divmask;                 // 7+0+7+ since 1<<i is inlined on unrolling
                rem ^= (s << i) & divmask;                  // 7+7+7) +
            }
            int divmask1 = is_nonzero(ord & (rem >> 1));    // 24+7+5
            quot ^= 2 & divmask1;                           // 7+7+
            rem ^= (s << 1) & divmask1;                     // 7+5+7+
            int divmask0 = is_nonzero(ord & rem);           // 24+7
            quot ^= 1 & divmask0;                           // 7+7+
            rem ^= s & divmask0;                            // 7+7

            // This next block does the rest for the cost of a mul (503) plus 28
            // When depth = 0, b = 1 so we can skip the mul on unrolling
            r = s;
            s = rem;
            quot = mul(quot, b) ^ a;
            a = b;
            b ^= (quot ^ b) & mask;
        }

        // The rest of handling the special case: cost 7
        return b & zeromask;
    }

    // Gets the highest set bit in the input. Assumes that it's always at least 1<<1
    // Cost: 233
    private static int polynomial_order(int s) {
        int ord = 2;
        ord ^= 6 & -((s >> 2) & 1);           // 7+7+5+7+7 = 33 +
        ord ^= (ord ^ 8) & -((s >> 3) & 1);   // 7+7+7+5+7+7 = 40 +
        ord ^= (ord ^ 16) & -((s >> 4) & 1);  // 40 +
        ord ^= (ord ^ 32) & -((s >> 5) & 1);  // 40 +
        ord ^= (ord ^ 64) & -((s >> 6) & 1);  // 40 +
        ord ^= (ord ^ 128) & -((s >> 7) & 1); // 40
        return ord;
    }

    // Returns 0 if c is 0 and -1 otherwise
    // Cost: 24
    private static int is_nonzero(int c) {
        c |= -c;   // 7+5+
        c >>>= 31; // 7+ (simulating a cast to unsigned and right shift by CHAR_BIT)
        c = -c;    // 5+ (could be saved assuming a particular implementation of signed right shift)
        return c;
    }

    // Performs a multiplication in the Rijndael finite field
    // Cost: 503 (496 if working with unsigned bytes)
    private static int mul(int a, int b) {
        int p = 0;
        for (int counter = 0; counter < 8; counter++) { // 8*(_+_
            p ^= a & -(b & 1);                          // +7+7+5+7
            a = (a << 1) ^ (0x1b & -(a >> 7));          // +5+7+7+5+7
            b >>= 1;                                    // +5)
        }
        p &= 0xff;                                      // +7 avoidable with unsigned bytes
        return p;
    }
}
Peter Taylor
fuente
2

Puntuación: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (asumiendo bucles desenrollados)

unsigned char SubBytes(unsigned char x) {
static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

unsigned char ret_val = 0;
int i,j;
for(i=0;i<256;i++) {
  unsigned char is_index = (x ^ ((unsigned char) i));
  for(j=0;j<8;j++) {
   is_index |= (is_index << (1 << j)) | (is_index >> (1 << j));
  }
  is_index = ~is_index;
  ret_val |= is_index & t[i];
}

return ret_val;}

Explicación:

Esencialmente, una búsqueda de matriz se vuelve a implementar utilizando operadores bit a bit y siempre procesando toda la matriz. Recorremos la matriz y xor xcon cada índice, luego usamos operadores bit a bit para negar lógicamente el resultado, por lo que obtenemos 255 cuando x=iy 0 de lo contrario. Hacemos bit a bit, y esto con el valor de la matriz, de modo que el valor elegido no cambia y los demás se convierten en 0. Luego tomamos el bit a bit o de esta matriz, sacando así solo el valor elegido.

Las dos 1 << joperaciones desaparecerían como parte del desenrollado del bucle, reemplazándolas con las potencias de 2 de 1 a 128.

histocrat
fuente
Ahora para ver si es posible hacer los cálculos usando operadores bit a bit.
histocrat
Mirando a través del algoritmo aquí , dudo que sea posible implementar la inversión polinomial sin recorrer todos los bytes al menos una vez como sustituto de algunos de los pasos de tiempo polinomial. Por lo tanto, esto podría superar cualquier solución "inteligente". Sospecho que ajustar esta búsqueda de matriz de tiempo constante es una vía más prometedora.
histocrat
Agradable. La función rj_sbox en aes.c aquí puede inspirar (aunque, como es, no coincide con la pregunta).
fgrieu
¿De dónde -(2+2)viene el cálculo de puntuación? Editar: ah, la creación de líneas crea ay <<1a >>1.
Peter Taylor
0

Puntuación 1968 1692, usando la tabla de búsqueda

Nota: Esta solución no pasa los criterios debido a w >> b.

Usando la tabla de búsqueda, pero leyendo 8 bytes a la vez.
3 * 7 + 32 * (6 * 7 + 2 * 5) + 7 = 692

unsigned char SubBytes(unsigned char x){

static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

  unsigned long long *t2 = (unsigned long long *)t;
  int a = x>>3, b=(x&7)<<3;                       // 7+7+7
  int i;
  int ret = 0;
  for (i=0;i<256/8;i++) {                         // 32 *
      unsigned long long w = t2[i];
      int badi = ((unsigned int)(a|-a))>>31;      // 7+7+5
      w &= (badi-1);                              // +7+7
      a--;                                        // +5
      ret |= w >> b;                              // +7+7
  }
  return ret & 0xff;                              // +7
}
Ugoren
fuente
No creo que esto cumpla con la definición de tiempo constante en la pregunta, porque w>>bRHS ha calculado a partir dex
Peter Taylor
Hay varias violaciones: w >> bdonde bdepende de la entrada; También x/8, x%8y *= (1-badi). El primero es especialmente probable que degenere en una dependencia de tiempo en las CPU de gama baja. Sin embargo, la idea de usar variables amplias ciertamente tiene potencial.
fgrieu
No leí las instrucciones con suficiente cuidado. Puedo solucionar la mayoría de los problemas, pero w >> bes bastante esencial (que necesito para ver si se puede solucionar sin tener que reescribir todo.
ugoren
0

Tabla de búsqueda y máscara, puntaje = 256 * (5 * 7 + 1 * 5) = 10240

Utiliza la búsqueda de tablas con una máscara para seleccionar solo el resultado que queremos. Utiliza el hecho de que j|-jes negativo (cuando i! = X) o cero (cuando i == x). Shifting crea una máscara de todo uno o de cero, que se utiliza para seleccionar solo la entrada que queremos.

static const unsigned char t[256] = {
  0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
  0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
  0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
  0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
  0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
  0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
  0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
  0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
  0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
  0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
  0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
  0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
  0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
  0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
  0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
  0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16};

unsigned char SubBytes(unsigned char x) {
  unsigned char r = 255;
  for (int i = 0; i < 256; i++) {
    int j = i - x;
    r &= t[i] | ((j | -j) >> 31);
  }
  return r;
}
Keith Randall
fuente
¿No es esto lo mismo que mi segunda respuesta, excepto que está menos optimizado?
Peter Taylor
Cerca, supongo. Estoy usando shift firmado, así que no tengo que hacer el -1 al final.
Keith Randall