Implementar a S-box de Rijndael

15

A S-box de Rijndael é uma operação frequentemente usada na criptografia e descriptografia AES . Geralmente é implementado como uma tabela de pesquisa de 256 bytes. Isso é rápido, mas significa que você precisa enumerar uma tabela de pesquisa de 256 bytes no seu código. Aposto que alguém nesta multidão poderia fazê-lo com menos código, dada a estrutura matemática subjacente.

Escreva uma função no seu idioma favorito que implemente a S-box de Rijndael. O menor código vence.

Keith Randall
fonte
1
Pontos de bônus (upvotes para mim) se a função resultante for de tempo constante (ou seja, nenhum caminho de código dependente de dados ou acesso à matriz ou o que seu idioma suportar).
Paŭlo Ebermann 4/10/11
@ PaŭloEbermann acessos matriz são de tempo constante em muitas línguas (é a adição de um valor (em escala) para um ponteiro e dereferencing-lo, é por isso que uma tabela de pesquisa é muito rápido)
catraca aberração
@ratchetfreak Os acessos à matriz são O (1), mas o tempo real de acesso depende de acertos ou erros do cache, por exemplo, o que leva a ataques de canal lateral no AES.
Paŭlo Ebermann 4/10/11
@ PaŭloEbermann, mas você pode usar o código mais curto para preencher uma tabela de pesquisa, que se encaixará bem em uma página de memória.
Peter Taylor
@ PaŭloEbermann e se a tabela de 256 de comprimento é armazenado ao longo do código (como enum gerado em tempo de compilação), você quase garantido um acerto de cache
aberração catraca

Respostas:

6

Ruby, 161 caracteres

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}

Para verificar a saída, você pode usar o seguinte código para imprimi-lo em forma de tabela:

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

GolfScript, 60 caracteres

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

Esse código define uma função nomeada Sque recebe um byte e aplica a caixa S de Rijndael. (Ele também usa uma função auxiliar interna chamada rpara salvar alguns caracteres.)

Essa implementação usa uma tabela de logaritmo para calcular os inversos GF (2 8 ), conforme sugerido por Thomas Pornin . Para salvar alguns caracteres, toda a tabela de logaritmos é recalculada para cada byte de entrada; mesmo assim, e apesar do GolfScript ser uma linguagem muito lenta em geral, esse código leva apenas 10 ms para processar um byte no meu laptop antigo. O pré-cálculo da tabela de logaritmo (as L) acelera-a para cerca de 0,5 ms por byte, ao custo modesto de mais três caracteres:

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

Por conveniência, aqui está um equipamento de teste simples que chama a função S, conforme definido acima, para calcular e imprimir toda a caixa S em hexadecimal, como na Wikipedia :

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

Experimente este código online.

(A demonstração on-line pré-calcula a tabela de logaritmo para evitar muito tempo. Mesmo assim, o site GolfScript on-line às vezes pode exceder o tempo limite; esse é um problema conhecido do site, e uma atualização geralmente o corrige.)

Explicação:

Vamos começar com o cálculo da tabela de logaritmos e, especificamente, com a função auxiliar r:

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

Essa função recebe duas entradas na pilha: um byte e uma máscara de bits de redução (uma constante entre 256 e 511). Duplica o byte de entrada, multiplica a cópia por 2 e, se o resultado exceder 255, XORs com a máscara de bits para trazê-lo de volta para 256.

Dentro do código de geração da tabela de log, a função ré chamada com a máscara de bits de redução 283 = 0x11b (que corresponde ao polinômio de redução de Rijndael GF (2 8 ) x 8 + x 4 + x 3 + x + 1) e o resultado é XOR com o byte original, multiplicando-o efetivamente por 3 (= x + 1, como um polinômio) no campo finito de Rijndael. Essa multiplicação é repetida 255 vezes, iniciando no byte 1, e os resultados (mais um byte zero inicial) são coletados em uma matriz de 257 elementos Lque se parece com isso (parte do meio omitida):

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

