C: sostituire la tabella SubBytes AES FIPS-197 con codice a tempo costante


17

In FIPS-197 ( Advanced Encryption Standard , noto come AES), è ampiamente utilizzato SubBytese potrebbe essere implementato come

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];}

Questa funzione non è arbitraria; è una mappatura reversibile, consistente in un'inversione in un campo di Galois seguita da una trasformazione affine. Tutti i dettagli sono nella sezione 5.1.1 di FIPS-197 o qui nella sezione 4.2.1 (con un nome leggermente diverso).

Un problema con l'implementazione come tabella è che si apre ai cosiddetti attacchi di temporizzazione della cache .

Quindi la tua missione è escogitare un sostituto esatto per la SubBytes()funzione sopra , che mostra un comportamento a tempo costante; supponiamo che sia il caso quando non viene utilizzato nulla a seconda dell'input xdi SubBytes:

  • come indice di array,
  • come controllo operando di if, while, for, case, o operatore ?:;
  • come uno degli operandi di operatori &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • come il diritto operando di operatori >>, <<, *=, /=, %=, <<=, >>=.

Il vincitore sarà quello con il più basso costo, ottenuto dal numero di operatori eseguiti nel percorso dei dati di ingresso-dipendente, con un peso di 5 per operatori unari -e ~così come per <<1, >>1, +1, -1; peso di 7 per tutti gli altri operatori, turni con altri conteggi o aggiunte / sottotitoli di altre costanti (i cast di tipo e le promozioni sono gratuiti). In linea di principio, tale costo è invariato da eventuali cicli di srotolamento e indipendenti dall'input x. Come tie-breaker, la risposta con il codice più breve dopo aver rimosso spazi bianchi e commenti vince.

Ho intenzione di designare una voce come risposta il prima possibile nell'anno 2013, UTC. Prenderò in considerazione le risposte in lingue di mia conoscenza, classificandole come traduzione diretta in C non ottimizzata per dimensione.

Ci scusiamo per l'omissione iniziale +1e -1nei confronti degli operatori favoriti, di calchi e promozioni gratuiti e classificazione delle dimensioni. Nota che *è proibito sia quando unario, sia come moltiplicazione.


1
Vale la pena notare che le ricerche sono gratuite perché è possibile incorporarle come costanti.
Peter Taylor,

"inizio 2013, UTC" - la data non sarebbe più interessante del fuso orario?
Paŭlo Ebermann,

@ PaŭloEbermann: la mia intenzione dovrebbe essere chiara ora.
fgrieu,

Risposte:


13

