数据加密标准(DES)的C++语言描述实现

前言

网络安全中数据加密标准(DES)的C++语言描述实现。


代码仓库


代码特点

纯C++语言:

  • 相对规范和整洁
  • 一定程度地面向对象
  • 使用一部分高级特性
  • 考虑优化性能

详细注释:

  • 提示规范和整洁
  • 提示面向对象
  • 提示高级特性
  • 提示优化性能
  • 解析数据加密标准(DES)步骤
  • 注意易错点

代码

des.h

//预处理指令——————————
//预定义宏
#ifndef DES_DES_H_
#define DES_DES_H_
//提示:防止头文件被重复包含

//标准库头文件
#include <iostream> //string,cout,endl
#include <vector>   //vector

//命名空间
//提示:
//使用using声明而不是using指示,以体现命名空间的作用
//本项目并未体现命名空间的作用,因为只使用一个命名空间std
using std::cout;
using std::endl;
using std::string;
using std::vector;

//自定义数据类型——————————
//类
//数据加密标准类
class DES
{
public:
    void encrypt(const string &plainText, const string &key, string &cipherTextASCII);      //加密
    void decrypt(const string &cipherTextASCII, const string &key, string &plainTextASCII); //解密
    //提示:
    //加密和解密过程相似,存在大量代码复用
    //但仍依据语义拆分成两函数,解耦合以能够单独调用
    //提示:返回值的数据类型为void而不是string,和其他函数形式统一,反正结果都会输出

private:
    //提示:属性初始化,避免未显式定义的默认构造函数调用造成未知错误
    //提示:因为会修改属性,所以不使用const修饰
    //提示:记录二进制而不是字符是数据加密标准的原貌,更有实际意义
    string plainTextASCII{""};  //明文的ASCII码  64位
    string keyASCII{""};        //密钥的ASCII码  64位
    string cipherTextASCII{""}; //密文的ASCII码 64位

    vector<string> wheelKeys{
        "", "", "", "",
        "", "", "", "",
        "", "", "", "",
        "", "", "", ""}; //轮密钥   16个,每个56位
    //注意:
    // vector<string> wheelKeys(16,"");报错
    // string默认为空字符串,可不初始化
    //可初始化,和其他属性初始化方式统一
    //提示:记录轮密钥,可将轮密钥生成过程和轮函数过程解耦合,可提前准备轮密钥

    //提示:将固有属性放在类中,而不是初始化为静态全局变量
    //提示:C++11新特性,可用大括号初始化任何类型
    //初始置换IP表
    const string ipTable{
        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};

    //密钥的选择置换表
    const string keySeRepTable{
        57, 49, 41, 33, 25, 17, 9,
        1, 58, 50, 42, 34, 26, 18,
        10, 2, 59, 51, 43, 35, 27,
        9, 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};

    //密钥的左移位表
    const vector<int> keyLeftMoveTable{1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};

    //密钥的压缩置换表
    const string keyComRepTable{
        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};

    //扩展置换E表
    const string eRepTable{
        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};

    // S盒选择代替表
    const vector<string> sBoxSeRepTable{
        // 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}};

    // 置换P表
    const string pRepTable{
        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};

    //逆初始置换IP-1表
    const string iIpTable{
        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};

    //提示:使用const表示只读;使用引用传地址而不是值,避免拷贝开销->搭配使用
    //提示:非常量引用实参可传递给常量引用形参
    void byteStrToBitStr(const string &str, string &bitStr); // 8字节字符串转64位字符串 依据ASCII码

    void wheelKeyGener(const string &preKey, const int &wheelCount, string &nextKey, string &wheelKey); //轮密钥生成
    void wheelFunc(const string &textIp, const string &keySeRep, string &textWheelF);                   //轮函数F
};

#endif // DES_DES_H_

des.cpp

//预处理指令——————————
//标准库头文件
#include <bitset>    //<bitset>
#include <algorithm> //reverse()
#include <string>    //stoi()

//自定义头文件
#include "des.h"

//命名空间
//提示:
//使用using声明而不是using指示,以体现命名空间的作用
//本项目并未体现命名空间的作用,因为只使用一个命名空间std
using std::bitset;

//加密
void DES::encrypt(const string &plainText, const string &key, string &cipherTextASCII) //参数:明文,密钥,密文的ASCII码
{
    cout << "加密过程:————————————————————" << endl;

    //对明文和密钥:
    cout << "明文:\t" << plainText << endl;
    cout << "密钥:\t" << key << endl;

    // 0.初始化为二进制  8字节->64位
    string plainTextASCII("");                        //明文的ASCII码   64位
    this->byteStrToBitStr(plainText, plainTextASCII); // 8字节字符串转64位字符串 依据ASCII码
    this->plainTextASCII = plainTextASCII;            //记录明文的ASCII码
    cout << "明文的ASCII码:\t" << this->plainTextASCII << endl;
    string keyASCII("");                  //密钥的ASCII码   64位
    this->byteStrToBitStr(key, keyASCII); // 8字节字符串转64位字符串 依据ASCII码
    this->keyASCII = keyASCII;            //记录密钥的ASCII码
    cout << "密钥的ASCII码:\t" << this->keyASCII << endl;
    cout << endl;

    //对密钥:
    // 1.密钥的选择置换 64->56位
    string keySeRep(56, '-'); //密钥的选择置换   56位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->keySeRepTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第57位是输出的第1位->输入的第56位是输出的第0位
        //注意:下标从0开始
        keySeRep[i] = this->keyASCII[this->keySeRepTable[i] - 1];
    }
    cout << "密钥的选择置换:\t" << keySeRep << endl;

