对FEAL-4的差分攻击

1. FEAL密码算法简介

FEAL密码算法家族是日本NTT(日本电报电话公司)的清水(Shimizi)和宫口(Miyaguchi)设计的(1986)。作为一种分组密码,与DES相比其主要想法为增加每一圈迭代的算法强度,因此可以通过减少迭代次数而提高运算速度。

FEAL-8即为8圈迭代的FEAL密码算法。FEAL密码算法推出之后,引起有关专家的注意。密码专家比哈姆和沙米尔利用差分密码分析技术发现,可以用比穷举法更快的速度破译FEAL密码。如FEAL-8只需2000个选择明文即可破译,而FEAL-4更只需8个精心选择的明文便可破译。

这个小密码系统用来做破译练手最合适不过了,本文对http://www.theamazingking.com/crypto-feal.php中的文章进行了学习,记录笔记如下。

2. FEAL密码算法结构

FEAL密码算法仅4圈,数据块为64位。最核心的便是轮函数f,输入、输出均32位。抗差分或线性分析均取决于此函数的设计。故用红色标出,其实Feistel结构本身的混淆是有限的。Feistel结构的优点是其轮函数可以是单向的,这样密码算法有更好的统计属性。而SPN网络的SBOX虽求逆困难,但不能单向,否则无法解密。

对FEAL-4的差分攻击_休闲

图1 FEAL密码结构

这里的密钥K0~K5均是从初始密钥K扩展而来,每个32位。而实际的攻击结果是完全攻破6*32=192位密钥。所以,密钥协商算法是单向的也没关系,可以把子密钥看做随机生成。

2.1 FEAL轮函数结构

对FEAL-4的差分攻击_FEAL_02

图2 FEAL轮函数结构

轮函数的关键是非线性性,移位和异或都起到了这样的作用。输入为32位字,分为4个8位的小块。

3. FEAL密码算法差分攻击

差分攻击的本质是利用非线性变换的并非完全随机性。即对于特定的输入差分,输出的差分并不是均匀分布的。

3.1 概率为100%的差分特征

对FEAL-4的差分攻击_网络安全_03

图3 概率为100%的差分特征

由于轮函数的输入是32位,所以不可能像小SBOX一样,穷举所有差分。

3.2概率为100%的差分特征原理

这里要把轮函数分解为(a+b+x mod 255)<&lt;2,首先+x对差分来说可忽略。而mod 255对输入差分为0x80,0x00来说,其输出必然0x80。参见图4.(详细解释见原文)

对FEAL-4的差分攻击_网络安全_04

图4 概率为100%的差分特征

对于轮函数对差分的跟踪,如图5.

对FEAL-4的差分攻击_网络安全_05

图5 轮函数对差分的跟踪

3.3 概率为100%的4轮差分特征的寻找

这里应用了中间相遇攻击的原理,即从前向后利用明文差分找到两轮输入差分。同时,从后向前,利用密文差分找到两轮差分。

对FEAL-4的差分攻击_职场_06

图6 4轮差分特征

利用输入明文差分P0 XOR P1 = 0x8080000080800000 可以如图6追踪前两轮的差分传播。但只能分析到红色部分之前。因为 0x02000000作为输入差分是很难预测其输出差分的。

而若想找到最后一轮的子密钥k3,则需要知道最后一轮函数f 的输出差分。而该输出差分恰好等于左边32位的密文差分与0x02000000的异或。

3.4 最后一轮的子密钥k3的获取

这里的关键点在于最后一轮函数的输入差分及输出差分的获取,由图7可知,最后一轮函数f的输入差分可通过穷举密钥获得。这里看似输入差分似乎与子密钥k3没有关系,但子密钥k3能影响具体的输出差分(已经计算出)的值。正确的子密钥k3会使其输出差分完全吻合,即该子密钥的分数为6,见伪码图8

对FEAL-4的差分攻击_职场_07

图7 对密钥的寻找

对FEAL-4的差分攻击_职场_08

图8 伪代码

4. 试验结果

对于6个明、密文对,每次总能找到4个可能的最后一轮子密钥,但如何排除?(增加更多的明、密文对并不能排除,换条差分路径也许可以?)对其他轮子密钥,在已知第四轮子密钥的情况下,可效仿求第三轮子密钥。但结果亦不唯一。只能在找到的结果中用穷举去排除。只要结果比穷举2^64小(初始密钥64位),就算成功。

