Rijndael의 S-box 구현


15

Rijndael의 S-boxAES 암호화 및 암호 해독 에서 자주 사용되는 작업입니다 . 일반적으로 256 바이트 조회 테이블로 구현됩니다. 빠르지 만 코드에 256 바이트 조회 테이블을 열거해야 함을 의미합니다. 나는이 군중의 누군가가 기본 수학 구조를 감안할 때 적은 코드로 그것을 할 수 있다고 확신합니다.

Rijndael의 S-box를 구현하는 좋아하는 언어로 함수를 작성하십시오. 가장 짧은 코드가 승리합니다.


1
결과 함수가 일정 시간 인 경우 보너스 포인트 (나에게서 찬성) (즉, 데이터 종속 코드 경로 또는 배열 액세스가 없거나 언어가 지원하는 것이 없음).
Paŭlo Ebermann

@ PaŭloEbermann 어레이 액세스는 여러 언어로 일정한 시간입니다 (포인터에 (스케일링 된) 값을 추가하고 참조 해제하기 때문에 조회 테이블이 매우 빠릅니다)
ratchet freak

@ratchetfreak 어레이 액세스는 O (1)이지만 실제 액세스 시간은 캐시 적중 또는 누락에 따라 다릅니다 (예 : AES에 대한 사이드 채널 공격으로 이어짐).
Paŭlo Ebermann

@ PaŭloEbermann이지만 더 짧은 코드를 사용하여 조회 테이블을 채울 수 있으며, 이는 메모리 페이지 아래에 잘 맞습니다.
피터 테일러

@ PaŭloEbermann 그리고 256 길이 테이블이 코드를 따라 저장되면 (컴파일시 생성 된 열거 형) 캐시 적중을 거의 보장 할 수 있습니다.
ratchet freak

답변:


6

루비, 161 자

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}

출력을 확인하기 위해 다음 코드를 사용하여 테이블 형식으로 인쇄 할 수 있습니다.

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

7

GolfScript, 60 자

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

이 코드는 S바이트 단위로 이름 이 지정된 함수를 정의 하고 Rijndael S 상자를 적용합니다. (또한 r몇 개의 문자를 저장하기 위해 명명 된 내부 도우미 함수를 사용합니다 .)

이 구현은 Thomas Pornin에서 제안한 대로 로그 테이블을 사용하여 GF (2 8 ) 역 을 계산합니다 . 몇 개의 문자를 저장하기 위해 각 입력 바이트에 대해 전체 로그 테이블이 다시 계산됩니다. 그럼에도 불구하고 GolfScript가 일반적으로 매우 느린 언어 임에도 불구 하고이 코드는 오래된 랩톱에서 바이트를 처리하는 데 약 10ms가 걸립니다. 로그 테이블을 미리 계산하면 (와 같이 ) 3 자 이상의 추가 비용을 들이지 않고 바이트 당 최대 약 0.5ms의 속도를냅니다.L

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

편의를 위해 S위의 정의에 따라 함수를 호출하여 위키 백과 에서와 같이 S- 박스 전체를 16 진수로 계산하고 인쇄 하는 간단한 테스트 하네스가 있습니다 .

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

이 코드를 온라인으로 사용해보십시오.

(온라인 데모는 너무 많은 시간이 걸리지 않도록 로그 테이블을 미리 계산합니다. 그럼에도 불구하고 온라인 GolfScript 사이트는 때때로 시간 초과 될 수 있습니다. 이는 사이트의 알려진 문제이므로 일반적으로 다시로드하면 문제가 해결됩니다.)

설명:

로그 테이블 계산, 특히 도우미 함수부터 시작하겠습니다 r.

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

이 함수는 스택에서 바이트와 축소 비트 마스크 (256과 511 사이의 상수)의 두 가지 입력을받습니다. 입력 바이트를 복제하고 사본에 2를 곱한 다음 결과가 255를 초과하면 비트 마스크와 함께 XOR하여 256 미만으로 되돌립니다.

로그 테이블 생성 코드 내에서 r감소 비트 마스크 283 = 0x11b ( Rijndael GF (2 8 ) 감소 다항식 x 8 + x 4 + x 3 + x + 1)로 함수 가 호출 되고 결과는 XOR입니다 원래 바이트 를 사용하여 Rijndael 유한 필드에서 3 ( 다항식으로 = x + 1)을 효과적으로 곱 합니다. 이 곱셈은 바이트 1부터 255 번 반복되며 결과 (초기 0 바이트 포함)는 L다음과 같은 257 요소 배열로 수집됩니다 (중간 부분은 생략 됨).

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