    // 2.轮密钥生成
    //提示:预先生成
    string preKey(keySeRep);  //上一轮密钥 56位
    string nextKey("");       //下一轮密钥 56位
    string wheelKey(48, '-'); //轮密钥 48位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    //提示:
    //在循环外而不是循环中声明并初始化/定义对象,避免重复进行对象的构造和析构,减少开销
    //但可读性差

    // 16轮轮函数
    //注意:轮数从1开始
    for (int wheelCount = 1; wheelCount <= 16; ++wheelCount) //轮数计数
    {
        //轮密钥生成
        //参数:上一轮密钥(56位),轮数(从1开始),下一轮密钥(56位),轮密钥(48位)
        this->wheelKeyGener(preKey, wheelCount, nextKey, wheelKey);

        this->wheelKeys[wheelCount - 1] = wheelKey; //记录轮密钥
        //注意:轮数从1开始,轮密钥下标从0开始
        //注意:记录轮密钥(48位)而不是下一轮密钥(56位)

        //注意:
        //循环外定义的变量可能需要更新
        //循环内定义的变量为局部变量,不需要更新,方便
        preKey = nextKey; //更新上一轮密钥 56位
        nextKey = "";     //更新下一轮密钥 56位
        //轮密钥每轮都通过下标填充,不需要更新
    }

    //对明文:
    // 1.初始置换IP 64->64位
    string plainTextIp(64, '-'); //明文的初始置换IP   64位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->ipTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第58位是输出的第1位->输入的第57位是输出的第0位
        //注意:下标从0开始
        plainTextIp[i] = this->plainTextASCII[this->ipTable[i] - 1];
    }
    cout << "明文的初始置换IP:\t" << plainTextIp << endl;
    cout << endl;

    // 2.轮函数F
    string plainTextWheelF(""); //明文的轮函数  64位
    wheelFunc(plainTextIp, keySeRep, plainTextWheelF);
    //参数:明文的初始置换IP,密钥的选择置换,明文的轮函数
    cout << "明文的轮函数F:\t" << plainTextWheelF << endl;

    // 3.左右置换/轮函数F的第16轮不进行左右置换 64->64位
    string left(plainTextWheelF.substr(0, 32));   //左 范围:0~31
    string right(plainTextWheelF.substr(32, 32)); //右 范围:32~63
    string plainTextLeRiRep = right + left;       //明文的左右置换
    cout << "明文的左右置换:\t" << plainTextLeRiRep << endl;

    // 4.逆初始置换IP-1 64->64位
    string plainTextIIp(64, '-'); //明文的逆初始置换IP-1
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->iIpTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第40位是输出的第1位->输入的第39位是输出的第0位
        //注意:下标从0开始
        plainTextIIp[i] = plainTextLeRiRep[this->iIpTable[i] - 1];
    }
    cout << "明文的逆初始置换IP-1:\t" << plainTextIIp << endl;
    cout << endl;

    this->cipherTextASCII = plainTextIIp; //记录密文的ASCII码   密文的ASCII码为明文的逆初始置换IP-1
    cout << "密文的ASCII码:\t" << this->cipherTextASCII << endl;
    cout << endl;

    cipherTextASCII = plainTextIIp; //记录结果   密文的ASCII码为明文的逆初始置换IP-1

    return;
}

