文章目录
1. DES的S盒满足的规则
①S盒的每一行是整数0-15的一个置换;
② 每个S盒的传输函数都不是线性的或仿射的;
③对一个S盒而言,输入端每改变一位,至少引起输出端改变两位;
④S(x)和S(x⊕001100)至少有2位不同;
⑤对于任何e与f,S(x)≠S(x⊕11ef00)
⑥当输入端任何一位保持不变时,S盒应保证在其输出端0和1的个数之差达到最小。
- 文中实现了①~⑤条,第⑥条是统计规律。实际上主要需要实现的是3、4、5这3条规则。
2. 设计思路
2.1 总的思路
总的思路是,类比用栈实现走迷宫的方法,生成一个符合要求的S盒,实际上就是把数字按照顺序从(0,0)一个个摆放到(3,15)。所以实现时,就依顺序,从(0,0)开始放置数字,直到成功放到(3,15)。如果在摆放数字到(r,c)失败时,回退一步,消除影响,再次尝试换一个数据来进行摆放。
2.2 满足S盒规则
2.2.1 满足①+②
因为本身摆放数字的时候,每行是独立的,所以实现第①条很容易,并且在满足③和④后,不满足第②的几率很小很小。用数组haveput[16]来防止生成的S盒出现列内重复数字。
2.2.2 满足③
要满足第③条,输入变化1位,输出至少变化2位。
首先考虑简单的,输入变化导致选择到了同一列中的不同行。只改变S=b1b2b3b4b5b6中的1/6位,实际上就是在列内行变化。原来是第0(00)行,可能会变到第1行(01)或者第2行(10)。再考虑行内的列变化,如果改变b2b3b4b5中的某一位,则会导致行内列变化,变化到同一行中的不同列。
实现行内列变化,引入矛盾组的概念,相当于黑名单。组<x>内的数字,都是与<x>只有一位差别,或者为x。例如,下图中每列为1个组,组<1>={0,3,5,9},组<9>={8,11,13,1}。
先实现输入变化一位导致的行内列变化,输出变化2位。实现时,在放置(2,4)时,需要让(2,4)与(2,0),(2,5),(2,6),(2,12)内的数字相差至少2位。因为(2,5),(2,6),(2,12)还没有摆放,所以在放置(2,4)只需要考虑(2,0),如果(2,0)中现在摆放着6,摆放(2,4)时,摆放的数字只要满足不在组<6>中即可,就满足了摆放的不是相差1位的要求。
再实现输入变化一位导致的列内行变化,输出变化2位。放置(3,3)时,需要检查<3>的矛盾组<3>={2,1,7,11},因为2,1都小于3,所以放(3,3)时需要考虑(2,3)和(1,3)中放的数字γ和β,(3,3)中放的数字不能是<γ>和<β>中的数字,也就是不能只相差一位。
对于不能相同数字,行内不同列数字不相同是用haveput数组保证的,列内则需要第一次扩展矛盾组。组<3>扩展为<3> ={3,2,1,7,11},组中第一个数字就是组号,这样检查矛盾组的时候,就能检查是否相同和相差一位。这样,放(3,3)时需要考虑(2,3)和(1,3)中放的数字γ和β,(3,3)中放的数字不能是<γ>和<β>中的数字,就考虑到了相同和只相差一位。
到此为止,S盒就满足了输入相差一位,输出变化至少两位。
2.2.3 满足④
用满足第三条的方法,只不过在矛盾组中再加入一个数字即可。如<4>={4, 5, 6, 0, 12},满足第四条,就再次扩展<4>,加入4与0110异或的结果2,扩展后的<4>={4, 5, 6, 0, 12, 2}, ,因为第四条仅仅是行内的列规则,所以实现时只需要检查行内的列即可。
如放置(2,4)时,<4>的最后一位存放着与0110异或结果,为2,所以考虑中(2,2)存放的数字,假设为8,那么(2,4)中不能存放与8相差一位的数字即<8>中的9, 10, 12, 0。
2.2.4 满足⑤
类比满足前面准则的方法,引入不等组。观察以后发现,⑤的实现实际上是先选择行,在选择列不相等。第0行和第1行的摆放不受⑤的影响,因为b1=1,b6=0不会让第0和第1之间影响。但是第0行和第2行、第1行与第3行间有影响,摆放第2、3行数据的时候,要对应考虑第0、1内的数据。
具体来说,2/3行中元素,需要查不等表: 0/2/4/6列查0/1行的8/10/12/14列不等,1/3/5/7列查0/1行的9/11/13/15列不等,8/10/12/14列的查0/1行的0/2/4/6列不等,9/11/13/15列的查0/1行的1/3/5/7列不等。
这样一来,就实现了模2变化11ef00元素的不相等。
3. 编程实现
3.1 矛盾组
int exgroup[16][6] = {//列矛盾是[0]--[4].行矛盾是[1]--[5]
{0, 1, 2, 4, 8, 6}, //0 group
{1, 0, 3, 5, 9, 7}, //1 group
{2, 3, 0, 6, 10, 4}, //2 group
{3, 2, 1, 7, 11, 5}, //3 group
{4, 5, 6, 0, 12, 2}, //4 group
{5, 4, 7, 1, 13, 3}, //5 group
{6, 7, 4, 2, 14, 0}, //6 group
{7, 6, 5, 3, 15, 1}, //7 group
{8, 9, 10, 12, 0, 14}, //8 group
{9, 8, 11, 13, 1, 15}, //9 group
{10, 11, 8, 14, 2, 12}, //10 group
{11, 10, 9, 15, 3, 13}, //11 group
{12, 13,14, 8, 4, 10}, //12 group
{13, 12,15, 9, 5, 11}, //13 group
{14, 15,12, 10, 6, 8}, //14 group
{15, 14,13, 11, 7, 9} //15 group
};
3.2 ⑤的不等组
int forrule4[4][4]{
{8,10,12,14}, // 0/2/4/6
{9,11,13,15}, // 1/3/5/7
{0, 2, 4, 6}, // 8/10/12/14
{1, 3, 5, 7} // 9/11/13/15
};
3.3 S盒存放
typedef struct
{
int x = 0; //当前单元格的行号
int num; //num is the current num of the cell
set <int>tried;//some have try but fail
} Cell; //定义单元格类型
typedef struct
{
Cell data[4][BoxSize];//存放1个S盒
int top; //栈顶指针
} StType; //顺序栈类型
3.4 ③④规则实现
//行内规则
for (int k = 1; k < 6; k++)//考虑行内规则
{
if (exgroup[i][k] < i)
for (int m = 1; m < 5; m++)//因为有havaput检查,所以行里面本身不会有相同的
{
notchoose.insert(exgroup[st.data[row][exgroup[i][k]].num][m]);
}
}
行内规则③,就遍历矛盾组的[1]-[5]列,如果它们比当前列号小,已经摆放上了,就得考虑。考虑时,不用担心重复0-15,有haveput保证。保证不是只相差1位就行,也就是内部考虑矛盾组的[1]-[4]列。
行内规则④,存在矛盾组的第[5]列,所以合并以后就可以和③一起实现。
//列内规则
for (int k = 1; k < 5; k++)//考虑列内规则
{
if (exgroup[row][k] < row)//行号小于row的,需要考虑
for (int m = 0; m < 5; m++)//与矛盾的列中数字相差一位不能选,相同也不能选,所以要到m=4
{
notchoose.insert(exgroup[st.data[exgroup[row][k]][i].num][m]);//索引次序不能搞错
}
}
列内规则,也是遍历矛盾组的[1]-[4]列,如果如果它们比当前行号小,已经摆放上了,就得考虑。haveput是一维的,所以需要考虑重复和差一位,内部考虑矛盾组[0]-[4]列。
3.5 ⑤的实现
mod = i % 2;
if (row > 1)//第2/3行开始才要和前面对比,第2行对比第0行,第3行对比第1行
{
if (mod==0 && i < 8)
notequal = 0;
else if(mod==1 && i < 8)
notequal = 1;
else if (mod == 0 && i >7)
notequal = 2;
else if (mod == 1 && i > 7)
notequal = 3;
for (int q = 0; q < 4; q++)
notchoose.insert(st.data[row - 2][forrule4[notequal][q]].num);
}
只有row>1才要判断,判断当前列号大于8还是小于8,奇数还是偶数,0/2/4/6列查0/1行的8/10/12/14列不等notequal = 0, 1/3/5/7列查0/1行的9/11/13/15列不等notequal = 1,8/10/12/14列的查0/1行的0/2/4/6列不等notequal = 2, 9/11/13/15列的查0/1行的1/3/5/7列不等notequal = 3,然后对应到索引查表即可。
3.6 摆放数字
//数字摆放
//当前单元格选哪个数字好呢?
for (int j = 0; j < 16; j++)
if (haveput[j] == 1)
notchoose.insert(j);//不仅要满足矛盾组规则,还要满足同一行不重复
if (notchoose.size() < 16)
{
//做差集,全集与不能选的相减,得到的就是可以选的
it = set_difference(U.begin(), U.end(), notchoose.begin(), notchoose.end(), canchoose.begin());
canchoose.resize(it - canchoose.begin());
if (canchoose.size() > 1)random_shuffle(canchoose.begin(), canchoose.end());//不然就随机选一个
currentnum = *canchoose.begin();//重排列以后选第一个
flag = 1;//OhYeah!找到一个可以放在这个单元格的了
canchoose.resize(16);//把那个向量大小复原
}
判断能不能放数字,做全集U=[0-15]差集就可以了,如果差集非空就说明可以放,找到这样的数字,就立下flag=1。如果flag=1说明可以放数字,那就放。
if (flag == 1)//可以在当前单元格放数字
{
st.top++; st.data[row][st.top].x = i; st.data[row][st.top].num = currentnum;
st.data[row][st.top].tried.insert(currentnum);//记住这个单元格放过这个数字
haveput[currentnum] = 1;//放好了,行内重复标记
notchoose.clear();//清空,准备下一个单元格
flag = 0;
if (st.top < 15)//可能现在是之前的回溯,把下一个单元格的黑历史清空
{
if (!st.data[row][st.top + 1].tried.empty())
st.data[row][st.top + 1].tried.clear();
}
else if (st.top == 15 && row < 3)//如果在最后一列,要清空下一行第一个的黑历史
if (!st.data[row + 1][0].tried.empty())
st.data[row + 1][0].tried.clear();
}
放数字放下以后,haveput进行标记,不能重复放,放完以后,flag=0复位准备下一次,同时为了回溯时不走重复的路,S盒结构的那个单元需要记住自己放了这个数字。如果不凑巧这个单元格没有数字可以放,那就怪前面的单元格放错了,回溯,如下:
else {//不凑巧,这个单元格没有数字可以放,回溯
notchoose.clear();
haveput[st.data[row][st.top].num] = 0;//退一步,之前放的更改,其它单元可以放
notchoose.insert(st.data[row][st.top].tried.begin(), st.data[row][st.top].tried.end());//不在同一个地方摔倒两次
st.top--;
if (st.top == -1 && row > 0)//注意放置在哪里!!!
{
st.top = 15;//最右上
row--;
for (int m = 0; m < 16; m++) haveput[m] = 1;
haveput[st.data[row][st.top].num] = 0;
}
}
什么时候会知道放完了呢,那就是i=16(只有[15]列且row为3),跳出while循环:
i = st.data[row][st.top].x + 1;//现在在放第row行第i个单元格
if (row == 3 && i == 16) break;//已经做完了!注意顺序!!!
if (i == 16)//往下走一行,走到下一行的最左边
{
row++;
st.top = -1;
i = 0;
for (int m = 0; m < 16; m++) haveput[m] = 0;//新起一行了,把行内重复标记清空
}
4. 结果呈现
总共有8个S盒:
5. 完整代码
#include <cstdlib>
#include <set>
#include <vector>
#include <iostream>
#include <algorithm>
const int BoxSize = 16;
using namespace std;
typedef struct
{
int x = 0; //当前单元格的行号
int num; //num is the current num of the cell
set <int>tried;//some have try but fail
} Cell; //定义单元格类型
typedef struct
{
Cell data[4][BoxSize];//存放1个S盒
int top; //栈顶指针
} StType; //顺序栈类型
// the neibough of a is exgroup[a],they diff 1 bit from a
int exgroup[16][6] = {//列矛盾是[0]--[4].行矛盾是[1]--[5]
{0, 1, 2, 4, 8, 6}, //0 group
{1, 0, 3, 5, 9, 7}, //1 group
{2, 3, 0, 6, 10, 4}, //2 group
{3, 2, 1, 7, 11, 5}, //3 group
{4, 5, 6, 0, 12, 2}, //4 group
{5, 4, 7, 1, 13, 3}, //5 group
{6, 7, 4, 2, 14, 0}, //6 group
{7, 6, 5, 3, 15, 1}, //7 group
{8, 9, 10, 12, 0, 14}, //8 group
{9, 8, 11, 13, 1, 15}, //9 group
{10, 11, 8, 14, 2, 12}, //10 group
{11, 10, 9, 15, 3, 13}, //11 group
{12, 13,14, 8, 4, 10}, //12 group
{13, 12,15, 9, 5, 11}, //13 group
{14, 15,12, 10, 6, 8}, //14 group
{15, 14,13, 11, 7, 9} //15 group
};
int forrule4[4][4]{
{8,10,12,14}, // 0/2/4/6
{9,11,13,15}, // 1/3/5/7
{0, 2, 4, 6}, // 8/10/12/14
{1, 3, 5, 7} // 9/11/13/15
};
void genSbox(int times, FILE*&fid)
{
StType st; st.top = -1;//初始化结构
int haveput[16] = { 0 };//行内重复标记,行内不能有重复的!
int currentnum;//当前单元格决定摆放的数字
int a[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
vector<int>U(a, a + 16);//全集,用于与不能摆的数字集合notchoose做差运算,得到可以摆的
set<int> notchoose;//当前单元格不能摆放的数字
vector<int> canchoose(16);//当前单元格可以摆的数字,由全集U和不能摆的数字集合notchoose做差运算得到
vector<int>::iterator it;//用于获得当前单元格可以摆的所有数字
int flag = 0;//当前单元格摆成功了,flag=1.当前单元格摆不了,要回溯,flag=0
int i = 0;//列号,正在摆的那一列
int row = 0;//行号,摆到哪一行了
int notequal = 0;
currentnum = rand() % 16;//摆第一个,启动
st.top++; st.data[row][st.top].x = i; st.data[row][st.top].num = currentnum;
haveput[currentnum] = 1;
int mod = 0;
while (1)
{
i = st.data[row][st.top].x + 1;//现在在放第row行第i个单元格
if (row == 3 && i == 16) break;//已经做完了!注意顺序!!!
//printf("(row,i,st.top)===(%d,%d,%d)\n", row, i, st.top);
if (i == 16)//往下走一行,走到下一行的最左边
{
row++;
st.top = -1;
i = 0;
for (int m = 0; m < 16; m++) haveput[m] = 0;//新起一行了,把行内重复标记清空
}
for (int k = 1; k < 6; k++)//考虑行内规则
{
if (exgroup[i][k] < i)
for (int m = 1; m < 5; m++)//因为有havaput检查,所以行里面本身不会有相同的
{
notchoose.insert(exgroup[st.data[row][exgroup[i][k]].num][m]);
}
}
for (int k = 1; k < 5; k++)//考虑列内规则
{
if (exgroup[row][k] < row)//行号小于row的,需要考虑
for (int m = 0; m < 5; m++)//与矛盾的列中数字相差一位不能选,相同也不能选,所以要到m=4
{
notchoose.insert(exgroup[st.data[exgroup[row][k]][i].num][m]);//索引次序不能搞错
}
}
mod = i % 2;
if (row > 1)//第2/3行开始才要和前面对比,第2行对比第0行,第3行对比第1行
{
if (mod == 0 && i < 8)
notequal = 0;
else if (mod == 1 && i < 8)
notequal = 1;
else if (mod == 0 && i > 7)
notequal = 2;
else if (mod == 1 && i > 7)
notequal = 3;
for (int q = 0; q < 4; q++)
notchoose.insert(st.data[row - 2][forrule4[notequal][q]].num);
}
//当前单元格选哪个数字好呢?
for (int j = 0; j < 16; j++)
if (haveput[j] == 1)
notchoose.insert(j);//不仅要满足矛盾组规则,还要满足同一行不重复
if (notchoose.size() < 16)
{
//做差集,全集与不能选的相减,得到的就是可以选的
it = set_difference(U.begin(), U.end(), notchoose.begin(), notchoose.end(), canchoose.begin());
canchoose.resize(it - canchoose.begin());
if (canchoose.size() > 1)random_shuffle(canchoose.begin(), canchoose.end());//不然就随机选一个
currentnum = *canchoose.begin();//重排列以后选第一个
flag = 1;//OhYeah!找到一个可以放在这个单元格的了
canchoose.resize(16);//把那个向量大小复原
}
if (flag == 1)//可以在当前单元格放数字
{
st.top++; st.data[row][st.top].x = i; st.data[row][st.top].num = currentnum;
st.data[row][st.top].tried.insert(currentnum);//记住这个单元格放过这个数字
haveput[currentnum] = 1;//放好了,行内重复标记
notchoose.clear();//清空,准备下一个单元格
flag = 0;
if (st.top < 15)//可能现在是之前的回溯,把下一个单元格的黑历史清空
{
if (!st.data[row][st.top + 1].tried.empty())
st.data[row][st.top + 1].tried.clear();
}
else if (st.top == 15 && row < 3)//如果在最后一列,要清空下一行第一个的黑历史
if (!st.data[row + 1][0].tried.empty())
st.data[row + 1][0].tried.clear();
}
else {//不凑巧,这个单元格没有数字可以放,回溯
notchoose.clear();
haveput[st.data[row][st.top].num] = 0;//退一步,之前放的更改,其它单元可以放
notchoose.insert(st.data[row][st.top].tried.begin(), st.data[row][st.top].tried.end());//不在同一个地方摔倒两次
st.top--;
if (st.top == -1 && row > 0)//注意放置在哪里!!!
{
st.top = 15;//最右上
row--;
for (int m = 0; m < 16; m++) haveput[m] = 1;
haveput[st.data[row][st.top].num] = 0;
}
}
}
fprintf(fid, "%s%d%s", "-------------------S[", times + 1, "]----------------------\n");
for (int h = 0; h < 4; h++)
{
for (int n = 0; n <= 15; n++)
{
fprintf(fid, "%d", st.data[h][n].num);
if (n < 15) fprintf(fid, "%s", ",");
}
fprintf(fid, "%s", "\n");
}
fprintf(fid, "%s", "\n");
}
int main()
{
cout << "please enter your rand seed,an integer like 123:" << endl;
int myseed;
cin >> myseed;
srand(myseed);
char sboxpath[81] = "Sbox.csv";
FILE * fid = fopen(sboxpath, "w");
for (int i = 0; i < 8; i++)
genSbox(i, fid);
fclose(fid);
cout << "Your Sbox has stored in " << sboxpath << ". Please check it!";
system("pause");
return 0;
}