一、实验目的
(1)实现DES 算法并掌握 DES 算法的加解密过程;
(2)了解 DES 算法的安全性,差分密码分析及线性分析等。
硬件:运行Windows操作系统的计算机
软件:C语言
二、方案设计
背景
DES算法的研究始于1973年,由美国国家标准局(NBS,现NIST)发起。
经过几年的研究和测试,DES算法于1977年被正式发布为美国政府的加密标准。
DES算法的设计目的是保护政府敏感信息的安全,防止数据在传输过程中被非法获取和篡改。DES算法采用64位密钥和64位分组大小,可以对8字节的数据块进行加密,是一种块加密算法。
原理
1.整体结构
DES 算法对 64 位明文进行操作,首先利用初始置换 IP 对明文块的位进行换位。初始置换的作用是打乱原有输入消息。经过初始置换 IP 之后,明文被分为左半部分 L0 和 右半部分 R0,各 32 位。然后利用 F 函数进行 16 轮完全相同的迭代,其中,在每轮加密中,数据均需要与相应的子密钥进行异或运算。每轮函数加密可表示为:
整体结构如下图:
图1 DES 算法的整体结构图
逆初始置换相当于初始置换的逆过程。逆初始置换的结果即为算法的输出。初初始置换IP与逆初始置换如下表所示:
表1 DES 初始置换表
表2 DES 逆初始置换表
2.F函数
DES 算法加密的每轮运算结构如图 2 所示,包括扩展置换(运算)E、压缩运算(非线性代换 S)和线性置换 P 三种基本操作。
图2 DES 算法加密每轮运算结构
F 函数是 DES 的核心,是其中最终要的部分。F 函数的输入是上一轮的右半部分和 子密钥,由于上一轮的右半部分为 32bit,而子密钥是 48bit,所以需要对上一轮的右半 部分进行扩展。选择扩展运算 E 用于将 32bit 的输入扩展为 48bit,可以称之为 E 盒。方 法是将 32bit 写成 8*4 的矩阵,然后增加两列,也就是增加 16bit。E 盒扩展置换表如表 3 所示,表中第 1 列重复了原数据的第 位,向下平移。表中第 6 列重复了 原数据的第 位,向上平移。F函数的输出经过一个异或运算,和左半部分结合,其结果成为新的右半部分,原来的右半部分成为新的左半部分。
表3 DES E 盒置换表
E 盒能够生成与子密钥相同长度的数据与之进行异或,同时扩展后的数据经过 S 盒 压缩后,能够实现非线性变换。经过 E 盒扩展之后得到的 48 位数据,可分为 8 组,每 组 6 位。将其输入 8 个不同的 S 盒(),每一个 S 盒都有 6 位输入,4 位输出的结 构。每个分组对应一个 S 盒操作,分组 1 由 盒 操作,分组 2 由 操作等等。如图 3 所示。S 盒()如图 4 所示。
图 3 S 盒代换
图4 S盒
输入位以一种特殊的方式确定了 S 盒中的项,其中第 1 位和第 6 位作为行号,第 2 位到第 5 位作为列号,选择一个 16 进制数字,然后将输入的 6bit 数字替换成 S 盒中的 4bit 数字(一个十六进制数字)。图 5 给出了 S 和输入输出的例子。
图 5 S 盒输入输出例子
经过 S 盒代换过程,得到 8 个 4 位的分组,它们重新合在一起形成一个 32 位的输 出。这个分组将进入行下一组:P 盒置换。
P 盒的作用是进行线性置换(简单的位置置换)。经过 P 盒,由 32 位输入得到 32 位 输出。置换后的结果与最初的 64 位分组中的左半部分的 32 位进行异或,然后左、右半部分进行交换,接着开始下一轮迭代。P 盒置换如下表所示。
表 4 P 盒置换
3、密钥生成
在密钥生成算法中,主要经过了置换选择 PC-1、循环移位运算和置换选择 PC-2 的过程,最终生成所需要的 16 轮子密钥。如图 6 所示。
图 6 密钥生成算法
DES 实际有效的密钥长度是 56bit,在初始密钥中,第 8、16、24、32、40、48 位为 奇偶校验位,其余为有效位。
置换选择 PC-1(表 6)的作用是先去掉了 8bit 奇偶校验位,然后将剩下的 56bit 密 钥按照置换表的位置重新进行排列。在完成置换选择 PC-1 后,将 56bit 密钥分成左右各 28bit,然后左右分别循环左移,需要注意不同轮数需要的移位位数是不同的(见表 5), 并且左右两边是分开进行循环移位。然后合并左右两边,去掉固定位置的比特,然后再 经过置换选择 PC-2(表 7),PC-2 去掉了 C 中的第 9、18、22、25 位和 D 中的第 7、9、 15、26 位,其余部分经过置换后输出该轮 48bit 的轮密钥。
表 5 每轮移动的位数
表 6 置换选择 PC-1
表 7 置换选择 PC-2
4.DES解密
DES 解密过程与加密过程相同,只不过在 16 轮的迭代中使用子密钥的次序正好相 反,第一次迭代使用子密钥 K16,第二次迭代使用子密钥 K15,……下一轮的左边变成上一轮的右边,下一轮的左边和轮密钥(与加密使用轮密钥次序相反)作为轮函数的输入, 轮函数的输出和下一轮的右边异或后作为上一轮的左边。解密流程图如图 7 所示。
图 7 DES 解密流程图
三、方案实现
1.DES加密
1.1 流程图
图 8 DES加密算法流程图
图 9 DES加密算法密钥生成流程图
1.2 主要函数
void keygen();——设置种子密钥。此处为了让密钥更有意义,对字符按ASCCL码的码值进行2进制编码,转换为7位2进制数,最后一位为前7位的校验位,由此可以将有意义的英文字符串作为密钥,让算法效果更明显,同时方便记忆。接着按照DES密钥生成算法,进行置换,循环移位,再次置换得到每一轮的密钥。
int turnbinary();——从message.txt文件中读取明文,此处同样为了使得明文有意义,添加将明文按ASCCL码码值编码转换为2进制编码,每一个字符转换为8为2进制数,并存储到明文二维数组中,每64位为一组,记录组数以及储存的字符数(为后续译码恢复做准备)。
int Enc();——具体执行加密算法,为明文加密,并且将密文存入code.txt文件中。
1.3 DES加密主要代码
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#include <stdlib.h>
//注意,由于本代码使用文件输入,在运行本代码前,需要按自己的需求,填写目标文件和路径
//本文采用的文件是message.txt,存放路径是F:/visiua/my c program/cryptography/DES
//请修改后再运行
int Enc();
void keygen();
int turnbinary(); //将输入的明文文本转换为2进制
#define max 100 //定义最大的分组量为100
int IP1[] = { 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7 };//初始置换矩阵
int IP2[] = { 40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25 };
int PC1[] = { 57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4 };//秘钥的置换表1
int PC2[] = { 14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32 };//秘钥置换表2
int right[56];
int left[56];
int E[] = { 32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1 };//E置换矩阵
int P[] = { 16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25 };//p置换矩阵
//s和代换矩阵
int S[8][4][16] = {
// S1
{
{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
{4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
},
// S2
{
{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
{3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
{0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
},
// S3
{
{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
{13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
},
// S4
{
{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
{3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
},
// S5
{
{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
{14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
},
// S6
{
{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
},
// S7
{
{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
{13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
},
// S8
{
{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
{7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
}
};
// int* message[max];
int message[max][64];
int nummeassage = 0;//计数,第几组
int numcharmessage = 0;//计数,防止超出文本长度后出现乱码
int keys[16][48];//储存16轮的密钥
int turnbinary() {//将输入的明文文本转换为2进制
FILE* file = fopen("F:/visiua/my c program/cryptography/DES/message.txt", "r"); // 打开文件
if (file == NULL) {
printf("Failed to open file\n");
return -1;
}
char c;
// int onemessage[64];//一组数据
int count = 0;//计数,每64位存一次
printf("明文原文:\n");
c = getc(file);
while (c != EOF)
{
printf("%c", c);//打印明文
for (int i = 7;i >= 0;i--) {
// if (c >= 'a' && c <= "z") { cc = c - 'a'; }else if (c >= 'A' && c <= "Z")
// {
// /* code */
// }
message[nummeassage][count] = (c >> i) & 1;//由于地址会出错,直接存入数组
count++;
numcharmessage++;
}//将字符转换成8位的等长编码,按照ASCII码的值转换
if (count == 64) {//初始化
// message[nummeassage] = onemessage;
nummeassage++;
if (nummeassage == max) {
printf("overflow!\n");
}
count = 0;
}
c = getc(file);
}
if (count != 0)nummeassage++;//不满64,但是要多一组
// message[nummeassage] = onemessage;
// nummeassage++;
if (nummeassage == max) {
printf("overflow!\n");
}
count = 0;
fclose(file);
//打印转换后的明文
printf("\n编码后的明文:\n");
for (int i = 0;i < nummeassage;i++) {
for (int j = 0;j < 64;j++) {
printf("%d", message[i][j]);
}
}
}
void keygen() {
char k[] = "Fang Rao";
int keysand[64];
int count1 = 0;
int count2 = 0;
//将秘钥转换成二进制
while (k[count1] != 0)
{
int e = 1;
for (int i = 6;i >= 0;i--) {
keysand[count2] = (k[count1] >> i) & 1;
e = e ^ keysand[count2++];//奇偶校验位
}
keysand[count2++] = e;
count1++;
}
//打印种子秘钥
printf("种子密钥:\n");
for (int i = 0;i < 64;i++) {
printf("%d", keysand[i]);
}
printf("\n");
int key[48];//存储48位的生成密钥
int pckey[56];//用于储存置换后的密钥
//置换1
for (int i = 0;i < 56;i++) {
pckey[i] = keysand[PC1[i] - 1];
}
//将秘钥分成两份
int leftk[28], rightk[28];
for (int i = 0;i < 28;i++) {
leftk[i] = pckey[i];
rightk[i] = pckey[i + 28];
}
for (int round = 0;round < 16;round++) {
//处理每一轮的左右密钥
int move = 2;
switch (round)//轮数位1,2,9,16时左移一位,其他都是两位
{
case 1:
case 2:
case 9:
case 16:
move = 1;
break;
default:
move = 2;
}
//实现左移
for (int j = 0;j < move;j++) {
int temp1 = leftk[0];
int temp2 = rightk[0];
for (int i = 0;i < 27;i++) {
leftk[i] = leftk[i + 1];
rightk[i] = rightk[i + 1];
}
leftk[27] = temp1;
rightk[27] = temp2;
}
int combined_key[56];//存储左右合并后的密钥
for (int i = 0;i < 28;i++) {
combined_key[i] = leftk[i];
combined_key[i + 28] = rightk[i];
}
//置换2
printf("第%d的密钥K%d\n", round + 1, round + 1);//打印密钥
for (int i = 0;i < 48;i++) {
keys[round][i] = combined_key[PC2[i] - 1];
printf("%d", keys[round][i]);
}
printf("\n");
// keys[round] = key;//将48位生成密钥存入
}
}
void IPone(int num) {//初始置换
int* onemessage = message[num];
int ipm[64];//用于储存置换后的数据
for (int i = 0;i < 64;i++) {
ipm[i] = onemessage[IP1[i] - 1];
}
for (int i = 0;i < 64;i++) {
message[num][i] = ipm[i];
}
}
int Enc() {
int num = 0;
while (num < nummeassage)
{
int Code[64] = { 0 };
int combined_code[64];//储存合并完未逆初始转换的code
//初始置换
IPone(num);
//将明文分成两组
for (int i = 0;i < 32;i++) {
left[i] = message[num][i];
right[i] = message[num][i + 32];
}
int round = 0;//轮数
while (round != 16)//16轮变换
{
int reight[48], lleft[32], rsight[32], rpight[32];
//生成Li+1
for (int i = 0;i < 32;i++) {
lleft[i] = right[i];
}
//生成Ri+1
//E置换
for (int i = 0;i < 48;i++) {
reight[i] = right[E[i] - 1];
}
//引入密钥
for (int i = 0;i < 48;i++) {
reight[i] = reight[i] ^ keys[round][i];//密钥加
}
//s盒
for (int i = 0;i < 8;i++) {//6个位一组,进行代换
int h = reight[i * 6] * 2 + reight[i * 6 + 5];
int l = reight[i * 6 + 1] * 8 + reight[i * 6 + 2] * 4 + reight[i * 6 + 3] * 2 + reight[i * 6 + 4];
int nums = S[i][h][l];
for (int j = 3; j >= 0; j--) {
rsight[i * 4 + j] = nums % 2;
nums = nums / 2;//转换为2进制
}
}
//P置换
for (int i = 0;i < 32;i++) {
rpight[i] = rsight[P[i] - 1];
}
//更新每次的l和r
for (int i = 0;i < 32;i++) {
right[i] = rpight[i] ^ left[i];//32位,加入左边的异或
left[i] = lleft[i];
}
//为了使得加密与解密流程一致,最后一轮再将左右互换
if (round == 15)
{
for (int i = 0;i < 32;i++) {
int temp;
temp = left[i];
left[i] = right[i];
right[i] = temp;
}
}
round++;
}
//合并左右
for (int i = 0;i < 32;i++) {
combined_code[i] = left[i];
combined_code[i + 32] = right[i];
}
//逆初始置换
for (int i = 0;i < 64;i++) {
Code[i] = combined_code[IP2[i] - 1];
}
//写入文件
FILE* file = fopen("F:/visiua/my c program/cryptography/DES/code.txt", "a"); // 打开文件
if (file == NULL) {
printf("Failed to open outfile\n");
return -1;
}
for (int i = 0;i < 64;i++) {
// if (64 * num + i < numcharmessage)//防止文本溢出,出现乱码
// {
// fprintf(file, "%d", Code[i]);
// }
//此法不可行,一位密码的特性,需要保持64位的完整
fprintf(file, "%d", Code[i]);
printf("%d", Code[i]);
}
fclose(file);
num++;
}
//写入文件
FILE* file = fopen("F:/visiua/my c program/cryptography/DES/code.txt", "a"); // 打开文件
fprintf(file, "\n%d", numcharmessage);
fclose(file);
}
int main() {
keygen();
turnbinary();
printf("\n密文:\n");
Enc();
}
2. DES解密
2.1 流程图
图 10 DES解密算法流程图
2.2 主要函数
DES的解密过程与加密过程基本一致,只是修改了输入,密钥使用的顺序。
void keygen();——与加密完全一致。
int turnbinary();——从code.txt文件中读取密文,将密文直接读入数组中记录。读取字符个数,防止后续解码后恢复出的明文,出现无意义部分乱码。
Int DEc();——具体执行解密算法,为解密密文并且将明文存入message2.txt文件中。
2.3 DES解密主要代码
#include "stdio.h"
#include"string.h"
#include"ctype.h"
#include <unistd.h>
#include <stdlib.h>
//注意,由于本代码使用文件输入,在运行本代码前,需要按自己的需求,填写目标文件和路径
//本文输出算法采用的文件是code.txt与message2.txt,
//存放路径是F:/visiua/my c program/cryptography/DES
//请修改后再运行
//由F的特性,加密与解密流程基本相同,只是将置换顺序反之,密钥流反之
//主要任务,将文件读入修改,密钥使用顺序调整
int DEC();
void keygen();
int turnbinary(); //将输入的明文文本转换为2进制
#define max 100 //定义最大的分组量为100
int IP1[] = { 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7 };//初始置换矩阵
int IP2[] = { 40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25 };
int PC1[] = { 57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4 };//秘钥的置换表1
int PC2[] = { 14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32 };//秘钥置换表2
int right[56];
int left[56];
int E[] = { 32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1 };//E置换矩阵
int P[] = { 16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25 };//p置换矩阵
//s和代换矩阵
int S[8][4][16] = {
// S1
{
{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
{4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
},
// S2
{
{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
{3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
{0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
},
// S3
{
{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
{13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
},
// S4
{
{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
{3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
},
// S5
{
{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
{14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
},
// S6
{
{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
},
// S7
{
{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
{13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
},
// S8
{
{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
{7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
}
};
// int* message[max];
int message[max][64];
int nummeassage = 0;//计数,第几组
int numcharmessage = 0;//计数,防止超出文本长度后出现乱码
int keys[16][48];//储存16轮的密钥
int turnbinary() {//将输入的密文文本以2进制存入
FILE* file = fopen("F:/visiua/my c program/cryptography/DES/code.txt", "r"); // 打开文件
if (file == NULL) {
printf("Failed to open file\n");
return -1;
}
char c;
// int onemessage[64];//一组数据
int count = 0;//计数,每64位存一次
c = getc(file);
while (c != '\n')
{
message[nummeassage][count] = c - '0';//由于地址会出错,直接存入数组
count++;
if (count == 64) {//初始化
// message[nummeassage] = onemessage;
nummeassage++;
if (nummeassage == max) {
printf("overflow!\n");
}
count = 0;
}
c = getc(file);
}
fscanf(file, "%d", &numcharmessage);
// if (count != 0)nummeassage++;因为必然是64的整数倍
// message[nummeassage] = onemessage;
if (nummeassage == max) {
printf("overflow!\n");
}
count = 0;
fclose(file);
}
void keygen() {
char k[] = "Fang Rao";
int keysand[64];
int count1 = 0;
int count2 = 0;
//将秘钥转换成二进制
while (k[count1] != 0)
{
int e = 1;
for (int i = 6;i >= 0;i--) {
keysand[count2] = (k[count1] >> i) & 1;
e = e ^ keysand[count2++];//奇偶校验位
}
keysand[count2++] = e;
count1++;
}
int key[48];//存储48位的生成密钥
int pckey[56];//用于储存置换后的密钥
//置换1
for (int i = 0;i < 56;i++) {
pckey[i] = keysand[PC1[i] - 1];
}
//将秘钥分成两份
int leftk[28], rightk[28];
for (int i = 0;i < 28;i++) {
leftk[i] = pckey[i];
rightk[i] = pckey[i + 28];
}
for (int round = 0;round < 16;round++) {
//处理每一轮的左右密钥
int move = 2;
switch (round)//轮数位1,2,9,16时左移一位,其他都是两位
{
case 1:
case 2:
case 9:
case 16:
move = 1;
break;
default:
move = 2;
}
//实现左移
for (int j = 0;j < move;j++) {
int temp1 = leftk[0];
int temp2 = rightk[0];
for (int i = 0;i < 27;i++) {
leftk[i] = leftk[i + 1];
rightk[i] = rightk[i + 1];
}
leftk[27] = temp1;
rightk[27] = temp2;
}
int combined_key[56];//存储左右合并后的密钥
for (int i = 0;i < 28;i++) {
combined_key[i] = leftk[i];
combined_key[i + 28] = rightk[i];
}
//置换2
for (int i = 0;i < 48;i++) {
keys[round][i] = combined_key[PC2[i] - 1];
}
// keys[round] = key;//将48位生成密钥存入
}
}
void IPone(int num) {//初始置换
int* onemessage = message[num];
int ipm[64];//用于储存置换后的数据
for (int i = 0;i < 64;i++) {
ipm[i] = onemessage[IP1[i] - 1];
}
for (int i = 0;i < 64;i++) {
message[num][i] = ipm[i];
}
}
int DEC() {
int num = 0;
while (num < nummeassage)
{
int Code[64];
int combined_code[64];//储存合并完未逆初始转换的code
//初始置换
IPone(num);
//将明文分成两组
for (int i = 0;i < 32;i++) {
left[i] = message[num][i];
right[i] = message[num][i + 32];
}
int round = 0;//轮数
while (round != 16)//16轮变换
{
int reight[48], lleft[32], rsight[32], rpight[32];
//生成Li+1
for (int i = 0;i < 32;i++) {
lleft[i] = right[i];
}
//生成Ri+1
//E置换
for (int i = 0;i < 48;i++) {
reight[i] = right[E[i] - 1];
}
//引入密钥
for (int i = 0;i < 48;i++) {
reight[i] = reight[i] ^ keys[15 - round][i];//反序密钥加
}
//s盒
for (int i = 0;i < 8;i++) {//6个位一组,进行代换
int h = reight[i * 6] * 2 + reight[i * 6 + 5];
int l = reight[i * 6 + 1] * 8 + reight[i * 6 + 2] * 4 + reight[i * 6 + 3] * 2 + reight[i * 6 + 4];
int nums = S[i][h][l];
for (int j = 3; j >= 0; j--) {
rsight[i * 4 + j] = nums % 2;
nums = nums / 2;//转换为2进制
}
}
//P置换
for (int i = 0;i < 32;i++) {
rpight[i] = rsight[P[i] - 1];
}
//更新每次的l和r
for (int i = 0;i < 32;i++) {
right[i] = rpight[i] ^ left[i];//32位,加入左边的异或
left[i] = lleft[i];
}
//为了使得加密与解密流程一致,最后一轮再将左右互换
if (round == 15)
{
for (int i = 0;i < 32;i++) {
int temp;
temp = left[i];
left[i] = right[i];
right[i] = temp;
}
}
round++;
}
//合并左右
for (int i = 0;i < 32;i++) {
combined_code[i] = left[i];
combined_code[i + 32] = right[i];
}
//逆初始置换
for (int i = 0;i < 64;i++) {
Code[i] = combined_code[IP2[i] - 1];
}
//写入文件
FILE* file = fopen("F:/visiua/my c program/cryptography/DES/message2.txt", "a"); // 打开文件
if (file == NULL) {
printf("Failed to open outfile\n");
return -1;
}
//8为为一组,再次恢复出明文
for (int i = 0;i < 8;i++) {
int edc = 0;
for (int j = 0;j < 8;j++) {
edc = edc * 2 + Code[i * 8 + j];
}
if (64 * num + i * 8 < numcharmessage)//防止文本溢出,出现乱码
{
fprintf(file, "%c", edc);
}
// fprintf(file, "%c", edc);
}
num++;
fclose(file);
}
}
int main() {
keygen();
turnbinary();
DEC();
}
3.DES的差分攻击
3.1流程图
图 11 DES差分攻击算法流程图
3.2主要函数
SBox()——该函数用于实现DES算法中的S盒替换。它接受S盒编号和6位的输入差分,返回对应的4位输出。它通过查找预先定义好的S盒内容数组来实现S盒的置换操作。
differentialAttack()——这个函数是差分攻击的主要部分。它接受两个参数:输入差分和输出差分。函数通过对每个S盒进行遍历,并对每个可能的输入进行遍历,计算其输出,并通过差分分析比较输入差异和输出差异,寻找符合要求的输入。如果找到了一个输入差异,使得输出差异符合我们想要的要求,就输出符合要求的S盒的编号。
3.3代码实现
#include <stdio.h>
#include <stdint.h>
//注意,本代码只实现了对S盒的差分分析,对具体结果进行分析需自行补充
// DES S盒
static uint8_t S[8][4][16] = {
{
// S1
{14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
{0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
{4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
{15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
},
// ...
// S2, S3, S4, S5, S6, S7, S8
{
{15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
{3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
{0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
{13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
},
// S3
{
{10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
{13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
{13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
{1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
},
// S4
{
{7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
{13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
{10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
{3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
},
// S5
{
{2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
{14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
{4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
{11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
},
// S6
{
{12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
{10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
{9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
{4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
},
// S7
{
{4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
{13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
{1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
{6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
},
// S8
{
{13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
{1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
{7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
{2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
}
};
// S盒替换函数
uint8_t SBox(uint8_t input, uint8_t box_num) {
uint8_t row = ((input & 0x80) >> 4) | (input & 0x01);
uint8_t col = (input & 0x7E) >> 1;
return S[box_num][row][col];
}
// 差分攻击函数
void differentialAttack(uint8_t input_diff, uint8_t output_diff) {
// 对每个S盒进行攻击
for (int i = 0; i < 8; i++) {
// 对每个可能的输入差异进行攻击
for (uint8_t input = 0; input < 16; input++) {
uint8_t output = SBox(input, i);
if ((output ^ SBox(input ^ input_diff, i)) == output_diff) {
printf("SBox %d: Input %d -> Output %d\n", i + 1, input, output);
printf("SBox %d: Input %d -> Output %d\n", i + 1, input ^ input_diff, SBox(input ^ input_diff, i));
}
}
}
}
int main() {
// 进行差分攻击
uint8_t input_diff = 0x04;
uint8_t output_diff = 0x06;
differentialAttack(input_diff, output_diff);
return 0;
}
四、数据分析
1.加密
明文:I love cryptography.
进行8位2进制编码后的明文:
010010010010000001101100011011110111011001100101001000000110001101110010011110010111000001110100011011110110011101110010011000010111000001101000011110010010111000000000000000000000000000000000
密钥种子:“Fang Rao”
进行7位2进制转换,1位校验位后的密钥种子:
1000110011000010110111001100111001000000101001001100001011011111
中间数据包括明文 M,轮密钥 K1,K2,…,K16 及密文 C 等如图所示。
图 12 DES加密算法中间数据结果图
加密结果(两部分,第一行是DES运行完成的明文,第二行是字符个数,用与明文恢复)存入code.txt中,如图所示:
图 13 DES加密算法密文结果图
2.解密
密文:由文件读入
密钥种子:“Fang Rao”
进行7位2进制转换,1位校验位后的密钥种子:
1000110011000010110111001100111001000000101001001100001011011111
恢复出的明文:
I love cryptography.
图 14 DES解密算法明文结果图
3.攻击
对S盒进行分析,可以得到如下分析结果。
图 15 DES差分攻击算法分析结果图
五、思考与总结
1.DES算法从设计角度看是否存在什么缺陷?请简述。
答:
DES算法存在以下缺陷:
1. 密钥长度较短,仅有56位,容易受到暴力破解攻击。
2. 存在弱密钥和半弱密钥,这些密钥在加密和解密过程中会产生相同的结果,降低了安全性。
3. DES算法的轮数较少,只有16轮,这使得攻击者可以通过分析明文和密文之间的关系来获取密钥信息。
4. DES算法没有提供完整性保护,无法检测数据是否被篡改。
5. DES算法的设计没有公开,缺乏透明度。
6.无法抵御中途相遇攻击。
2.你了解到的针对DES算法的分析方法有哪些?请简述
答:
中途相遇攻击:给定一已知明密文对(x1, y1):以密钥k1的所有256个可能的取值对此明文x1加密,并将密文Z存储在一个表中;从所有可能的256个密钥k2中依任意次序选出一个对给定的密文y1解密,并将每次解密结果在上述表中查找相匹配的值Z 。一旦找到,则可确定出两个密钥k1和k2;用k1和k2对另一已知明文密文对(x2, y2)中的明文x2进行加密,如果能得出相应的密文y2就可确定k1和k2是所要找的密钥。
暴力破解:由于DES密钥长度为56位,理论上暴力破解需要2^56次尝试,对于现代计算机而言,这在可接受的时间范围内,尤其是当攻击者拥有一定计算资源时。
差分密码分析:差分密码分析是针对DES等对称加密算法的有效攻击手段之一。它利用了DES算法中的非线性组件(如S盒)的差分行为,通过大量的明文-密文对来推测密钥。
3.实验过程中还遇到了什么问题,如何解决的?通过该实验有何收获?
问题1:由于采用了将具体字符编码的方式作为明文,所面临的问题就是在恢复明文时,原文字符最后的一组分组未达到64位,但是加密所需明文长度为64,此时补全明文后会出现无意义的数据,如何在明文恢复时处理?
答:加密时,在读取数据同时记录字符个数,并且存入code.txt传递给解密算法,当达到改数据个数时,停止输出。
问题2:在储存密钥和储存明文时,使用指针数组进行记录,出现数据覆盖问题。
答:直接将数据存储在二维数组中。但是会出现一个潜在的问题就是,当数据量过大时,二维数组会出现溢出。但是在本算法中,每一组个数是固定的64位,避免了溢出问题。
收获:
通过本次实验,我对DES算法的运行机制获得了更加深入的理解。在实验过程中,我逐步剖析了DES算法的核心组件,如初始置换、函数f、S盒等,深感其设计的巧妙,但是对于为什么这样设计,这样的设计如何得到,这样设计有没有存在后面?等问题仍有疑问。同时,我也学会了如何根据密钥和明文生成密文,再将其解密还原为原始信息。不仅理论知识得以巩固,更培养了我解决实际问题的能力。