A razão pela qual existem 257 elementos é que, com o 0 precedido e o 1 ocorrendo duas vezes, podemos encontrar o inverso modular de qualquer byte dado simplesmente pesquisando seu índice (com base em zero) nessa matriz, negando-o e procurando o byte no índice negado na mesma matriz. (No GolfScript, como em muitas outras linguagens de programação, os índices negativos da matriz contam para trás a partir do final da matriz.) Na verdade, é exatamente isso que o código L?~)L=no início da função Sfaz.

O restante do código chama a função auxiliar rquatro vezes com a máscara de bit de redução 257 = 2 8 + 1 para criar quatro cópias com rotação de bits do byte de entrada invertido. Todos eles são coletados em uma matriz, juntamente com a constante 99 = 0x63, e XORed juntos para produzir a saída final.

Ilmari Karonen
fonte
7

x86-64 Código da máquina - 23 22 20 19 bytes

Usa o conjunto de instruções 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

O uso de convenções de chamada do Windows recebe um byte e gera um byte. Não é necessário reverter o valor ShiftRows, pois isso não afeta o primeiro byte.

mim'
fonte
2
Pela primeira vez, x86_64 puxa um mathematica e tem um builtin para isso.
moonheart08
6

A tabela pode ser gerada sem computar inversos no campo finito GF (256), usando logaritmos. Seria assim (código Java, usando intpara evitar problemas com o bytetipo assinado ):

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

A idéia é que 3 é um gerador multiplicativo de GF (256) *. A tabela t[]é tal que t[x]é igual a 3 x ; desde 3 255 = 1, obtemos esse 1 / (3 x ) = 3 255-x .

Thomas Pornin
fonte
não deveria ser 0x1B(um 1 no literal hex) em vez de0x11B
aberração catraca
@ratchetfreak: não, deve ser 0x11B (tentei). O inttipo é de 32 bits em Java; Eu devo cancelar a parte superior.
Thomas Pornin
ah não percebeu que
catraca aberração
Isso é um >>> em vez de um >> na linha 4?
Joe Z.
@ JoeZeng: ambos funcionariam. Em Java, '>>>' é o "turno não assinado", '>>' é o "turno assinado". Eles diferem na maneira como lidam com o bit de sinal. Aqui, os valores nunca serão grandes o suficiente para que o bit de sinal seja diferente de zero, portanto, não faz diferença real.
Thomas Pornin
6

GolfScript (82 caracteres)

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

Usa variáveis ​​globais Ae B, e cria a função como variável global S.

A inversão de Galois é força bruta; Eu experimentei ter uma mulfunção separada que poderia ser reutilizada para a transformação afina pós-inversão, mas acabou sendo mais cara devido ao comportamento diferente do estouro.

Isso é muito lento para uma demonstração on-line - atingia o tempo limite até nas duas primeiras linhas da tabela.

Peter Taylor
fonte
O meu é mais rápido (e mais curto;). +1 de qualquer maneira, no entanto.
Ilmari Karonen
4

Python, 176 caracteres

Esta resposta é para a pergunta-comentário de Paŭlo Ebermann sobre tornar a função constante. Este código se encaixa na conta.

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
Keith Randall
fonte
A multiplicação em tempo constante depende da plataforma (mesmo em plataformas de 32 bits, por exemplo, ARM Cortex M0). Veja esta questão relacionada
fgrieu
1
@fgrieu Claro, mas todas essas são multiplicações por constantes, que podem ser facilmente implementadas em tempo constante usando turnos e adições.
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;

}

isso pode gerar a tabela de pesquisa em tempo de compilação, eu poderia economizar alguns tornando o ubyte um parâmetro genérico

edite diretamente ubytepara, ubytesem pesquisas de array, sem ramificações e loops totalmente desenroláveis

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 usou o algo do @Thomas para criar a tabela de pesquisa

catraca arrepiante
fonte
0

Stax , 53 bytes

ë■ÿÆ♂◄º5hUø√!«mr¿¡ƒR3Å←ç≥#/$taJkαn∩╓▒ÿ╔τy╫π§╪S♫╔┴H╔║Ö

Execute e depure

Eu não tenho nenhum entendimento particular de S-boxes. Esta é uma conversão da solução de Thomas Pornin (8 anos!) .

recursivo
fonte