生成DES的S盒

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;
}
  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值