Implementa l'S-box di Rijndael


15

La S-box di Rijndael è un'operazione di uso frequente nella crittografia e decrittografia AES . In genere è implementato come una tabella di ricerca a 256 byte. È veloce, ma significa che è necessario enumerare una tabella di ricerca a 256 byte nel codice. Scommetto che qualcuno in questa folla potrebbe farlo con meno codice, data la struttura matematica sottostante.

Scrivi una funzione nella tua lingua preferita che implementa la S-box di Rijndael. Il codice più corto vince.


1
Punti bonus (voti positivi da parte mia) se la funzione risultante è a tempo costante (ovvero nessun percorso di codice dipendente dai dati o accesso all'array o qualunque cosa la tua lingua supporti).
Paŭlo Ebermann,

Gli accessi all'array di @ PaŭloEbermann sono tempi costanti in molte lingue (sta aggiungendo un valore (ridimensionato) a un puntatore e dereferenziandolo, ecco perché una tabella di ricerca è così molto veloce)
maniaco del cricchetto

Gli accessi all'array di @ratchetfreak sono O (1), ma il tempo di accesso effettivo dipende, ad esempio, dagli accessi alla cache o dagli errori, il che porta ad attacchi del canale laterale su AES.
Paŭlo Ebermann,

@ PaŭloEbermann, ma puoi usare il codice più breve per riempire una tabella di ricerca, che si inserirà bene in una pagina di memoria.
Peter Taylor,

@ PaŭloEbermann e se la tabella 256 di lunghezza viene memorizzato insieme al codice (come enum generato al momento della compilazione) si quasi garantito una cache hit
cricchetto maniaco

Risposte:


6

Rubino, 161 caratteri

R=0..255
S=R.map{|t|t=b=R.select{|y|x=t;z=0;8.times{z^=y*(x&1);x/=2;y*=2};r=283<<8;8.times{r/=2;z^r<z/2&&z^=r};z==1}[0]||0;4.times{|r|t^=b<<1+r^b>>4+r};t&255^99}

Per controllare l'output è possibile utilizzare il seguente codice per stamparlo in forma tabellare:

S.map{|x|"%02x"%x}.each_slice(16){|l|puts l*' '}

7

GolfScript, 60 caratteri

{[[0 1{.283{1$2*.255>@*^}:r~^}255*].@?~)={257r}4*99]{^}*}:S;

Questo codice definisce una funzione denominata Sche accetta un byte e ad esso applica la S-box Rijndael. (Utilizza anche una funzione di supporto interna denominata rper salvare alcuni caratteri.)

Questa implementazione utilizza una tabella di logaritmi per calcolare le inversioni di GF (2 8 ), come suggerito da Thomas Pornin . Per salvare alcuni caratteri, l'intera tabella del logaritmo viene ricalcolata per ogni byte di input; anche così, e nonostante GolfScript sia un linguaggio molto lento in generale, questo codice impiega solo circa 10 ms per elaborare un byte sul mio vecchio laptop. Il pre-calcolo della tabella del logaritmo (as L) lo accelera fino a circa 0,5 ms per byte, al costo modesto di altri tre caratteri:

[0 1{.283{1$2*.255>@*^}:r~^}255*]:L;{[L?~)L={257r}4*99]{^}*}:S;

Per comodità, ecco un semplice cablaggio di prova che chiama la funzione S, come definita sopra, per calcolare e stampare l'intera S-box in esadecimale come su Wikipedia :

"0123456789abcdef"1/:h; 256, {S .16/h= \16%h= " "++ }% 16/ n*

Prova questo codice online.

(La demo online precalcola la tabella dei logaritmi per evitare di impiegare troppo tempo. Anche così, il sito GolfScript online a volte può andare in timeout in modo casuale; questo è un problema noto con il sito e una ricarica di solito lo risolve.)

Spiegazione:

Cominciamo con il calcolo della tabella dei logaritmi, e in particolare con la funzione helper r:

{1$2*.255>@*^}:r

Questa funzione accetta due input nello stack: un byte e una maschera di bit di riduzione (una costante tra 256 e 511). Duplica il byte di input, moltiplica la copia per 2 e, se il risultato supera 255, XORs con la maschera di bit per riportarlo sotto 256.

All'interno del codice di generazione della tabella di registro, la funzione rviene chiamata con la maschera di bit di riduzione 283 = 0x11b (che corrisponde al polinomio di riduzione Rijndael GF (2 8 ) x 8 + x 4 + x 3 + x + 1) e il risultato è XORed con il byte originale, moltiplicandolo efficacemente per 3 (= x + 1, come polinomio) nel campo finito di Rijndael. Questa moltiplicazione viene ripetuta 255 volte, a partire dal byte 1, e i risultati (più un byte zero iniziale) vengono raccolti in un array di 257 elementi Lche assomiglia a questo (parte centrale omessa):

[0 1 3 5 15 17 51 85 255 26 46 ... 180 199 82 246 1]

Il motivo per cui ci sono 257 elementi è che, con lo 0 anteposto e con 1 che si verificano due volte, possiamo trovare l'inverso modulare di un dato byte semplicemente osservando il suo indice (a base zero) in questo array, negandolo e osservando il byte nell'indice negato nello stesso array. (In GolfScript, come in molti altri linguaggi di programmazione, gli indici di array negativi contano all'indietro dalla fine dell'array.) In effetti, questo è esattamente ciò che fa il codice L?~)L=all'inizio della funzione S.