Punteggio: 940933 926 910, avvicinamento alla torre di 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 struttura è essenzialmente uguale all'implementazione di Boyar e Peralta: ridurre l'inversione in GF (2 ^ 8) all'inversione in GF (2 ^ 4), scomporla in un prologo lineare, un corpo non lineare e un epilogo lineare, e quindi minimizzarli separatamente. Pago alcune penalità per l'estrazione dei bit, ma compenso essendo in grado di fare operazioni in parallelo (con un po 'di riempimento ragionevole dei bit di fwd). Più in dettaglio...

sfondo

Come menzionato nella descrizione del problema, l'S-box consiste in un'inversione in una particolare implementazione del campo Galois GF (2 ^ 8) seguita da una trasformazione affine. Se sai cosa significano entrambi, salta questa sezione.

Una trasformazione affine (o lineare) è una funzione f(x)che rispetta f(x + y) = f(x) + f(y)e f(a*x) = a*f(x).

Un campo è un insieme Fdi elementi con due elementi speciali, che chiameremo 0e 1, e due operatori, che chiameremo +e *, che rispettano varie proprietà. In questa sezione, si supponga che x, ye zsono elementi di F.

  • Gli elementi di Fforma un gruppo abeliano sotto +con 0l'identità: cioè x + yè un elemento F; x + 0 = 0 + x = x; ognuno xha un corrispondente -xtale che x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; e x + y= y + x.
  • Gli elementi Fdiversi da 0formano un gruppo abeliano sotto *con 1l'identità.
  • Moltiplicazione distribuisce su più: x * (y + z) = (x * y) + (x * z).

Si scopre che ci sono alcune limitazioni piuttosto gravi sui campi finiti:

  • Devono avere un numero di elementi che è un potere di un numero primo.
  • Sono isomorfi con tutti gli altri campi finiti della stessa dimensione (cioè c'è davvero solo un campo finito di una data dimensione, e tutti gli altri sono solo rietichettatura; chiama quel campo GF (p ^ k) dove pè il primo ed kè il potere) .
  • Il gruppo moltiplicativo F\{0}sotto *è ciclico; cioè c'è almeno un elemento gtale che ogni elemento è un potere di g.
  • Per poteri maggiori di 1 esiste una rappresentazione come polinomi univariati dell'ordine kdel campo dell'ordine primario. Ad esempio GF (2 ^ 8) ha una rappresentazione in termini di polinomi di xoltre GF (2). In effetti, di solito c'è più di una rappresentazione. Considerare x^7 * xin GF (2 ^ 8); deve essere equivalente a qualche polinomio di ordine 7, ma quale? Ci sono molte scelte che danno la giusta struttura; AES sceglie di realizzare x^8 = x^4 + x^3 + x + 1(il polinomio lessicograficamente più piccolo che funziona).

Quindi, come possiamo calcolare un inverso in quella particolare rappresentazione di GF (2 ^ 8)? È un problema troppo grosso da affrontare direttamente, quindi dobbiamo scomporlo.

Torri di campo: rappresentante GF (2 ^ 8) in termini di GF (2 ^ 4)

Invece di rappresentare GF (2 ^ 8) con polinomi di 8 termini su GF (2) possiamo rappresentarlo con polinomi di 2 termini su GF (2 ^ 4). Questa volta dobbiamo scegliere un polinomio lineare per x^2. Supponiamo di scegliere x^2 = px + q. Poi (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

È più facile trovare un contrario in questa rappresentazione? Se (cx + d) = (ax + b)^-1otteniamo le equazioni simultanee

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

Lascia D = [b(b+ap) + a^2 q]e imposta c = a * D^-1; d = (b + ap) * D^-1. Quindi possiamo fare un inverso in GF (2 ^ 8) per il costo di una conversione in GF (2 ^ 4), un inverso e alcune aggiunte e moltiplicazioni in GF (2 ^ 4) e una conversione indietro. Anche se facciamo l'inverso per mezzo di una tabella, abbiamo ridotto le dimensioni della tabella da 256 a 16.

Dettagli di implementazione

Per costruire una rappresentazione di GF (4) possiamo scegliere tra tre polinomi per ridurre x^4:

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

La differenza più importante è nell'attuazione della moltiplicazione. Per uno dei tre (che corrispondono a poly3, 9, f), funzionerà quanto segue:

// 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;
}

Tuttavia, se scegliamo poly = 3, possiamo gestire l'overflow in modo molto più efficiente perché ha una struttura piacevole: non esiste un doppio overflow, perché i due input sono entrambi cubici ed x^6 = x^2 (x + 1)è anche cubico. Inoltre possiamo salvare i turni di b: dato che stiamo lasciando l'ultimo overflow, a0 x^2non ci sono bit impostati corrispondenti a xo 1 e quindi possiamo mascherarlo con -4 invece di -1. Il risultato è

// 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;
}

Dobbiamo ancora scegliere i valori di pe qper la rappresentazione di GF (2 ^ 8) su GF (2 ^ 4), ma non abbiamo molti vincoli su di essi. L'unica cosa che conta è che possiamo ottenere una funzione lineare dai bit della nostra rappresentazione originale ai bit della rappresentazione di lavoro. Ciò è importante per due motivi: in primo luogo, è facile eseguire trasformazioni lineari, mentre una trasformazione non lineare richiederebbe un equivalente di ottimizzazione in difficoltà per ottimizzare semplicemente la S-box; in secondo luogo, perché possiamo ottenere alcuni vantaggi collaterali. Per ricapitolare la struttura:

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
}

