对FEAL-4的线性攻击
1. FEAL密码算法简介
FEAL密码算法家族是日本NTT(日本电报电话公司)的清水(Shimizi)和宫口(Miyaguchi)设计的(1986)。作为一种分组密码,与DES相比其主要想法为增加每一圈迭代的算法强度,因此可以通过减少迭代次数而提高运算速度。
FEAL-8即为8圈迭代的FEAL密码算法。FEAL密码算法推出之后,引起有关专家的注意。密码专家比哈姆和沙米尔利用差分密码分析技术发现,可以用比穷举法更快的速度破译FEAL密码。如FEAL-8只需2000个选择明文即可破译,而FEAL-4更只需8个精心选择的明文便可破译。
这个小密码系统用来做破译练手最合适不过了,本文对http://www.computing.dcu.ie/~mike/linear.html中的文章进行了学习,记录笔记如下。
2. FEAL密码算法结构
FEAL密码算法仅4圈,数据块为64位。最核心的便是轮函数f,输入、输出均32位。抗差分或线性分析均取决于此函数的设计。故用红色标出,其实Feistel结构本身的混淆是有限的。Feistel结构的优点是其轮函数可以是单向的,这样密码算法有更好的统计属性。而SPN网络的SBOX虽求逆困难,但不能单向,否则无法解密。
图1 FEAL密码结构
这里的密钥K0~K5均是从初始密钥K扩展而来,每个32位。而实际的攻击结果是完全攻破6*32=192位密钥。所以,密钥协商算法是单向的也没关系,可以把子密钥看做随机生成。
2.1 FEAL轮函数结构
图2 FEAL轮函数结构
轮函数的关键是非线性性,移位和异或都起到了这样的作用。输入为32位字,分为4个8位的小块。
3. FEAL密码算法线性攻击
线性攻击的本质是获得整个密码算法的线性表达式。可先分析轮函数的线性表达式,再分析整个密码算法的线性表达式。该表达式最终能化成只含明、密文及第1轮子密钥的式子。
a = S23,29(L0 Å R
3.1 轮函数中的线性表达式
直接贴来了,参见《Breaking Ciphers in the Real World》。本文省略了很多线性表达式由来的分析。
图3 轮函数中的线性表达式推导
由于轮函数的输入是32位,所以不可能像小SBOX一样,穷举所有差分。
3.2 整个密码算法的表达式推导
整个密码算法的线性表达式。该表达式最终能化成只含明、密文及第1轮子密钥的式子。a = S23,29(L0 Å R
图4 整个密码算法
3.3 初始密钥k0的获取
穷举所有的K0的值,看a的值是否对n个明、密文对在特定的K0下全为常数,若是,则很有可能是正确的K0.本质上a只与密钥相关,密钥定了,值就定了。a = S23,29(L0 Å R
3.4 复杂度的进一步化简
实际在a表达式中真正用到的就12位:10~15,17~23,其它位并不参与a值得运算,所以先穷举出这12位,再同样的技巧分析其它位,最后穷举剩余的位会更为简洁。同样的技巧可用于K1~K5的分析。
4. 试验结果
使用了60个已知明文、密文。在穷举10~15,17~23下,共4096种可能。看哪一种会使60个明文、密文的式子a全为常数0或1,则视为正确密钥10~15,17~23。可能的密钥有几个,但仅一个是正确的。
5. 附录代码
注:代码中的a表达式用的是下面的,推导见http://www.computing.dcu.ie/~mike/linear.html
a=Ph[10,16,18,26] ÅPl[10,18,26] Å Ch[10,16,18,26] Å Cl[16] Å F(PhÅPl,K1)[16] = K4[16,24] Å K6[10,18,26] Å K2[16,24]
- /*
- * The FEAL cipher - Linear Cryptanalysis
- */
- #include <stdio.h>
- #include <stdlib.h>
- /* uncomment this next statement to activate linear cryptanalysis code... */
- #define DEBUG
- #define WORD32 unsigned int
- #define BYTE unsigned char
- //循环左移技巧
- #define ROT2(x) (((x)<<2) | ((x)>>6))
- #define S0(a,b) (ROT2((BYTE)((a)+(b))))
- #define S1(a,b) (ROT2((BYTE)((a)+(b)+1)))
- //字节数组化字技巧
- static WORD32 pack32(BYTE *b)
- { /* pack 4 bytes into a 32-bit Word */
- return ((WORD32)b[3]<<24)|((WORD32)b[2]<<16)|((WORD32)b[1]<<8)|(WORD32)b[0];
- }
- //字化字节数组技巧
- static void unpack32(WORD32 a,BYTE *b)
- { /* unpack bytes from a 32-bit word */
- b[0]=(BYTE)a;
- b[1]=(BYTE)(a>>8);
- b[2]=(BYTE)(a>>16);
- b[3]=(BYTE)(a>>24);
- }
- WORD32 f(WORD32 x,WORD32 key)
- {
- BYTE f[4];
- BYTE k[4];
- unpack32(x,f);
- unpack32(key,k);
- f[2]^=(f[3]^k[2]);
- f[1]^=(f[0]^k[1]);
- f[2]=S1(f[2],f[1]);
- f[1]=S0(f[1],f[2]);
- f[3]=S0(f[3]^k[3],f[2]);
- f[0]=S1(f[0]^k[0],f[1]);
- /* printf("%02x%02x%02x%02x\n",f[3],f[2],f[1],f[0]); */
- return pack32(f);
- }
- /* H for High, L for Low */
- void feal_encrypt(BYTE data[8],WORD32 *key)
- {
- int i;
- WORD32 H,L,T;
- H=pack32(&data[4]);
- L=H^pack32(&data[0]);
- T=L;
- L=H^f(L,key[0]);
- H=T;
- T=L;
- L=H^f(L,key[1]);
- H=T;
- L^=key[4];
- H^=key[5];
- T=L;
- L=H^f(L,key[2]);
- H=T;
- T=L;
- L=H^f(L,key[3]);
- H=T;
- H^=L;
- unpack32(L,&data[4]);
- unpack32(H,&data[0]);
- }
- void feal_decrypt(BYTE data[8],WORD32 *key)
- {
- int i;
- WORD32 H,L,T;
- L=pack32(&data[4]);
- H=L^pack32(&data[0]);
- T=H;
- H=L^f(H,key[3]);
- L=T;
- T=H;
- H=L^f(H,key[2]);
- L=T;
- L^=key[4];
- H^=key[5];
- T=H;
- H=L^f(H,key[1]);
- L=T;
- T=H;
- H=L^f(H,key[0]);
- L=T;
- L^=H;
- unpack32(H,&data[4]);
- unpack32(L,&data[0]);
- }
- int main(int argc,char **argv)
- {
- int i,j,lhs,rhs;
- int Ph10161826,Pl101826,Ch10161826,Cl16,Ch16;
- BYTE input[10];
- BYTE data[8],k[4];
- WORD32 key[6],ph,pl,ch,cl,k1;
- BYTE bits[4096];
- int first_time_in=1;
- /* DELETED - Secret key generator. Sets keys key[0] ... key[5] */
- key[1]=key[2]=key[3]=key[4]=key[5]=0; /* not the real key ! */
- key[0]=0x2E3500; /* !! This is the first sub-key we find! */
- #ifndef DEBUG
- argc--; argv++;
- if (argc!=8)
- {
- printf("command line error - input 8 bytes of plaintext in hex\n");
- printf("For example:-\n");
- printf("findkey 0 1 2 3 4e 5a f6 37\n");
- return 0;
- }
- for (i=0;i<8;i++)
- sscanf(argv[i],"%x",&input[i]);
- for (i=0;i<8;i++) data[i]=input[7-i];
- printf("Plaintext= ");
- for (i=7;i>=0;i--) printf("%02x ",data[i]);
- printf("\n");
- feal_encrypt(data,key);
- printf("Ciphertext= ");
- for (i=7;i>=0;i--) printf("%02x ",data[i]);
- printf("\n");
- feal_decrypt(data,key);
- printf("Plaintext= ");
- for (i=7;i>=0;i--) printf("%02x ",data[i]);
- printf("\n");
- #else
- for (i=0;i<8;i++) data[i]=(BYTE)rand();
- /*
- the debug code below searches through all k1[8~13] and k1[16~21],
- as described by Matsui. That's 4096 possibilities. It outputs a
- 0 or a 1 for each possible key.
- For all known plaintexts/ciphertexts the right key will always give the
- same fixed 0 or 1
- For each plaintext/ciphertext pair newly discovered wrong keys
- are replaced with an X
- */
- /* Now try for 60 random plaintexts... */
- for (j=0;j<60;j++)
- {
- for (i=0;i<8;i++) data[i]=(BYTE)rand(); /* random plaintexts */
- printf("\nPlaintext= ");
- for (i=7;i>=0;i--) printf("%02x ",data[i]);
- printf("\n");
- pl=pack32(&data[0]);
- ph=pack32(&data[4]);
- Ph10161826=((ph>>10)^(ph>>16)^(ph>>18)^(ph>>26))&1;
- Pl101826 =((pl>>10)^(pl>>18)^(pl>>26))&1;
- feal_encrypt(data,key);
- printf("Ciphertext= ");
- for (i=7;i>=0;i--) printf("%02x ",data[i]);
- printf("\n");
- cl=pack32(&data[0]);
- ch=pack32(&data[4]);
- Ch10161826=((ch>>10)^(ch>>16)^(ch>>18)^(ch>>26))&1;
- Cl16=((cl>>16)&1);
- Ch16=((ch>>16)&1);
- for (i=0;i<4096;i++)
- {
- //无关的位置0,用到的&出来
- k[0]=0; k[1]=i&0x3F; k[2]=(i>>6)&0x3F; k[3]=0;
- k1=pack32(k);
- lhs=(Ph10161826^Pl101826^Ch10161826^Cl16^(f(ph^pl,k1)>>16))&1;
- if (first_time_in) bits[i]=lhs;
- else if (lhs!=bits[i]) bits[i]=0xFF;
- }
- /* print out the 4096 bit array */
- for (i=0;i<4096;i++)
- {
- if (bits[i]==0xFF)
- {
- printf("X");
- continue;
- }
- printf("%d",bits[i]);
- }
- first_time_in=0;
- }
- printf("\nCandidates for bits 8-13, and 16-21 of key[0] are\n");
- for (i=0;i<4096;i++)
- {
- if (bits[i]==0xFF) continue;
- k[0]=0; k[1]=i&0x3F; k[2]=(i>>6)&0x3F; k[3]=0;
- k1=pack32(k);
- printf("%x\n",k1);
- }
- printf("Actual key[0] we were looking for = %x\n",key[0]);
- #endif
- return 0;
- }