// 8字节字符串转64位字符串 依据ASCII码
void DES::byteStrToBitStr(const string &str, string &bitStr) //参数:8字节字符串,64位字符串
{
    for (const char &ch : str)
    {
        bitStr += bitset<8>(ch).to_string(); // 1字节字符转8位字符串    字符->bitset对象->字符串
    }

    return;
}

//轮密钥生成
//参数:上一轮密钥(56位),轮数(从1开始),下一轮密钥(56位),轮密钥(48位)
void DES::wheelKeyGener(const string &preKey, const int &wheelCount, string &nextKey, string &wheelKey)
{
    // 2.1等分上一轮密钥为上一轮左右    56->28+28位
    //注意:substr()的参数:开始下标,子串大小
    string preLeft(preKey.substr(0, 28));   //上一轮左  范围:0~27
    string preRight(preKey.substr(28, 28)); //上一轮右  范围:28~55

    // 2.2上一轮左右各循环左移/旋转
    //上一轮左的左移
    //局部翻转
    //注意:轮数从1开始,下标从0开始
    //注意:reverse()是左闭右开区间[)
    reverse(preLeft.begin(), preLeft.begin() + this->keyLeftMoveTable[wheelCount - 1]); //翻转[0,1)或[0,2)
    reverse(preLeft.begin() + this->keyLeftMoveTable[wheelCount - 1], preLeft.end());   //翻转[1,n)或[2,n)
    //整体翻转
    reverse(preLeft.begin(), preLeft.end()); //翻转[0,n)

    //上一轮右的左移位
    //局部翻转
    //注意:轮数从1开始,下标从0开始
    //注意:reverse()是左闭右开区间[)
    reverse(preRight.begin(), preRight.begin() + this->keyLeftMoveTable[wheelCount - 1]); //翻转[0,1)或[0,2)
    reverse(preRight.begin() + this->keyLeftMoveTable[wheelCount - 1], preRight.end());   //翻转[1,n)或[2,n)
    //整体翻转
    reverse(preRight.begin(), preRight.end()); //翻转[0,n)

    // 2.3拼接上一轮左右得下一轮密钥    28+28->56位
    nextKey = preLeft + preRight;

    // 2.4下一轮密钥的压缩置换得轮密钥    56->48位
    for (int i = 0; i < this->keyComRepTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第14位是输出的第1位->输入的第13位是输出的第0位
        //注意:下标从0开始
        wheelKey[i] = nextKey[this->keyComRepTable[i] - 1];
    }

    return;
}

