C : 상수 시간 코드로 AES FIPS-197 서브 바이트 테이블 대체


17

에서 FIPS-197 합니다 ( 고급 암호화 표준 AES로 알려진이)가 많이 사용되어 SubBytes구현 될 수있다,

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

이 기능은 임의적이지 않습니다. Galois Field의 반전과 아핀 변환으로 구성된 가역적 매핑입니다. 모든 세부 사항은 FIPS-197 섹션 5.1.1 또는 여기 섹션 4.2.1 (약간 다른 이름으로)에 있습니다.

테이블로 구현할 때의 한 가지 문제점은 소위 캐시 타이밍 공격에 노출 된다는 것 입니다.

따라서 당신의 임무는 SubBytes()일정한 시간 행동을 나타내는 위의 기능을 정확하게 대체하는 것입니다 . 우리는 입력 x에 따라 아무것도 사용되지 않는 경우라고 가정 SubBytes합니다.

  • 배열 인덱스로
  • 컨트롤의 피연산자로 if, while, for, case, 또는 조작자 ?:;
  • 연산자의 피연산자로서 &&, ||, !, ==, !=, <, >, <=, >=, *, /, %;
  • 사업자의 오른쪽 피연산자로 >>, <<, *=, /=, %=, <<=, >>=.

수상 항목 단항 연산자 대 (5)의 중량과, 상기 입력 종속 데이터 경로 실행 사업자의 수로부터 얻어지는 최저 비용으로 한 것 -~도만큼이나 <<1, >>1, +1, -1; 다른 모든 연산자의 경우 7의 가중치, 다른 계수와의 이동 또는 다른 상수의 추가 / 하위 (유형 캐스트 ​​및 프로모션은 무료) 원칙적으로이 비용은 롤링 루프 (있는 경우)에 의해 변경되지 않으며 입력과 무관합니다 x. 타이 브레이커로서 공백과 주석을 제거한 후 가장 짧은 코드를 가진 답이 이깁니다.

2013 년에 가능한 한 빨리 UTC로 항목을 답변으로 지정할 계획입니다. 내가 아는 언어로 된 대답을 고려하여 크기에 최적화되지 않은 C 로의 직선 번역으로 순위를 매길 것입니다.

무료 운영자 및 무료 캐스트 및 프로모션, 크기 순위의 초기 생략 +1-1선호에 대한 사과 . 참고 *때 단항, 곱셈으로 모두 금지됩니다.


1
조회를 상수로 인라인 할 수 있기 때문에 조회가 무료라는 점에 주목할 가치가 있습니다.
피터 테일러

"2013 년 초 UTC"– 시간대보다 날짜가 더 흥미롭지 않습니까?
Paŭlo Ebermann

@ PaŭloEbermann : 내 의도는 분명해야합니다.
fgrieu

답변:


13

점수 : 940 933 926 910, 필드 타워 접근

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

구조는 기본적으로 Boyar 및 Peralta의 구현과 동일합니다. GF (2 ^ 8)의 반전을 GF (2 ^ 4)의 반전으로 줄이고 선형 프롤로그, 비선형 몸체 및 선형 에필로그로 분류합니다. 그런 다음 별도로 최소화하십시오. 나는 비트 추출에 대한 몇 가지 처벌을 지불하지만 (의 비트에 대한 적절한 패딩으로) 병렬 작업을 수행함으로써 보상합니다 fwd. 더 자세하게...

배경

문제 설명에서 언급했듯이 S-box는 Galois 필드 GF (2 ^ 8)의 특정 구현에서 반전 후 아핀 변환으로 구성됩니다. 이 두 가지의 의미를 알고 있다면이 섹션을 건너 뛰십시오.

아핀 (또는 선형) 변환 함수 f(x)측면 f(x + y) = f(x) + f(y)f(a*x) = a*f(x).

필드는 F우리가 호출 할 두 개의 특수 요소 0와 호출 할 1두 개의 연산자 +*다양한 속성을 존중 하는 두 개의 연산자를 가진 요소 집합 입니다 . 이 섹션에서는, 그 가정 x, y그리고 z의 요소입니다 F.

  • 의 요소 F아래 형태 아벨 군 +0신원과 같이 즉, x + y의 원소이고 F; x + 0 = 0 + x = x; 각각 x대응 보유 -x되도록 x + (-x) = (-x) + x = 0; x + (y + z) = (x + y) + z; 그리고 x + y= y + x.
  • 요소의 F다른보다 0아래 형태 아벨 군 *1신원있다.
  • 곱셈은 ​​덧셈에 분포 x * (y + z) = (x * y) + (x * z)합니다.

