算法步骤
- 收集大量明密文对;
- 选取一个固定的输入值 x ′ x' x′;
- 遍历所有收集到的明密文对,寻找 x x x、 x ∗ x^* x∗,使得 x ⨁ x ∗ = x ′ x\bigoplus x^*=x' x⨁x∗=x′;
- 创建4元组 ( x , x ∗ , y , y ∗ ) (x,x^*,y,y^*) (x,x∗,y,y∗),其中, y = π ( x ) , y ∗ = π ( x ∗ ) y=\pi(x),y^*=\pi(x^*) y=π(x),y∗=π(x∗);
- 通过4元组构建差分表;
- 从输入 x ′ x' x′开始,在差分表中选取差分值最大的作为输出,找到输出对应的s盒子并将该值作为输入,再在差分表中寻找差分值大的作为输入,以此类推,构建出差分链;
- 计算该差分链中最后一轮输入对应位置的 u ′ = u ⨁ u ∗ u'=u\bigoplus u^* u′=u⨁u∗的值,记作 u 真 ′ u'_真 u真′;
- 遍历所有可能的密钥,遍历4元组,要求四元组中差分链最后一轮输入对应位置的 y = y ∗ y=y^* y=y∗;根据收集到的 y 、 y ∗ y、y^* y、y∗值即当前测试密钥值计算最后一轮输入的 u u u及 u ∗ u^* u∗,再根据该值计算 u 测 ′ = u ⨁ u ∗ u'_测=u\bigoplus u^* u测′=u⨁u∗;若 u 真 ′ = = u 测 ′ u'_真==u'_测 u真′==u测′,则该密钥对应count值加一;
- 遍历结束后输出count值最大的密钥。
代码
首先使用书中给出的差分链分析出第5轮第2、4部分的密钥。再选择新的差分链分析出第5轮第1、3部分密钥,然后穷举高16位密钥情况,并验证密钥正确性。
与线性分析不同的是,由于差分分析中可以找到一条第5轮仅包含第1、3部分且偏差很大的差分链,因此不需要在已知第5轮第2、4部分的基础上进行差分分析,也因此减小了部分时间开销。
与线性分析相同的是,差分分析也是基于概率的分析方法,因此仍需要对一定范围内的密钥情况进行遍历并验证是否正确。
同时,由于对基于不同规模的明密文对进行分析得到的差分分析准确度也有所不同,但在超过阈值后将出现平台期,即当分析的明密文对达到MAX规模后,差分分析得到的结果的准确度几乎保持不变,因此为降低时间开销,MAX作为可调参数需要进行调参摸索。在本题中,该MAX值大约为8000。
综上代码实现流程为:快速读入数据并存入数组中;根据已有的明密文对分别对第5轮第2、4部分和第1、3部分进行差分分析,记录每一种密钥对应的count值;在一定范围内遍历第5轮第2、4部分和第1、3部分密钥,穷举高16位密钥并验证正确性(一定范围指内循环和外循环的循环次数,该参数需要测试调整到最合适);得到正确密钥后快速输出密钥。
需要注意的是,穷举验证过程中由于输入数据的随机程度不大(实际上检测的数据具有连续性),因此在一定检测数量下,相同明文在不同密钥下得到相同密文的个数将会有所提升,因此需要适度增加验证数量。
#include <stdio.h>
#include <string.h>
typedef unsigned short ushort;
typedef unsigned int uint;
int n;
ushort ciphertext[65540];
uint key, tail_key;
int cnt13[16][16], cnt24[16][16];
bool flag13[16][16];
ushort key51, key52, key53, key54;
//SPN statement
const unsigned short sBox_4[16] = {
0xe, 0x4, 0xd, 0x1, 0x2, 0xf, 0xb, 0x8, 0x3, 0xa, 0x6, 0xc, 0x5, 0x9, 0x0, 0x7};
const unsigned short inverse_sBox[16] = {
0xe, 0x3, 0x4, 0x8, 0x1, 0xc, 0xa, 0xf, 0x7, 0xd, 0x9, 0x6, 0xb, 0x2, 0x0, 0x5};
const unsigned short pos[17] = {
0x0,
0x8000, 0x4000, 0x2000, 0x1000,
0x0800, 0x0400, 0x0200, 0x0100,
0x0080, 0x0040, 0x0020, 0x0010,
0x0008, 0x0004, 0x0002, 0x0001};
const unsigned short pBox[17] = {
0x0,
0x8000, 0x0800, 0x0080, 0x0008,
0x4000, 0x0400, 0x0040, 0x0004,
0x2000, 0x0200, 0x0020, 0x0002,
0x1000, 0x0100, 0x0010, 0x0001};
unsigned short sBox_16[65536], spBox[65536];
void get_spBox(){
//获得spBox
for(int i = 0; i < 65536; ++i){
sBox_16[i] = (sBox_4[i >> 12] << 12) | (sBox_4[(i >> 8) & 0xf] << 8) | (sBox_4[(i >> 4) & 0xf] << 4) | sBox_4[i & 0xf];
spBox[i] = 0;
for(int j = 1; j <= 16; ++j){
if(sBox_16[i] & pos[j]) spBox[i] |= pBox[j];
}
}
}
inline ushort read(){
char ch;
ushort buf = 0;
for(int i = 0; i < 4; ){
ch = getchar();
if(ch >= '0' && ch <= '9'){
buf = (buf << 4) | (ch ^ 48);
i++;
}
else if(ch >= 'a' && ch <= 'z'){
buf = (buf << 4) | (ch - 'a' + 10);
i++;
}
}
return buf;
}
inline void input(){
for(int i = 0; i < 65536; ++i){
ciphertext[i] = read();
}
}
inline void output(){
char buf[8]; //输出缓冲区
for(int i = 0; i < 8; ++i){
buf[7 - i] = key & 0xf;
if(buf[7 - i] < 10) buf[7 - i] += '0';
else buf[7 - i] = buf[7 - i] - 10 + 'a';
key >>= 4;
}
for(int i = 0; i < 8; ++i) putchar(buf[i]);
putchar('\n');
}
inline void diff_analysis(){
uint x, x_, y, y_, x_xor;
ushort u1, u2, u3, u4, u1_, u2_, u3_, u4_;
x_xor = 0x0b00;
for(x = 12345; x < 20567; ++x){
x_ = x ^ x_xor;
y = ciphertext[x];
y_ = ciphertext[x_];
if((y & 0xf0f0) == (y_ & 0xf0f0)){
for(int L1 = 0; L1 < 16; ++L1){
for(int L2 = 0; L2 < 16; ++L2){
//v2 = L1 ^ ((y >> 8) & 0xf);
//v4 = L2 ^ (y & 0xf);
u2 = inverse_sBox[L1 ^ ((y >> 8) & 0xf)];
u4 = inverse_sBox[L2 ^ (y & 0xf)];
//v2_ = L1 ^ ((y_ >> 8) & 0xf);
//v4_ = L2 ^ (y_ & 0xf);
u2_ = inverse_sBox[L1 ^ ((y_ >> 8) & 0xf)];
u4_ = inverse_sBox[L2 ^ (y_ & 0xf)];
//u2_xor = u2 ^ u2_;
//u4_xor = u4 ^ u4_;
if(((u2 ^ u2_) == 0x6) && ((u4 ^ u4_) == 0x6)) cnt24[L1][L2]++;
}
}
}
}
x_xor = 0x0020;
for(x = 12345; x < 20567; ++x){
x_ = x ^ x_xor;
y = ciphertext[x];
y_ = ciphertext[x_];
if((y & 0x0f0f) == (y_ & 0x0f0f)){
for(int L1 = 0; L1 < 16; ++L1){
for(int L2 = 0; L2 < 16; ++L2){
//v1 = L1 ^ ((y >> 12) & 0xf);
//v3 = L2 ^ ((y >> 4) & 0xf);
u1 = inverse_sBox[L1 ^ ((y >> 12) & 0xf)];
u3 = inverse_sBox[L2 ^ ((y >> 4) & 0xf)];
//v1_ = L1 ^ ((y_ >> 12) & 0xf);
//v3_ = L2 ^ ((y_ >> 4) & 0xf);
u1_ = inverse_sBox[L1 ^ ((y_ >> 12) & 0xf)];
u3_ = inverse_sBox[L2 ^ ((y_ >> 4) & 0xf)];
//u1_xor = u1 ^ u1_;
//u3_xor = u3 ^ u3_;
if(((u1 ^ u1_) == 0x5) && ((u3 ^ u3_) == 0x5)) cnt13[L1][L2]++;
}
}
}
}
}
int main(){
get_spBox();
scanf("%d", &n);
bool flag;
for(int group = 0; group < n; ++group){
input();
flag = false;
//计算2、4位
memset(cnt24, 0, 256 * sizeof(int));
memset(cnt13, 0, 256 * sizeof(int));
diff_analysis(); //差分分析
//外循环
for(int round24 = 0; round24 < 10; ++round24){
int max24 = -1;
for(int L1 = 0; L1 < 16; ++L1){
for(int L2 = 0; L2 < 16; ++L2){
if(cnt24[L1][L2] > max24){
max24 = cnt24[L1][L2];
key52 = L1;
key54 = L2;
}
}
}
cnt24[key52][key54] = 0;
//内循环
memset(flag13, true, 256 * sizeof(bool));
for(int round13 = 0; round13 < 10; ++round13){
int max13 = -1;
for(int L1 = 0; L1 < 16; ++L1){
for(int L2 = 0; L2 < 16; ++L2){
if(cnt13[L1][L2] > max13 && flag13[L1][L2]){
max13 = cnt13[L1][L2];
key51 = L1;
key53 = L2;
}
}
}
flag13[key51][key53] = false;
//开始穷举
tail_key = (key51 << 12) | (key52 << 8) | (key53 << 4) | key54;
int plaintext, k1, k2, k3, k4, k5;
for(int fore_key = 0; fore_key < 65536; ++fore_key){
key = (fore_key << 16) | tail_key;
k5 = tail_key;
k4 = (key >> 4) & 0xffff;
k3 = (key >> 8) & 0xffff;
k2 = (key >> 12) & 0xffff;
k1 = (key >> 16) & 0xffff;
for(plaintext = 0; plaintext < 24; ++plaintext){
if((sBox_16[spBox[spBox[spBox[plaintext ^ k1] ^ k2] ^ k3] ^ k4] ^ k5) != ciphertext[plaintext]) break;
}
if(plaintext == 24){
flag = true;
break;
}
}
if(flag) break;
}
if(flag) break;
}
output();
}
return 0;
}