Se i bit di xsono x7 x6 ... x0quindi poiché la trasformazione è lineare, otteniamo a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). Quadratelo e arriviamo a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2dove i termini incrociati si annullano (perché in GF (2), 1 + 1 = 0). Quindi a^2può anche essere calcolato come una funzione lineare di x. Possiamo aumentare la trasformazione lineare in avanti per ottenere:

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);
}

e siamo fino a tre moltiplicazioni e un'aggiunta. Possiamo anche estendere il codice di moltiplicazione per fare le due moltiplicazioni Dinvin parallelo. Quindi il nostro costo totale è una trasformazione lineare diretta, un'aggiunta, due moltiplicazioni, un inverso in GF (2 ^ 4) e una trasformazione lineare posteriore. Possiamo inserire la trasformazione lineare post-inversa dell'S-box e ottenerla essenzialmente gratuitamente.

Il calcolo dei coefficienti per le trasformazioni lineari non è molto interessante, né la micro-ottimizzazione per salvare una maschera qui e uno spostamento lì. La parte interessante rimanente è l'ottimizzazione diinverse_GF16. Esistono 2 ^ 64 funzioni diverse da 4 bit a 4 bit, quindi un'ottimizzazione diretta richiede molta memoria e tempo. Quello che ho fatto è considerare 4 funzioni da 4 bit a 1 bit, mettere un limite al costo totale consentito per ciascuna funzione (con un costo massimo di 63 per funzione posso elencare tutte le espressioni adatte in meno di un minuto), e per ogni tupla di funzioni eseguire l'eliminazione della sottoespressione comune. Dopo 25 minuti di scricchiolio, trovo che il miglior inverso possibile con quel limite abbia un costo totale di 133 (una media di 33,25 per bit dell'output, il che non è male considerando che l'espressione più economica per ogni singolo bit è 35) .

Sto ancora sperimentando altri approcci per minimizzare l'inversione in GF (2 ^ 4), e un approccio che costruisce dal basso verso l'alto anziché dall'alto verso il basso ha prodotto un miglioramento da 133 a 126.


Fantastico! Confermo che funziona! Un dettaglio: l'ottavo & 1può essere tagliato (specialmente se xè unsigned char; CHAR_BITè 8 in codegolf).
fgrieu,

@fgrieu, buon punto.
Peter Taylor,

8

Punteggio: 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, minimizzazione di Boyar e Peralta

Ho trovato una nuova tecnica di minimizzazione della logica combinatoria con applicazioni alla crittografia di Joan Boyar e René Peralta, che (salvo il formalismo in C) fa ciò che è richiesto. La tecnica utilizzata per ricavare le loro equazioni è brevettata da non meno degli Stati Uniti. Ho appena fatto una traduzione diretta in C delle loro equazioni , gentilmente collegata qui .

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;}

Wow, funziona davvero ed è davvero economico. Quando si smonta, sono infatti 144 istruzioni, ad esclusione di prologo, epilogia e istruzioni di spostamento.
ugoren,

5

Punteggio: 10965

Questo utilizza lo stesso principio di srotolamento della ricerca di array. Potrebbe richiedere cast extra.

Grazie a ugoren per aver sottolineato come migliorare 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;
}

2
Per un numero intero c, (c|-c)>>31è 0 per 0 e -1 altrimenti.
ugoren,

@ugoren, in lingue sensibili, sì. In C, lo spostamento a destra di un tipo senza segno non è specificato.
Peter Taylor,

1
Immagino tu voglia dire firmato. Ma questo sito non è famoso per la rigorosa conformità standard. Inoltre, mi c >> 4sembra che tu abbia firmato il giusto turno. E se davvero insisti, lo ((unsigned int)(c|-c))>>31è c?1:0.
ugoren,

@ugoren, hai ragione, intendevo firmato. Il c >>4funziona con o senza estensione del segno. Ma buona cosa fare usando un turno senza segno: lo modificherò quando torno a casa e posso usare un computer adeguato anziché un telefono. Grazie.
Peter Taylor,