5. 附录代码

 

  1. //Differential Cryptanalysis of FEAL-4  
  2.  
  3. //Uses a chosen-plaintext attack to fully recover the last round subkey  
  4.  
  5. //For use with tutorial at http://theamazingking.com/crypto-feal.php  
  6.  
  7. //Hack the Planet!  
  8.  
  9. #include &lt;stdio.h>  
  10.  
  11. #include <math.h>  
  12.  
  13. //subkey为全局变量  
  14.  
  15. unsigned long subkey[6];  
  16.  
  17. //循环左移两位,先记录左移出的最高1位,在整体左移1位,  
  18.  
  19. //最高位再放到最低位。LL是unsigned long long赋值时的必须选择  
  20.  
  21. //实际a,b的取值只0~255,故最后要&=0xFFLL  
  22.  
  23. //long long 在vc6下没法通过,故用linux或vc2005  
  24.  
  25. //gcc -O3 feal4.c -o feal4  
  26.  
  27. unsigned long long shiftLeft2(unsigned long a)  
  28.  
  29. {  
  30.  
  31. unsigned long b;  
  32.  
  33. unsigned long carry = (a &gt;&gt; 7LL);  
  34.  
  35. carry &= 0x1LL;  
  36.  
  37. b = a <&lt; 1LL;  
  38.  
  39. b += carry;  
  40.  
  41. b &= 0xFFLL;  
  42.  
  43. carry = (b >&gt; 7LL);  
  44.  
  45. carry &= 0x1LL;  
  46.  
  47. b <&lt;= 1LL;  
  48.  
  49. b += carry;  
  50.  
  51. b &= 0xFFLL;  
  52.  
  53. return b;  
  54.  
  55. }  
  56.  
  57. //G函数  
  58.  
  59. unsigned long gBox(unsigned long a, unsigned long b, unsigned long mode)  
  60.  
  61. {  
  62.  
  63. return shiftLeft2((a + b + mode) % 256LL);  
  64.  
  65. }  
  66.  
  67. //轮函数  
  68.  
  69. unsigned long fBox(unsigned long plain)  
  70.  
  71. {  
  72.  
  73. //一个32BIT拆成4个8BIT  
  74.  
  75. unsigned long x0 = plain & 0xFFL;  
  76.  
  77. unsigned long x1 = (plain >&gt; 8L) & 0xFFL;  
  78.  
  79. unsigned long x2 = (plain &gt;&gt; 16L) & 0xFFL;  
  80.  
  81. unsigned long x3 = (plain &gt;&gt; 24L) & 0xFFL;  
  82.  
  83. unsigned long t0 = (x2 ^ x3);  
  84.  
  85. unsigned long t1 = gBox(x0 ^ x1, t0, 1L);  
  86.  
  87. unsigned long y0 = gBox(x0, t1, 0L);  
  88.  
  89. unsigned long y1 = t1;  
  90.  
  91. unsigned long y2 = gBox(t0, t1, 0L);  
  92.  
  93. unsigned long y3 = gBox(x3, y2, 1L);  
  94.  
  95. //4个8BIT重组一个32BIT  
  96.  
  97. unsigned long ret = y3 <&lt; 24L;  
  98.  
  99. ret += (y2 &lt;&lt; 16L);  
  100.  
  101. ret += (y1 &lt;&lt; 8L);  
  102.  
  103. ret += y0;  
  104.  
  105. return ret;  
  106.  
  107. }  
  108.  
  109. unsigned long long encrypt(unsigned long long plain)  
  110.  
  111. {  
  112.  
  113. unsigned long left = (plain >&gt; 32LL) & 0xFFFFFFFFLL;  
  114.  
  115. unsigned long right = plain & 0xFFFFFFFFLL;  
  116.  
  117. left = left ^ subkey[4];  
  118.  
  119. right = right ^ subkey[5];  
  120.  
  121. unsigned long round2Left = left ^ right;  
  122.  
  123. unsigned long round2Right = left ^ fBox(round2Left ^ subkey[0]);  
  124.  
  125. unsigned long round3Left = round2Right;  
  126.  
  127. unsigned long round3Right = round2Left ^ fBox(round2Right ^ subkey[1]);  
  128.  
  129. unsigned long round4Left = round3Right;  
  130.  
  131. unsigned long round4Right = round3Left ^ fBox(round3Right ^ subkey[2]);  
  132.  
  133. unsigned long cipherLeft = round4Left ^ fBox(round4Right ^ subkey[3]);  
  134.  
  135. unsigned long cipherRight = cipherLeft ^ round4Right;  
  136.  
  137. //32bit转64bit后的重组  
  138.  
  139. unsigned long long ret = (((unsigned long long)(cipherLeft)) <&lt; 32LL);  
  140.  
  141. ret += (((unsigned long long)(cipherRight)) & 0xFFFFFFFFLL);  
  142.  
  143. return ret;  
  144.  
  145. }  
  146.  
  147. void generateSubkeys(int seed)  
  148.  
  149. {  
  150.  
  151. srand(time(NULL));  
  152.  
  153. int c;  
  154.  
  155. //用两次rand函数更随机  
  156.  
  157. for(c = 0; c &lt; 6; c++)  
  158.  
  159. {  
  160.  
  161. subkey[c] = rand() &lt;&lt; 16L;  
  162.  
  163. subkey[c] += rand() & 0xFFFFL;  
  164.  
  165. }  
  166.  
  167. }  
  168.  
  169. int numPlain;  
  170.  
  171. unsigned long long plain0[10000];  
  172.  
  173. unsigned long long cipher0[10000];  
  174.  
  175. unsigned long long plain1[10000];  
  176.  
  177. unsigned long long cipher1[10000];  
  178.  
  179. unsigned long key3winner;  
  180.  
  181. void crackSubkey3ULTRA()  
  182.  
  183. {  
  184.  
  185. unsigned long fakeK;  
  186.  
  187. for(fakeK = 0x00000000L; fakeK &lt; 0xFFFFFFFFL; fakeK++)  
  188.  
  189. {  
  190.  
  191. int score = 0;  
  192.  
  193. int c;  
  194.  
  195. for(c = 0; c &lt; numPlain; c++)  
  196.  
  197. {  
  198.  
  199. unsigned long cipherLeft = (cipher0[c] >&gt; 32LL);  
  200.  
  201. cipherLeft ^= (cipher1[c] &gt;&gt; 32LL);  
  202.  
  203. unsigned long cipherRight = cipher0[c] & 0xFFFFFFFFLL;  
  204.  
  205. cipherRight ^= (cipher1[c] & 0xFFFFFFFFLL);  
  206.  
  207. //Y没用  
  208.  
  209. unsigned long Y = cipherLeft ^ cipherRight;  
  210.  
  211. unsigned long Z = cipherLeft ^ 0x02000000L;  
  212.  
  213. unsigned long fakeRight = cipher0[c] & 0xFFFFFFFFLL;  
  214.  
  215. unsigned long fakeLeft = cipher0[c] &gt;&gt; 32LL;  
  216.  
  217. unsigned long fakeRight2 = cipher1[c] & 0xFFFFFFFFLL;  
  218.  
  219. unsigned long fakeLeft2 = cipher1[c] &gt;&gt; 32LL;  
  220.  
  221. unsigned long Y0 = fakeLeft ^ fakeRight;  
  222.  
  223. unsigned long Y1 = fakeLeft2 ^ fakeRight2;  
  224.  
  225. unsigned long fakeInput0 = Y0 ^ fakeK;  
  226.  
  227. unsigned long fakeInput1 = Y1 ^ fakeK;  
  228.  
  229. unsigned long fakeOut0 = fBox(fakeInput0);  
  230.  
  231. unsigned long fakeOut1 = fBox(fakeInput1);  
  232.  
  233. unsigned long fakeDiff = fakeOut0 ^ fakeOut1;  
  234.  
  235. if (fakeDiff == Z) score++; else break;  
  236.  
  237. }  
  238.  
  239. if (score == numPlain)  
  240.  
  241. {  
  242.  
  243. printf("DISCOVERED ROUND #4 SUBKEY = %08lx\n", fakeK);  
  244.  
  245. printf(" ACTUAL ROUND #4 SUBKEY = %08lx\n", subkey[3]);  
  246.  
  247. key3winner = fakeK;  
  248.  
  249. //不break应该能找到更多,可通过增加明/密文对去过滤  
  250.  
  251. //break;  
  252.  
  253. }  
  254.  
  255. }  
  256.  
  257. }  
  258.  
  259. void chosenPlaintext(unsigned long long diff)  
  260.  
  261. {  
  262.  
  263. srand(time(NULL));  
  264.  
  265. printf("PLAINTEXT DIFFERENTIAL = %llx\n\n", diff);  
  266.  
  267. int c;  
  268.  
  269. for(c = 0; c &lt; numPlain; c++)  
  270.  
  271. {  
  272.  
  273. plain0[c] = (rand() & 0xFFFFLL) &lt;&lt; 48LL;  
  274.  
  275. plain0[c] += (rand() & 0xFFFFLL) &lt;&lt; 32LL;  
  276.  
  277. plain0[c] += (rand() & 0xFFFFLL) &lt;&lt; 16LL;  
  278.  
  279. plain0[c] += (rand() & 0xFFFFLL);  
  280.  
  281. cipher0[c] = encrypt(plain0[c]);  
  282.  
  283. plain1[c] = plain0[c] ^ diff;  
  284.  
  285. cipher1[c] = encrypt(plain1[c]);  
  286.  
  287. }  
  288.  
  289. }  
  290.  
  291. int main()  
  292.  
  293. {  
  294.  
  295. generateSubkeys(time(NULL));  
  296.  
  297. printf("Imput the num of pairs:");  
  298.  
  299. scanf("%d",&numPlain);  
  300.  
  301. //numPlain = 6;  
  302.  
  303. chosenPlaintext(0x8080000080800000LL);  
  304.  
  305. unsigned long startTime = time(NULL);  
  306.  
  307. crackSubkey3ULTRA();  
  308.  
  309. unsigned long endTime = time(NULL);  
  310.  
  311. printf("Time to crack round #4 = %i seconds\n\n", (endTime - startTime));  
  312.  
  313. return 0;  
  314.  
  315. }