유한 필드에는 상당히 심각한 제한이 있습니다.

  • 그것들은 소수의 힘인 많은 요소들을 가져야합니다.
  • 그들은 (즉,이 주어진 크기의 하나의 유한 체는 정말, 어떤 사람은 relabellings이다; 전화 필드 GF (p ^ k)는 어디 것과 같은 크기의 다른 모든 유한 필드 동형이다 p프라임과 k힘) .
  • F\{0}아래 의 곱셈기 *는 주기적이다; 즉, g모든 요소가의 거듭 제곱이되도록 하나 이상의 요소가 g있습니다.
  • 1보다 큰 거듭 제곱 k의 경우 소수의 필드의 일 변량 다항식으로 표현됩니다 . 예를 들어 GF (2 ^ 8)은 xGF (2) 이상의 다항식으로 표현됩니다 . 실제로는 일반적으로 둘 이상의 표현이 있습니다. x^7 * xGF에서 고려 (2 ^ 8); 7 차 다항식과 동일해야하지만 어느 것입니까? 올바른 구조를 제공하는 많은 선택이 있습니다. AES는 x^8 = x^4 + x^3 + x + 1(어휘 적으로 가장 작은 다항식)을 선택합니다.

그렇다면 우리는 GF (2 ^ 8)의 특정 표현에서 역수를 어떻게 계산합니까? 직접 해결하기에는 너무 번거롭기 때문에 문제를 해결해야합니다.

필드 타워 : GF (2 ^ 4)로 GF (2 ^ 8) 표현

GF (2)에 대해 8 항의 다항식으로 GF (2 ^ 8)을 나타내는 대신 GF (2 ^ 4)에 대해 2 항의 다항식으로 표현할 수 있습니다. 이번에는에 대한 선형 다항식을 선택해야합니다 x^2. 우리가 선택한다고 가정하자 x^2 = px + q. 그런 다음 (ax + b) * (cx + d) = (ad + bc + acp)x + (bd + acq).

이 표현에서 역을 찾는 것이 더 쉬운가요? (cx + d) = (ax + b)^-1우리가 동시에 방정식을 얻는 다면

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

하자 D = [b(b+ap) + a^2 q]및 세트 c = a * D^-1; d = (b + ap) * D^-1. 따라서 우리는 GF (2 ^ 4) 로의 변환 비용, GF (2 ^ 4)의 역수 및 덧셈과 곱셈, 그리고 다시 변환 비용으로 GF (2 ^ 8)에서 역수를 할 수 있습니다. 테이블을 사용하여 반대를 수행하더라도 테이블 크기를 256에서 16으로 줄였습니다.

구현 세부 사항

GF (4)의 표현을 구성하기 위해 3 개의 다항식 중 하나를 선택하여 줄일 수 있습니다 x^4.

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

가장 중요한 차이점은 곱셈 구현에 있습니다. 세 가지 중 하나 ( poly3, 9, f에 해당)에 대해 다음이 작동합니다.

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

그러나 우리가 선택 poly = 3하면 오버플로는 훌륭한 구조를 갖기 때문에 훨씬 효율적으로 처리 할 수 ​​있습니다. 두 개의 입력이 모두 입방체이고 입방체이기 때문에 이중 오버플로가 없습니다 x^6 = x^2 (x + 1). 또한 b오버 플로우를 마지막으로 남겨두기 때문에 또는 1에 a0 x^2해당하는 세트 비트가 없으므로 x-1 대신 -4로 마스킹 할 수 있습니다. 결과는

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

우리는 여전히 GF (2 ^ 4)보다 GF (2 ^ 8) 의 값 pq표현 을 선택해야하지만, 그에 대한 제약은 많지 않습니다. 중요한 것은 원래 표현의 비트에서 작업 표현의 비트까지 선형 함수를 얻을 수 있다는 것입니다. 이것은 두 가지 이유에서 중요합니다. 첫째, 선형 변환이 쉬우 며, 비선형 변환은 전체 S-box를 최적화하는 데 어려움이있는 최적화와 동등해야합니다. 둘째, 부수적 인 혜택을 얻을 수 있기 때문입니다. 구조를 요약하면 다음과 같습니다.

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
}