Il resto del codice chiama la funzione helper rquattro volte con la maschera di bit di riduzione 257 = 2 8 + 1 per creare quattro copie a rotazione di bit del byte di input invertito. Questi sono tutti raccolti in un array, insieme alla costante 99 = 0x63 e XORed insieme per produrre l'output finale.


7

x86-64 Codice macchina - 23 22 20 19 byte

Utilizza il set di istruzioni AES-NI

66 0F 6E C1          movd        xmm0,ecx
66 0F 38 DD C1       aesenclast  xmm0,xmm1
0F 57 C1             xorps       xmm0,xmm1  
66 0F 3A 14 C0 00    pextrb      eax,xmm0,0
C3                   ret

Utilizzando le convenzioni di chiamata di Windows, accetta un byte e genera un byte. Non è necessario invertire il ShiftRowsin quanto non influisce sul primo byte.


2
Per una volta, x86_64 tira una matematica, e ha un incorporato per questo.
moonheart08

6

La tabella può essere generata senza calcolare le inversioni nel campo finito GF (256), utilizzando i logaritmi. Sembrerebbe così (codice Java, usando intper evitare problemi con il bytetipo firmato ):

int[] t = new int[256];
for (int i = 0, x = 1; i < 256; i ++) {
    t[i] = x;
    x ^= (x << 1) ^ ((x >>> 7) * 0x11B);
}
int[] S = new int[256];
S[0] = 0x63;
for (int i = 0; i < 255; i ++) {
    int x = t[255 - i];
    x |= x << 8;
    x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7);
    S[t[i]] = (x ^ 0x63) & 0xFF;
}

L'idea è che 3 è un generatore moltiplicativo di GF (256) *. La tabella t[]è tale che t[x]è uguale a 3 x ; da 3 255 = 1, otteniamo che 1 / (3 x ) = 3 255-x .


non dovrebbe essere 0x1B(uno 1 nell'esagono letterale) invece di0x11B
maniaco del cricchetto

@ratchetfreak: no, deve essere 0x11B (ho provato). Il inttipo è a 32 bit in Java; Devo cancellare il bit più alto.
Thomas Pornin,

ah non me ne sono reso conto
maniaco del cricchetto

È un >>> invece di un >> nella riga 4?
Joe Z.

@JoeZeng: entrambi funzionerebbero. In Java, ">>>" è il "turno senza segno", ">>" è il "turno con segno". Differiscono da come gestiscono il bit di segno. In questo caso, i valori non saranno mai abbastanza ampi da consentire al bit di segno di essere diverso da zero, quindi non fa alcuna differenza reale.
Thomas Pornin,

6

GolfScript (82 caratteri)

{256:B,{0\2${@1$3$1&*^@2/@2*.B/283*^}8*;;1=},\+0=B)*:A.2*^4A*^8A*^128/A^99^B(&}:S;

Utilizza le variabili globali Ae B, e crea la funzione come variabile globale S.

L'inversione di Galois è forza bruta; Ho sperimentato di avere una mulfunzione separata che potrebbe essere riutilizzata per la trasformazione affine post-inversione, ma si è rivelata più costosa a causa del diverso comportamento di overflow.

Questo è troppo lento per una demo online: sarebbe scaduto anche nelle prime due righe della tabella.


Il mio è più veloce (e più breve;). +1 comunque.
Ilmari Karonen,

4

Python, 176 caratteri

Questa risposta è per la domanda-commento di Paŭlo Ebermann sul rendere costante la funzione. Questo codice si adatta al conto.

def S(x):
 i=0
 for y in range(256):
  p,a,b=0,x,y
  for j in range(8):p^=b%2*a;a*=2;a^=a/256*283;b/=2
  m=(p^1)-1>>8;i=y&m|i&~m
 i|=i*256;return(i^i/16^i/32^i/64^i/128^99)&255

La moltiplicazione essendo a tempo costante dipende dalla piattaforma (anche su piattaforme a 32 bit, ad esempio ARM Cortex M0). Vedi questa domanda correlata
fgrieu

1
@fgrieu Certo, ma queste sono tutte moltiplicazioni per costanti, che possono essere facilmente implementate in tempo costante usando turni e aggiunte.
Keith Randall,

2

d

ubyte[256] getLookup(){

    ubyte[256] t=void;
    foreach(i;0..256){
        t[i] = x;
        x ^= (x << 1) ^ ((x >>> 7) * 0x1B);
    }
    ubyte[256] S=void;
    S[0] = 0x63;
    foreach(i;0..255){
        int x = t[255 - i];
        x |= x << 8;
        x ^= (x >> 4) ^ (x >> 5) ^ (x >> 6) ^ (x >> 7);
        S[t[i]] = cast(ubyte)(x & 0xFF) ^ 0x63 ;
    }
    return S;

}

questo può generare la tabella di ricerca al momento della compilazione, potrei salvarne alcuni rendendo ubyte un parametro generico

modifica diretta ubytea ubytecolpo ricerche di matrice, senza ramificazione e loop completamente unrollable

B[256] S(B:ubyte)(B i){
    B mulInv(B x){
        B r;
        foreach(i;0..256){
            B p=0,h,a=i,b=x;
            foreach(c;0..8){
                p^=(b&1)*a;
                h=a>>>7;
                a<<=1;
                a^=h*0x1b;//h is 0 or 1
                b>>=1;
            }
            if(p==1)r=i;//happens 1 or less times over 256 iterations
        }
        return r;
    }
    B s= x=mulInv(i);
    foreach(j,0..4){
        x^=(s=s<<1|(s>>>7));
    }
    return x^99;
}

edit2 ha usato l'algo di @Thomas per creare la tabella di ricerca


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.