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
intJava中的类型为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算法创建查找表