비트 xx7 x6 ... x0그렇다면 변환이 선형이므로을 얻습니다 a = f({x7}0000000 + 0{x6}000000 + ... + 0000000{x0}) = f({x7}0000000) + f(0{x6}000000) + ... + f(0000000{x0}). 제곱 a^2 = f({x7}0000000)^2 + f(0{x6}000000)^2 + ... + f(0000000{x0})^2하면 교차 항이 취소되는 위치 를 얻 습니다 (GF (2) 때문에1 + 1 = 0 ). 따라서 a^2의 선형 함수로 계산할 수도 있습니다 x. 순방향 선형 변환을 보강하여 다음을 얻을 수 있습니다.

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

우리는 세 번의 곱셈과 한 번의 덧셈으로 진행됩니다. 곱셈 코드를 확장하여 두 곱셈을 Dinv병렬 로 수행 할 수도 있습니다 . 따라서 우리의 총 비용은 순 선형 변환, 덧셈, 두 곱셈, GF (2 ^ 4)의 역수 및 역 선형 변환입니다. 우리는 S- 박스의 사후 역 선형 변환을 수행하여 본질적으로 무료로 얻을 수 있습니다.

선형 변환에 대한 계수의 계산은 그다지 흥미롭지 않으며 여기에 마스크를 저장하고 이동하는 미세 최적화도 아닙니다. 나머지 흥미로운 부분은inverse_GF16. 4 비트에서 4 비트까지 2 ^ 64 개의 서로 다른 기능이 있으므로 직접 최적화에는 많은 메모리와 시간이 필요합니다. 내가 한 것은 4 비트에서 1 비트까지 4 개의 함수를 고려하고 하나의 함수에 허용되는 총 비용을 제한하는 것입니다 (함수 당 최대 63의 비용으로 1 분 이내에 모든 적합한 표현을 열거 할 수 있음). 각 함수 튜플마다 공통 하위 표현식 제거를 수행합니다. 25 분의 크 런칭 후, 그 캡으로 가능한 최고의 역수의 총 비용은 133입니다 (출력 비트 당 평균 33.25의 평균, 개별 비트에 대한 가장 저렴한 표현이 35라는 것을 고려하면 나쁘지 않습니다) .

나는 여전히 GF (2 ^ 4)의 반전을 최소화하기 위해 다른 접근법을 실험하고 있으며 하향식이 아닌 상향식을 만드는 접근법은 133에서 126로 향상되었습니다.


환상적인! 작동하는지 확인합니다! 세부 사항 : 8이 & 1트리밍 될 수있다. (ESP 경우 x이다 unsigned char; CHAR_BITcodegolf 8이다).
fgrieu

@fgrieu, 좋은 지적입니다.
피터 테일러

8

점수 : 980 = 7 * 5 + 115 * 7 + 7 * 5 + 15 * 7, Boyar and Peralta의 최소화

Joan Boyar와 René Peralta의 암호 응용 프로그램과 함께 새로운 조합 논리 최소화 기술을 찾았습니다 . 방정식을 도출하는 데 사용되는 기술 은 미국에 의해 특허를 받았습니다. 나는 방금 그들의 방정식을 C 형으로 직접 번역했는데, 여기에 친절하게 연결되어 있다 .

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

와우, 정말 작동하고 정말 저렴합니다. 분해 할 때 프롤로그, 에필 로지 및 이동 명령을 제외한 실제로 144 개의 명령입니다.
ugoren

5

점수 : 10965

이것은 배열 조회를 언 롤링하는 것과 동일한 원칙을 사용합니다. 추가 캐스트가 필요할 수 있습니다.

개선 방법을 지적 해 주신 ugoren에게 감사드립니다 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
정수 c의 경우 (c|-c)>>310은 0이고 그렇지 않으면 -1입니다.
ugoren

현명한 언어로 @ugoren. 예. C에서 부호없는 유형을 오른쪽으로 이동하는 것은 지정되어 있지 않습니다.
피터 테일러

1
서명 한 것 같아요 그러나이 사이트는 엄격한 표준 준수로 유명하지 않습니다. 또한, 당신 c >> 4은 저에게 서명 된 올바른 전환처럼 보입니다. 그리고 만약 당신이 정말로 주장한다면 – ((unsigned int)(c|-c))>>31입니다 c?1:0.
ugoren