//轮函数F
//注意:忘记加类作用域...
//参数:文本的初始置换IP,密钥的选择置换,文本的轮函数F
void DES::wheelFunc(const string &textIp, const string &keySeRep, string &textWheelF)
{
    string preText(textIp); //上一轮文本    64位
    //提示:
    //在循环外而不是循环中声明并初始化/定义对象,避免重复进行对象的构造和析构,减少开销
    //但可读性差

    // 16轮轮函数
    //注意:轮数从1开始
    for (int wheelCount = 1; wheelCount <= 16; ++wheelCount) //轮数计数
    {
        // 2.1等分上一轮文本为上一轮左右    64->32+32位
        //注意:substr()的参数:开始下标,子串大小
        string preLeft(preText.substr(0, 32));   //上一轮左   范围:0~31
        string preRight(preText.substr(32, 32)); //上一轮右 范围:32~63

        // 2.2下一轮左为上一轮右    32->32位
        string nextLeft(preRight);

        // 2.3.1下一轮右:上一轮右扩展置换E 32->48位
        string preRightERep(48, '-'); //上一轮右的扩展置换E
        //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
        //注意:初始化为不是0或1的字符,以验证上一轮右的扩展置换E是否成功
        for (int i = 0; i < this->eRepTable.size(); ++i)
        {
            //置换规则:依据置换表,输入的第32位是输出的第1位->输入的第31位是输出的第0位
            //注意:下标从0开始
            preRightERep[i] = preRight[this->eRepTable[i] - 1];
        }

        // 2.3.2下一轮右:上一轮右的扩展置换E(48位)与轮密钥(48位)异或 48w->48位
        string preRightXor(""); //上一轮右的异或
        for (int i = 0; i < preRightERep.size(); ++i)
        {
            //注意:轮数计数从1开始,轮密钥的下标从0开始
            if (preRightERep[i] == this->wheelKeys[wheelCount - 1][i]) //相同为0
            {
                preRightXor += '0';
            }
            else //不同为'1'
            {
                preRightXor += '1';
            }
        }

        // 2.3.4下一轮右:上一轮右的异或经S盒选择代替   48->32位
        string preRightSBoxSeRep(""); //上一轮右的S盒选择代替
        int sBoxCount = 0;            // S盒计数
        //注意:S盒计数从0开始
        for (int i = 0; i < preRightXor.size();) // i循环范围:0~47
        {
            string sBoxGroup(preRightXor.substr(i, 6)); // S盒组   一组从i开始取6位数
            //注意:substr()参数:开始下标,子串长度

            string rowStr{sBoxGroup[0], sBoxGroup[5]}; //行字符串:首尾2位
            //注意:初始化方式
            int rowNumber = stoi(rowStr, 0, 2); //行号
            //注意:stoi()参数:字符串,开始下标,字符串的基数

            string colStr(sBoxGroup.substr(1, 4)); //列字符串:中间4位
            //注意:初始化方式
            int colNumber = stoi(colStr, 0, 2); //列号
            //注意:stoi()参数:字符串,开始下标,字符串的基数

            int pos = rowNumber * 16 + colNumber; //位置:第rowNumber行第colNumber列
            //注意:行数从0开始,列数从0开始
            char sBoxNumber = this->sBoxSeRepTable[sBoxCount][pos]; // S盒数
            //注意:
            // S盒计数从0开始   第0盒->S1
            // S盒数是char数据类型

            preRightSBoxSeRep += bitset<4>(sBoxNumber).to_string(); // 1字节字符转4位字符串 字符->bitset对象->字符串

            sBoxCount += 1; // S盒计数+1
            i += 6;         //循环i+6
            // 注意:i变化范围:0~5,6~11,12~17,18...
        }

        // 2.3.5下一轮右:上一轮右置换P 32->32位
        string preRightPRep(32, '-'); //上一轮右的置换P
        //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
        //注意:初始化为不是0或1的字符,以验证是否成功

        for (int i = 0; i < this->pRepTable.size(); ++i)
        {
            //置换规则:依据置换表,输入的第16位是输出的第1位->输入的第15位是输出的第0位
            //注意:下标从0开始
            preRightPRep[i] = preRightSBoxSeRep[this->pRepTable[i] - 1];
        }

        // 2.3.6下一轮右:上一轮右与上一轮左异或    32->32位
        string preRightXor2(""); //下一轮右的异或2
        for (int i = 0; i < preLeft.size(); ++i)
        {
            if (preRightPRep[i] == preLeft[i]) //相同为0
            {
                preRightXor2 += '0';
            }
            else //不同为'1'
            {
                preRightXor2 += '1';
            }
        }

        string nextRight(preRightXor2); //下一轮右

        //注意:
        //循环外定义的变量可能需要更新
        //循环内定义的变量为局部变量,不需要更新,方便
        preText = nextLeft + nextRight; //更新上一轮文本

        cout << "明/密文的第" << wheelCount << "轮输出:\t" << preText << endl;
    }
    cout << endl;

    textWheelF = preText; //文本的轮函数F为上一轮文本

    return;
}

