DES算法原理与实现

一、实验目的

(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 列重复了原数据的第4i(1\leqslant i\leqslant 8) 位,向下平移。表中第 6 列重复了 原数据的第4i+1(0\leqslant i\leqslant 7) 位,向上平移。F函数的输出经过一个异或运算,和左半部分结合,其结果成为新的右半部分,原来的右半部分成为新的左半部分。

 

表3 DES E 盒置换表

        E 盒能够生成与子密钥相同长度的数据与之进行异或,同时扩展后的数据经过 S 盒 压缩后,能够实现非线性变换。经过 E 盒扩展之后得到的 48 位数据,可分为 8 组,每 组 6 位。将其输入 8 个不同的 S 盒(S_{1}\sim S_{8}),每一个 S 盒都有 6 位输入,4 位输出的结 构。每个分组对应一个 S 盒操作,分组 1 由 S_{1}盒 操作,分组 2 由S_{2} 操作等等。如图 3 所示。S 盒(S_{1}\sim S_{8})如图 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盒等,深感其设计的巧妙,但是对于为什么这样设计,这样的设计如何得到,这样设计有没有存在后面?等问题仍有疑问。同时,我也学会了如何根据密钥和明文生成密文,再将其解密还原为原始信息。不仅理论知识得以巩固,更培养了我解决实际问题的能力。

  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值