Rijndael的S-box是AES加密和解密中经常使用的操作。它通常实现为256字节查找表。这很快,但是这意味着您需要在代码中枚举256字节的查找表。我敢打赌,鉴于基本的数学结构,这种人群中的某人可以用更少的代码来完成这项工作。
用您喜欢的语言编写一个实现Rijndael的S-box的函数。最短的代码获胜。
Rijndael的S-box是AES加密和解密中经常使用的操作。它通常实现为256字节查找表。这很快,但是这意味着您需要在代码中枚举256字节的查找表。我敢打赌,鉴于基本的数学结构,这种人群中的某人可以用更少的代码来完成这项工作。
用您喜欢的语言编写一个实现Rijndael的S-box的函数。最短的代码获胜。
Answers:
{[[0 1{.283{1$2*.255>@*^}:r~^}255*].@?~)={257r}4*99]{^}*}:S;
该代码定义了一个名为name的函数S
,该函数接收一个字节并将Rijndael S-box应用于该字节。(它也使用一个名为的内部帮助器函数r
来保存一些字符。)
如Thomas Pornin所建议的,此实现使用对数表来计算GF(2 8)逆。为了节省一些字符,将为每个输入字节重新计算整个对数表。即便如此,尽管GolfScript通常是一种非常慢的语言,但是此代码仅用约10毫秒即可处理旧笔记本电脑上的一个字节。预先计算对数表(as )可以将其提高至每字节约0.5毫秒,而仅需花费三个字符即可:L
[0 1{.283{1$2*.255>@*^}:r~^}255*]:L;{[L?~)L={257r}4*99]{^}*}:S;
为了方便起见,下面是一个简单的测试工具,它调用S
如上定义的函数,以像Wikipedia上那样以十六进制形式计算和打印出整个S-box :
"0123456789abcdef"1/:h; 256, {S .16/h= \16%h= " "++ }% 16/ n*
(在线演示程序会预先计算对数表,以避免花费过多时间。即使如此,在线GolfScript网站有时可能会随机超时;这是该网站的已知问题,通常可以通过重新加载来解决。)
让我们从对数表计算开始,特别是从helper函数开始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)调用该函数,并对结果进行异或运算与原始字节有效地乘以Rijndael有限域中的3(= x + 1,作为多项式)。从字节1开始,此乘法重复255次,结果(加上一个初始零字节)被收集到一个257个元素的数组中L
,看起来像这样(中间部分省略):
[0 1 3 5 15 17 51 85 255 26 46 ... 180 199 82 246 1]
之所以有257个元素,是因为在0和1出现两次的情况下,我们可以简单地通过在此数组中查找(从零开始的)索引,对其求反,然后查找来查找任何给定字节的模逆。在同一数组中取反索引处的字节。(在GolfScript中,就像在许多其他编程语言中一样,负数组索引从数组末尾开始倒数。)确实,这正是L?~)L=
函数开头的代码S
所做的。
其余代码r
用缩减位掩码257 = 2 8 +1 调用了辅助函数四次,以创建反相输入字节的四个位旋转副本。这些都与常量99 = 0x63一起被收集到一个数组中,并进行异或运算以产生最终输出。
使用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
因为它不会影响第一个字节。
通过使用对数,可以在不计算有限域GF(256)中的逆的情况下生成表。看起来像这样(Java代码,int
用于避免带符号的byte
类型出现问题):
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
int
Java中的类型为32位;我必须取消较高的位。
{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
。
Galois反演是蛮力的。我尝试过使用一个单独的mul
函数,该函数可以在反演后仿射变换中重复使用,但是由于溢出行为不同,结果证明它更昂贵。
对于在线演示而言,这太慢了-即使在表的前两行也会超时。
这个答案是针对PaŭloEbermann关于使函数恒定时间的注释问题。此代码符合要求。
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
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用作通用参数来节省一些
直接编辑ubyte
至ubyte
无数组查找,无分支和完全展开的循环
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算法创建查找表