//解密
//注意过程:
// 1.初始置换IP
// 2.轮函数F
// 3.左右置换
// 4.逆初始置换IP-1
//提示:因为通过重构解耦合,直接复用加密函数代码,修改变量名称和注释即可
//注意:
//加密的轮函数F中,使用轮密钥K1,K2...K16,轮函数下标0,1...15
//解密的轮函数F中,使用轮密钥K16,K15...K1,轮函数下标15,14...1
//所以,解密的轮密钥生成中,反向记录轮密钥,即可复用相同的轮函数函数
void DES::decrypt(const string &cipherTextASCII, const string &key, string &plainTextASCII) //参数:密文的ASCII码,密钥,明文的ASCII码
{
    cout << "解密过程:————————————————————" << endl;

    //对密文和密钥:
    cout << "密钥:\t" << key << endl;

    this->cipherTextASCII = cipherTextASCII; //记录密文的ASCII码
    cout << "密文的ASCII码:\t" << this->cipherTextASCII << endl;
    // 0.初始化为二进制  8字节->64位
    string keyASCII("");                  //密钥的ASCII码   64位
    this->byteStrToBitStr(key, keyASCII); // 8字节字符串转64位字符串 依据ASCII码
    this->keyASCII = keyASCII;            //记录密钥的ASCII码
    cout << "密钥的ASCII码:\t" << this->keyASCII << endl;
    cout << endl;

    //对密钥:
    // 1.密钥的选择置换 64->56位
    string keySeRep(56, '-'); //密钥的选择置换   56位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->keySeRepTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第57位是输出的第1位->输入的第56位是输出的第0位
        //注意:下标从0开始
        keySeRep[i] = this->keyASCII[this->keySeRepTable[i] - 1];
    }
    cout << "密钥的选择置换:\t" << keySeRep << endl;

    // 2.轮密钥生成
    //注意:预先生成
    string preKey(keySeRep);  //上一轮密钥 56位
    string nextKey("");       //下一轮密钥 56位
    string wheelKey(48, '-'); //轮密钥 48位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    //提示:
    //在循环外而不是循环中声明并初始化/定义对象,避免重复进行对象的构造和析构,减少开销
    //但可读性差

    // 16轮轮函数
    //注意:轮数从1开始
    for (int wheelCount = 1; wheelCount <= 16; ++wheelCount) //轮数计数
    {
        //轮密钥生成
        //参数:上一轮密钥(56位),轮数(从1开始),下一轮密钥(56位),轮密钥(48位)
        this->wheelKeyGener(preKey, wheelCount, nextKey, wheelKey);

        //记录轮密钥
        this->wheelKeys[16 - wheelCount] = wheelKey;
        //注意:轮数从1开始,轮密钥下标从15开始
        //注意:记录轮密钥(48位)而不是下一轮密钥(56位)

        //注意:
        //循环外定义的变量可能需要更新
        //循环内定义的变量为局部变量,不需要更新,方便
        preKey = nextKey; //更新上一轮密钥 56位
        nextKey = "";     //更新下一轮密钥 56位
        //轮密钥每轮都通过下标填充,不需要更新
    }

    //对密文:
    // 1.初始置换IP 64->64位
    string cipherTextIp(64, '-'); //密文的初始置换IP   64位
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->ipTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第58位是输出的第1位->输入的第57位是输出的第0位
        //注意:下标从0开始
        cipherTextIp[i] = this->cipherTextASCII[this->ipTable[i] - 1];
        //注意:忘记修改this->plain为cipher了...
    }
    cout << "密文的初始置换IP:\t" << cipherTextIp << endl;
    cout << endl;

    // 2.轮函数F
    string cipherTextWheelF(""); //密文的轮函数  64位
    wheelFunc(cipherTextIp, keySeRep, cipherTextWheelF);
    //参数:密文的初始置换IP,密钥的选择置换,密文的轮函数
    cout << "密文的轮函数F:\t" << cipherTextWheelF << endl;

    // 3.左右置换/轮函数F的第16轮不进行左右置换 64->64位
    string left(cipherTextWheelF.substr(0, 32));   //左 范围:0~31
    string right(cipherTextWheelF.substr(32, 32)); //右 范围:32~63
    string cipherTextLeRiRep = right + left;       //密文的左右置换
    cout << "密文的左右置换:\t" << cipherTextLeRiRep << endl;

    // 4.逆初始置换IP-1 64->64位
    string cipherTextIIp(64, '-'); //密文的逆初始置换IP-1
    //注意:因为需要使用下标赋值,所以需要初始化大小以预分配空间
    //注意:初始化为不是0或1的字符,以验证是否成功
    for (int i = 0; i < this->iIpTable.size(); ++i)
    {
        //置换规则:依据置换表,输入的第40位是输出的第1位->输入的第39位是输出的第0位
        //注意:下标从0开始
        cipherTextIIp[i] = cipherTextLeRiRep[this->iIpTable[i] - 1];
    }
    cout << "密文的逆初始置换IP-1:\t" << cipherTextIIp << endl;
    cout << endl;

    this->plainTextASCII = cipherTextIIp; //记录明文的ASCII码   明文的ASCII码为密文的逆初始置换IP-1
    cout << "明文的ASCII码:\t" << this->plainTextASCII << endl;
    cout << endl;

    plainTextASCII = cipherTextIIp; //记录结果   明文的ASCII码为密文的逆初始置换IP-1

    return;
}

main.cpp

//预处理指令——————————
//自定义头文件
#include "des.h"

//函数声明——————————
void compareStrBit(string str1, string str2); //比较两字符串不同的位数

