对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虽求逆困难,但不能单向,否则无法解密。
图1 FEAL密码结构
这里的密钥K0~K5均是从初始密钥K扩展而来,每个32位。而实际的攻击结果是完全攻破6*32=192位密钥。所以,密钥协商算法是单向的也没关系,可以把子密钥看做随机生成。
2.1 FEAL轮函数结构
图2 FEAL轮函数结构
轮函数的关键是非线性性,移位和异或都起到了这样的作用。输入为32位字,分为4个8位的小块。
3. FEAL密码算法差分攻击
差分攻击的本质是利用非线性变换的并非完全随机性。即对于特定的输入差分,输出的差分并不是均匀分布的。
3.1 概率为100%的差分特征
图3 概率为100%的差分特征
由于轮函数的输入是32位,所以不可能像小SBOX一样,穷举所有差分。
3.2概率为100%的差分特征原理
这里要把轮函数分解为(a+b+x mod 255)<<2,首先+x对差分来说可忽略。而mod 255对输入差分为0x80,0x00来说,其输出必然0x80。参见图4.(详细解释见原文)
图4 概率为100%的差分特征
对于轮函数对差分的跟踪,如图5.
图5 轮函数对差分的跟踪
3.3 概率为100%的4轮差分特征的寻找
这里应用了中间相遇攻击的原理,即从前向后利用明文差分找到两轮输入差分。同时,从后向前,利用密文差分找到两轮差分。
图6 4轮差分特征
利用输入明文差分P0 XOR P1 = 0x8080000080800000 可以如图6追踪前两轮的差分传播。但只能分析到红色部分之前。因为 0x02000000作为输入差分是很难预测其输出差分的。
而若想找到最后一轮的子密钥k3,则需要知道最后一轮函数f 的输出差分。而该输出差分恰好等于左边32位的密文差分与0x02000000的异或。
3.4 最后一轮的子密钥k3的获取
这里的关键点在于最后一轮函数的输入差分及输出差分的获取,由图7可知,最后一轮函数f的输入差分可通过穷举密钥获得。这里看似输入差分似乎与子密钥k3没有关系,但子密钥k3能影响具体的输出差分(已经计算出)的值。正确的子密钥k3会使其输出差分完全吻合,即该子密钥的分数为6,见伪码图8
图7 对密钥的寻找
图8 伪代码
4. 试验结果
对于6个明、密文对,每次总能找到4个可能的最后一轮子密钥,但如何排除?(增加更多的明、密文对并不能排除,换条差分路径也许可以?)对其他轮子密钥,在已知第四轮子密钥的情况下,可效仿求第三轮子密钥。但结果亦不唯一。只能在找到的结果中用穷举去排除。只要结果比穷举2^64小(初始密钥64位),就算成功。
5. 附录代码
- //Differential Cryptanalysis of FEAL-4
- //Uses a chosen-plaintext attack to fully recover the last round subkey
- //For use with tutorial at http://theamazingking.com/crypto-feal.php
- //Hack the Planet!
- #include <stdio.h>
- #include <math.h>
- //subkey为全局变量
- unsigned long subkey[6];
- //循环左移两位,先记录左移出的最高1位,在整体左移1位,
- //最高位再放到最低位。LL是unsigned long long赋值时的必须选择
- //实际a,b的取值只0~255,故最后要&=0xFFLL
- //long long 在vc6下没法通过,故用linux或vc2005
- //gcc -O3 feal4.c -o feal4
- unsigned long long shiftLeft2(unsigned long a)
- {
- unsigned long b;
- unsigned long carry = (a >> 7LL);
- carry &= 0x1LL;
- b = a << 1LL;
- b += carry;
- b &= 0xFFLL;
- carry = (b >> 7LL);
- carry &= 0x1LL;
- b <<= 1LL;
- b += carry;
- b &= 0xFFLL;
- return b;
- }
- //G函数
- unsigned long gBox(unsigned long a, unsigned long b, unsigned long mode)
- {
- return shiftLeft2((a + b + mode) % 256LL);
- }
- //轮函数
- unsigned long fBox(unsigned long plain)
- {
- //一个32BIT拆成4个8BIT
- unsigned long x0 = plain & 0xFFL;
- unsigned long x1 = (plain >> 8L) & 0xFFL;
- unsigned long x2 = (plain >> 16L) & 0xFFL;
- unsigned long x3 = (plain >> 24L) & 0xFFL;
- unsigned long t0 = (x2 ^ x3);
- unsigned long t1 = gBox(x0 ^ x1, t0, 1L);
- unsigned long y0 = gBox(x0, t1, 0L);
- unsigned long y1 = t1;
- unsigned long y2 = gBox(t0, t1, 0L);
- unsigned long y3 = gBox(x3, y2, 1L);
- //4个8BIT重组一个32BIT
- unsigned long ret = y3 << 24L;
- ret += (y2 << 16L);
- ret += (y1 << 8L);
- ret += y0;
- return ret;
- }
- unsigned long long encrypt(unsigned long long plain)
- {
- unsigned long left = (plain >> 32LL) & 0xFFFFFFFFLL;
- unsigned long right = plain & 0xFFFFFFFFLL;
- left = left ^ subkey[4];
- right = right ^ subkey[5];
- unsigned long round2Left = left ^ right;
- unsigned long round2Right = left ^ fBox(round2Left ^ subkey[0]);
- unsigned long round3Left = round2Right;
- unsigned long round3Right = round2Left ^ fBox(round2Right ^ subkey[1]);
- unsigned long round4Left = round3Right;
- unsigned long round4Right = round3Left ^ fBox(round3Right ^ subkey[2]);
- unsigned long cipherLeft = round4Left ^ fBox(round4Right ^ subkey[3]);
- unsigned long cipherRight = cipherLeft ^ round4Right;
- //32bit转64bit后的重组
- unsigned long long ret = (((unsigned long long)(cipherLeft)) << 32LL);
- ret += (((unsigned long long)(cipherRight)) & 0xFFFFFFFFLL);
- return ret;
- }
- void generateSubkeys(int seed)
- {
- srand(time(NULL));
- int c;
- //用两次rand函数更随机
- for(c = 0; c < 6; c++)
- {
- subkey[c] = rand() << 16L;
- subkey[c] += rand() & 0xFFFFL;
- }
- }
- int numPlain;
- unsigned long long plain0[10000];
- unsigned long long cipher0[10000];
- unsigned long long plain1[10000];
- unsigned long long cipher1[10000];
- unsigned long key3winner;
- void crackSubkey3ULTRA()
- {
- unsigned long fakeK;
- for(fakeK = 0x00000000L; fakeK < 0xFFFFFFFFL; fakeK++)
- {
- int score = 0;
- int c;
- for(c = 0; c < numPlain; c++)
- {
- unsigned long cipherLeft = (cipher0[c] >> 32LL);
- cipherLeft ^= (cipher1[c] >> 32LL);
- unsigned long cipherRight = cipher0[c] & 0xFFFFFFFFLL;
- cipherRight ^= (cipher1[c] & 0xFFFFFFFFLL);
- //Y没用
- unsigned long Y = cipherLeft ^ cipherRight;
- unsigned long Z = cipherLeft ^ 0x02000000L;
- unsigned long fakeRight = cipher0[c] & 0xFFFFFFFFLL;
- unsigned long fakeLeft = cipher0[c] >> 32LL;
- unsigned long fakeRight2 = cipher1[c] & 0xFFFFFFFFLL;
- unsigned long fakeLeft2 = cipher1[c] >> 32LL;
- unsigned long Y0 = fakeLeft ^ fakeRight;
- unsigned long Y1 = fakeLeft2 ^ fakeRight2;
- unsigned long fakeInput0 = Y0 ^ fakeK;
- unsigned long fakeInput1 = Y1 ^ fakeK;
- unsigned long fakeOut0 = fBox(fakeInput0);
- unsigned long fakeOut1 = fBox(fakeInput1);
- unsigned long fakeDiff = fakeOut0 ^ fakeOut1;
- if (fakeDiff == Z) score++; else break;
- }
- if (score == numPlain)
- {
- printf("DISCOVERED ROUND #4 SUBKEY = %08lx\n", fakeK);
- printf(" ACTUAL ROUND #4 SUBKEY = %08lx\n", subkey[3]);
- key3winner = fakeK;
- //不break应该能找到更多,可通过增加明/密文对去过滤
- //break;
- }
- }
- }
- void chosenPlaintext(unsigned long long diff)
- {
- srand(time(NULL));
- printf("PLAINTEXT DIFFERENTIAL = %llx\n\n", diff);
- int c;
- for(c = 0; c < numPlain; c++)
- {
- plain0[c] = (rand() & 0xFFFFLL) << 48LL;
- plain0[c] += (rand() & 0xFFFFLL) << 32LL;
- plain0[c] += (rand() & 0xFFFFLL) << 16LL;
- plain0[c] += (rand() & 0xFFFFLL);
- cipher0[c] = encrypt(plain0[c]);
- plain1[c] = plain0[c] ^ diff;
- cipher1[c] = encrypt(plain1[c]);
- }
- }
- int main()
- {
- generateSubkeys(time(NULL));
- printf("Imput the num of pairs:");
- scanf("%d",&numPlain);
- //numPlain = 6;
- chosenPlaintext(0x8080000080800000LL);
- unsigned long startTime = time(NULL);
- crackSubkey3ULTRA();
- unsigned long endTime = time(NULL);
- printf("Time to crack round #4 = %i seconds\n\n", (endTime - startTime));
- return 0;
- }