@ugoren, 네 말이 맞아 서명했다. 는 c >>4또는 부호 확장없이 작동합니다. 그러나 서명되지 않은 교대를 사용하는 것이 좋습니다 : 집에 돌아갈 때 편집하고 전화가 아닌 적절한 컴퓨터를 사용할 수 있습니다. 감사.
피터 테일러

3

점수 : 9109, 대수적 접근

누구든지 대폭 개선 할 수있는 경우를 대비하여 조회 접근 방식을 그대로 사용하지만 좋은 대수적 접근 방식이 가능하다는 것이 밝혀졌습니다. 이 구현은 Euclid의 알고리즘을 사용하여 곱셈 역 찾습니다 . Java로 작성했지만 원칙적으로 C로 이식 할 수 있습니다 .8 비트 유형 만 사용하여 다시 작업하려는 경우 변경 될 부분을 언급했습니다.

is_nonzero다른 답변에 대한 의견을 확인 하는 방법을 알려 주신 ugoren에게 감사드립니다 .

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

점수 : 256 * (7+ (8 * (7 + 7 + 7)-(2 + 2)) + 5 + 7 + 7) = 48640 (루프가 롤링되지 않은 것으로 가정)

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

설명:

본질적으로 배열 연산자는 비트 연산자를 사용하여 다시 구현되고 항상 전체 배열을 처리합니다. 우리는 배열을 반복하고 xor x를 각 인덱스와 함께 반복 한 다음 비트 연산자를 사용하여 결과를 논리적으로 무효화하므로 255를 얻습니다.x=i 그렇지 않으면 0을 . 선택한 값이 변경되지 않고 다른 값이 0이되도록 배열 값을 비트 단위로 사용합니다. 그런 다음이 배열의 비트 단위를 사용하여 선택한 값만 가져옵니다.

1 << j연산은 루프 풀림의 일부로 사라지고 1에서 128 사이의 2의 거듭 제곱으로 대체됩니다.


이제 비트 연산자를 사용하여 실제로 수학을 수행 할 수 있는지 확인하십시오.
histocrat

here 알고리즘을 살펴보면 다항식 시간 단계 중 일부를 대신하여 모든 바이트를 적어도 한 번 반복하지 않고 다항식 반전을 구현할 수있을 것입니다. 따라서 이것은 "스마트 한"솔루션을 능가 할 수 있습니다. 이 상수 배열 검색을 조정하는 것이 더 유망한 방법이라고 생각합니다.
histocrat

좋은. aes.c의 기능 rj_sbox 여기가 (한,이 문제와 일치하지 않는,하지만) 영감을 줄 수 있습니다.
fgrieu

어디 않습니다 -(2+2)당신의 점수 계산은 어디서? 편집 : 아, 인라인은 a <<1와 a를 만듭니다 >>1.
피터 테일러

0

점수 1,968 조회 테이블을 사용하여, 1692을

참고 :이 솔루션은로 인해 기준을 통과하지 못합니다 w >> b.

찾아보기 테이블을 사용하지만 한 번에 8 바이트를 읽습니다.
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
}

나는 이것이 w>>bRHS가 다음과 같이 계산 했기 때문에 이것이 질문에서 일정한 시간의 정의를 충족시키지 않는다고 생각합니다x
Peter Taylor

여러 위반 사항이 있습니다 w >> b어디에 b입력에 따라; 또한 x/8, x%8*= (1-badi). 첫 번째는 특히 저가형 CPU의 타이밍 종속성으로 저하 될 가능성이 높습니다. 그러나 넓은 변수를 사용한다는 아이디어는 확실합니다.
fgrieu

지시 사항을 충분히 읽지 못했습니다. 나는 대부분의 문제를 고칠 수 w >> b는 있지만 매우 중요합니다 (모든 것을 다시 쓰지 않고 고칠 수 있는지 확인해야합니다)
ugoren

0

테이블 조회 및 마스크, 점수 = 256 * (5 * 7 + 1 * 5) = 10240

마스크와 함께 테이블 조회를 사용하여 원하는 결과 만 선택합니다. j|-j음수 (i! = x 인 경우) 또는 0 (i == 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 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;
}

최적화 수준이 낮다는 점을 제외하면 두 번째 답변과 동일하지 않습니까?
피터 테일러

닫습니다. 서명 된 시프트를 사용하므로 끝에 -1을 수행 할 필요가 없습니다.
Keith Randall
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.