3

Punteggio: 9109, approccio algebrico

Lascerò l'approccio di ricerca nel caso in cui qualcuno possa migliorarlo drasticamente, ma si scopre che è possibile un buon approccio algebrico. Questa implementazione trova l'inverso moltiplicativo usando l'algoritmo di Euclide . L'ho scritto in Java, ma in linea di principio può essere portato su C - ho commentato le parti che potrebbero cambiare se si desidera rielaborarlo utilizzando solo tipi a 8 bit.

Grazie a ugoren per aver sottolineato come accorciare il is_nonzerocontrollo in un commento sull'altra mia risposta.

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;
    }
}

2

Punteggio: 256 * (7+ (8 * (7 + 7 + 7) - (2 + 2)) + 5 + 7 + 7) = 48640 (supponendo che i loop siano srotolati)

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;}

Spiegazione:

Sostanzialmente una ricerca di array reimplementata usando operatori bit per bit ed elaborando sempre l'intero array. Passiamo attraverso l'array e eseguiamo xor xcon ciascun indice, quindi utilizziamo operatori bit per negare logicamente il risultato, quindi otteniamo 255 quando x=ie 0 altrimenti. Facciamo bit a bit e questo con il valore di array, in modo che il valore scelto rimanga invariato e gli altri diventino 0. Quindi prendiamo il bit a bit o di questo array, estraendo così solo il valore scelto.

Le due 1 << joperazioni scompariranno durante lo svolgimento del ciclo, sostituendole con i poteri di 2 da 1 a 128.


Ora per vedere se è possibile fare effettivamente la matematica usando operatori bit per bit.
histocrat,

Guardando l'algoritmo qui , dubito che sarà possibile implementare l'inversione polinomiale senza passare in rassegna tutti i byte almeno una volta come sostituto di alcuni passaggi del tempo polinomiale. Quindi questo potrebbe battere qualsiasi soluzione "intelligente". Sospetto che l'ottimizzazione di questa ricerca di array a tempo costante sia una strada più promettente.
histocrat,

Bello. La funzione rj_sbox in aes.c qui potrebbe dare ispirazione (anche se, com'è, non corrisponde alla domanda).
febbraio

Da dove viene il -(2+2)calcolo del punteggio? Modifica: ah, l'allineamento crea a <<1e a >>1.
Peter Taylor,

0

Punteggio 1968 1692, usando la tabella di ricerca

Nota: questa soluzione non soddisfa i criteri, a causa di w >> b.

Utilizzando la tabella di ricerca, ma leggendo 8 byte alla volta.
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
}

Non penso che questo soddisfi la definizione di tempo costante nella domanda, perché w>>bRHS ha calcolato dax
Peter Taylor il

Esistono diverse violazioni: w >> bdove bdipende dall'input; Inoltre x/8, x%8e *= (1-badi). È particolarmente probabile che il primo degeneri in una dipendenza temporale dalle CPU di fascia bassa. Tuttavia, l'idea di utilizzare variabili ampie ha certamente delle potenzialità.
febbraio

Non ho letto abbastanza attentamente le istruzioni. Posso risolvere la maggior parte dei problemi, ma w >> bè abbastanza essenziale (devo vedere se può essere risolto senza riscrivere tutto.
Ugoren,

0

Ricerca tabella e maschera, punteggio = 256 * (5 * 7 + 1 * 5) = 10240

Utilizza la ricerca tabella con una maschera per selezionare solo il risultato desiderato. Usa il fatto che j|-jè negativo (quando i! = X) o zero (quando i == x). Shifting crea una maschera tutto-in-uno o tutto-zero che viene utilizzata per selezionare solo la voce desiderata.

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;
}

Non è la stessa della mia seconda risposta se non meno ottimizzata?
Peter Taylor,

Chiudi, immagino. Sto usando lo spostamento con segno, quindi non devo fare -1 alla fine.
Keith Randall,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.