//主函数——————————
int main()
{
    DES *des = new DES(); //数据加密标准对象
    //提示:
    //复杂类型/自定义类型可能占用空间大,堆区比栈区大,需要在堆区手动管理
    //手动管理:使用new创建对象,使用delete释放对象->搭配使用

    const string plainText("yezhenin"); //明文
    const string key("91002705");       //密钥
    string cipherTextASCII("");         //密文的ASCII码
    //提示:
    //密文的ASCII码没有必要转换为字符,因为:
    // 1.转换的字符无意义
    // 2.转换的字符可能为非打印字符
    string plainTextASCII(""); //明文的ASCII码

    des->encrypt(plainText, key, cipherTextASCII); //加密
    //参数:明文(已知),密钥(已知),密文的ASCII码(未知)

    des->decrypt(cipherTextASCII, key, plainTextASCII); //解密
    //参数:密文的ASCII码(已知),密钥(已知),明文的ASCII码(未知)

    //字符转ASCII码转换器网址:https://www.qqxiuzi.cn/bianma/ascii.htm
    // ASCII码转字符转换器网址:https://www.asciim.cn/m/tools/convert_ascii_to_string.html

    //雪崩效应测试
    //提示:
    //由设计接口:测试明文变化需要考虑到字节层面,传递明文参数;测试密文变化需要考虑到位层面,传递密文的ASCII码参数
    //因为加密和解密过程相似,所以变化密文进行测试

    string cipherTextCo = "0000110101111001101000001011100001101001111000010100000001011011"; //密文正确码
    string plainTextCo = "0111100101100101011110100110100001100101011011100110100101101110";  //明文正确码

    //第一组
    string cipherTextEr = cipherTextCo;           //密文错误码为密文正确码
    cipherTextEr[0] = 1;                          //修改1位:0->1
    string plainTextEr("");                       //明文错误码
    des->decrypt(cipherTextEr, key, plainTextEr); //解密
    //参数:密文错误码(已知),密钥(已知),明文错误码(未知)
    compareStrBit(plainTextCo, plainTextEr); //比较两字符串不同的位数

    //第二组
    cipherTextEr = cipherTextCo;                  //密文错误码为密文正确码
    cipherTextEr[0] = 1;                          //修改1位:0->1
    cipherTextEr[63] = 0;                         //修改1位:1->0
    plainTextEr = "";                             //明文错误码
    des->decrypt(cipherTextEr, key, plainTextEr); //解密
    //参数:密文错误码(已知),密钥(已知),明文错误码(未知)
    compareStrBit(plainTextCo, plainTextEr); //较两字符串不同的位数

    //第三组
    cipherTextEr = cipherTextCo;                  //密文错误码为密文正确码
    cipherTextEr[0] = 1;                          //修改1位:0->1
    cipherTextEr[1] = 1;                          //修改1位:0->1
    cipherTextEr[63] = 0;                         //修改1位:1->0
    plainTextEr = "";                             //明文错误码
    des->decrypt(cipherTextEr, key, plainTextEr); //解密
    //参数:密文错误码(已知),密钥(已知),明文错误码(未知)
    compareStrBit(plainTextCo, plainTextEr); //较两字符串不同的位数

    delete des;

    return 0;
}

//比较两字符串不同的位数
void compareStrBit(string str1, string str2)
{
    int diffBitCount = 0; //不同位计数
    for (int i = 0; i < str1.size(); ++i)
    {
        if (str1[i] != str2[i])
        {
            ++diffBitCount;
        }
    }

    cout << "不同位数:\t" << diffBitCount << endl;
    cout << endl;

    return;
}

结果

加密过程

在这里插入图片描述


解密过程

在这里插入图片描述


雪崩效应

  • 修改密文的1位,解密的错误明文与正确明文的不同位数为35:
    在这里插入图片描述
  • 修改密文的2位,解密的错误明文与正确明文的不同位数为31:
    在这里插入图片描述
  • 修改密文的3位,解密的错误明文与正确明文的不同位数为30:
    在这里插入图片描述

分析:

  • 明文和密钥/密文的一位发生变化会导致密文/明文的多位发生变化

  • 明文/密文发生变化的位数近似为明文/密文的二分之一(64÷2=32位)

  • 数据加密标准(DES)满足雪崩效应,强度高


总结

网络安全中数据加密标准(DES)的C++语言描述实现。


参考资料

  • 《密码编码学与网络安全——原理与实践(第五版)》作者:William Stallings

作者的话

  • 感谢参考资料的作者/博主
  • 作者:夜悊
  • 版权所有,转载请注明出处,谢谢~
  • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
  • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
  • 文章在认识上有错误的地方, 敬请批评指正
  • 望读者们都能有所收获

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值