257 개의 요소가있는 이유는 앞에 0이 붙고 1이 두 번 발생하면이 배열에서 (0부터 시작) 인덱스를 찾아서 무시하고 살펴보면 주어진 바이트의 모듈러 역수를 찾을 수 있기 때문입니다. 같은 배열의 부정 인덱스의 바이트를 증가시킵니다. 다른 많은 프로그래밍 언어와 마찬가지로 GolfScript에서는 음수 배열 인덱스가 배열의 끝부터 거꾸로 계산됩니다. 실제로 L?~)L=함수 시작 부분의 코드가하는 것과 정확히 같습니다 S.

나머지 코드 r는 축소 비트 마스크 257 = 2 8 + 1을 사용 하여 도우미 함수를 네 번 호출 하여 반전 된 입력 바이트의 비트 회전 복사본 4 개를 만듭니다. 이것들은 모두 상수 99 = 0x63과 함께 배열로 수집되며 XOR은 함께 최종 출력을 생성합니다.


7

x86-64 기계 코드 -23 22 20 19 바이트

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

Windows 호출 규칙을 사용하여 바이트를 받아서 바이트를 출력합니다. ShiftRows첫 번째 바이트에 영향을 미치지 않기 때문에 를 뒤집을 필요는 없습니다 .


2
x86_64는 한 번 수학을 풀고 내장되어 있습니다.
moonheart08

6

로그를 사용하여 유한 필드 GF (256)에서 역을 계산하지 않고 테이블을 생성 할 수 있습니다. 다음과 같이 표시됩니다 ( int서명 된 byte유형의 문제점을 피하기 위해 사용 하는 Java 코드 ).

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

아이디어는 3이 GF (256) *의 곱셈 생성기라는 것입니다. 테이블 t[]t[x]3 x 와 같습니다 . 3 이후 255 = 1, 우리는 (1) / (3 것을 얻을 X가 ) 3 = 255-X를 .


그것은 안 0x1B대신 (육각 문자 하나 1)0x11B
래칫 괴물

@ratchetfreak : 아니요, 0x11B 여야합니다 (시도했습니다). int유형은 자바 32 비트입니다; 더 높은 비트를 취소해야합니다.
Thomas Pornin

아는 몰랐어
래칫 괴물

4 행의 >> 대신 >>>입니까?
Joe Z.

@ JoeZeng : 둘 다 작동합니다. Java에서 '>>>'는 "서명되지 않은 시프트"이고 ">>"는 "서명 된 시프트"입니다. 부호 비트를 처리하는 방법에 따라 다릅니다. 여기서 부호 비트가 0이 아닌 값은 충분히 넓지 않으므로 실제 차이는 없습니다.
Thomas Pornin

6

GolfScript (82 자)

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

글로벌 변수를 사용 A하고 B, 글로벌 변수와 함수를 생성한다 S.

갈루아 역전은 무차별 적입니다. 나는 mul반전 후 아핀 변환에 재사용 할 수 있는 별도의 함수를 실험 했지만 다른 오버 플로우 동작으로 인해 더 비싸다는 것이 밝혀졌습니다.

온라인 데모에서는 속도가 너무 느립니다. 테이블의 처음 두 행에서도 시간이 초과됩니다.


내 것이 더 빠르며 짧습니다. 어쨌든 +1.
Ilmari Karonen

4

파이썬, 176 자

이 답변은 함수를 일정하게 유지하는 것에 대한 Paŭlo Ebermann의 의견 질문에 대한 것입니다. 이 코드는 청구서에 적합합니다.

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

상수 시간의 곱셈은 플랫폼에 따라 다릅니다 (예 : ARM Cortex M0과 같은 32 비트 플랫폼에서도). 이 관련 질문보기
fgrieu

1
@fgrieu 물론, 이것들은 모두 상수에 의한 곱셈이며, 쉬프트와 덧셈을 사용하여 일정한 시간에 쉽게 구현할 수 있습니다.
Keith Randall

2

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;

}

이것은 컴파일 타임에 룩업 테이블을 생성 할 수 있으며, ubyte를 일반 매개 변수로 만들어 일부를 절약 할 수 있습니다

편집은 직접하지 ubyteubyte, 배열 조회없이 아무 분기와 완전히 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는 룩업 테이블을 생성하기 위해 @Thomas 'algo를 사용했습니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.