这学期选了一门课: 计算机安全导论。据说主讲密码学,所以就先学习一下相关的知识啦~ 这几天学的是 DES算法。

初学只写了 ECB 模式下的 DES。其他的等以后再说吧 XD

1. 算法原理

1.1 什么是 DES ?

数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。它基于使用56位密钥的对称算法。这个算法因为包含一些机密设计元素,相对短的密钥长度以及怀疑内含美国国家安全局(NSA)的后门而在开始时有争议,DES因此受到了强烈的学院派式的审查,并以此推动了现代的块密码及其密码分析的发展。——Wikipedia

1.2 算法流程

DES 是一个分组加密算法,典型的 DES 以64位为分组对数据加密,加密和解密用的是同一个算法。其大致加密流程如下图:

des.jpg

下面我来慢慢分析这些都是什么意思吧~

2. 算法实现

2.1 IP置换

IP置换目的是将输入的64位数据块按位重新组合,并把输出分为L0、R0两部分,每部分各长32位。

置换规则如下表所示:

// 初始置换表
const int IP[64] = { 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 };

表中的数字代表新数据中此位置的数据在原数据中的位置,即原数据块的第58位放到新数据的第1位,第50位放到第2位,……依此类推。置换后的数据分为 L0 和 R0 两部分, L0 为新数据的左32位,R0 为新数据的右32位。

要注意一点,位数是从左边开始数的,即最0x0000 0080 0000 0002最左边的位为1,最右边的位为64。

2.2 生成子密钥

从流程图中可以看出,我们在进行下一步加密的时候需要一个密钥 K,且之后的每一轮都需要密钥。我们知道 DES 需要输入一个64位的密钥,那剩余的密钥就肯定是从这个密钥变化来的。下面就讨论一下这些子密钥是怎么来的。

2.2.1 去除奇偶校验位

不考虑每个字节的第8位,将 DES 的密钥由64位减至56位,每个字节的第8位作为奇偶校验位。产生的56位密钥由下表生成(注意表中没有8,16,24,32,40,48,56和64这8位):

    // 密钥置换表,将64位密钥变成56位
const int PC_1[56] = { 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 };

2.2.2 循环左移

在DES的每一轮中,从56位密钥产生出不同的48位子密钥,确定这些子密钥的方式如下:

  1. 将56位的密钥分成两部分,每部分28位。
  2. 根据轮数,这两部分分别循环左移1位或2位。每轮移动的位数如下表:
// 每轮左移的位数
const int shiftBits[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };

2.2.3 压缩置换

移动后,从56位中选出48位。这个过程中,既置换了每位的顺序,又选择了子密钥,因此称为压缩置换。压缩置换规则如下表(注意表中没有9,18,22,25,35,38,43和54这8位):

// 压缩置换,将56位密钥压缩成48位子密钥
const int PC_2[48] = { 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 };

至此,新的密钥就已经计算出来了。按照这个方法进行16轮,就可以得到16个子密钥。

2.3 加密处理-迭代

这个过程中数据和子密钥将通过 f 函数结合在一起。下面来分析一下这个 f 函数。

2.3.1 E扩展置换

我们上面生成的子密钥都是48位的,而输入的数据则是32位的,所以我们需要将数据扩展成48位再进行运算。

扩展置换目的有两个:

  • 生成与密钥相同长度的数据以进行异或运算;
  • 提供更长的结果,在后续的替代运算中可以进行压缩。

扩展的原理如下表:

// 扩展置换表,将 32位 扩展至 48位
const int E[48] = { 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 };

2.3.2 S盒替换

压缩后的密钥与扩展分组异或以后得到48位的数据,将这个数据送人S盒,进行替代运算。替代由8个不同的S盒完成,每个S盒有6位输入4位输出。48位输入分为8个6位的分组,一个分组对应一个S盒,对应的S盒对各组进行代替操作。

一个S盒就是一个4行16列的表,盒中的每一项都是一个4位的数。S盒的6个输入确定了其对应的输出在哪一行哪一列,输入的高低两位做为行数H,中间四位做为列数L,在S-BOX中查找第H行L列对应的数据(<32)

    // S盒,每个S盒是4x16的置换表,6位 -> 4位
    const int S_BOX[8][4][16] = {
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        }
    };

代替过程产生8个4位的分组,组合在一起形成32位数据。

S盒代替是 DES 算法的关键步骤,所有的其他的运算都是线性的,易于分析,而S盒是非线性的,相比于其他步骤,提供了更好安全性。

2.3.3 P盒置换

S盒代替运算的32位输出按照P盒进行置换。该置换把输入的每位映射到输出位,任何一位不能被映射两次,也不能被略去,映射规则如下表:

// P置换,32位 -> 32位
const int P[32] = { 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盒置换的结果与最初的64位分组左半部分L0异或,然后左、右半部分交换。

至此,f 函数就执行了一遍。按照上述方法执行16轮即可。

2.4 IP-1末置换

末置换是初始置换的逆过程,DES最后一轮后,左、右两半部分并未进行交换,而是两部分合并形成一个分组做为末置换的输入。末置换规则如下表:

    // 结尾置换表
    const int IP_1[64] = { 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 };

注意合并的时候顺序不能反,R16 在前,L16 在后。

经过以上步骤即可得到密文。

3. C++ 代码实现

网上找的代码好多都是有问题的,搞得我对着python的代码 debug 了几天才修完。这里放上我写的 DES 加密代码吧。有问题的话希望大佬指正。(不要问为什么用C++不用python,问就是快 (ಡωಡ) )

DES.h

#pragma once

#include <iostream>
#include <bitset>
#include <string>
#include <vector>

#define ECB 0 // 电子密码本模式(Electronic Codebook Book)
#define CBC 1 // 密码分组链接模式(Cipher Block Chaining)
#define CFB 2 // 密码反馈模式(Cipher FeedBack)
#define OFB 3 // 输出反馈模式(Output FeedBack)
#define CTR 4 // 计算器模式(Counter)

using std::bitset;
using std::string;
using std::vector;
using std::cout;
using std::endl;

const int BLOCK_SIZE = 8;

class DES
{
private:
    // 输入的64位密钥和子密钥
    bitset<64> key;
    bitset<48> subKey[16];
    
    string OFB_stream; // OFB 加密模式用的流
    vector<string> CTR_stream; // CTR 加密模式用的流

    // 初始置换表
    const int IP[64] = { 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 int IP_1[64] = { 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 };

    /*------------------下面是生成密钥所用表-----------------*/

    // 密钥置换表,将64位密钥变成56位
    const int PC_1[56] = { 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 };

    // 压缩置换,将56位密钥压缩成48位子密钥
    const int PC_2[48] = { 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 };

    // 每轮左移的位数
    const int shiftBits[16] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 };

    /*------------------下面是密码函数 f 所用表-----------------*/

    // 扩展置换表,将 32位 扩展至 48位
    const int E[48] = { 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盒,每个S盒是4x16的置换表,6位 -> 4位
    const int S_BOX[8][4][16] = {
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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}
        },
        {
            {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置换,32位 -> 32位
    const int P[32] = { 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 };

    
    /**********************************************************************/
    /*                                                                    */
    /*                  下面是DES算法实现所需的函数                       */
    /*                                                                    */
    /**********************************************************************/

protected:
    bitset<32> f(bitset<32> R, bitset<48> k); // 加密时的 f 函数
    static bitset<28> left_shift(bitset<28> k, int shift); // 循环左移

    static string padding(string str, const int& unit = 8);
    string depadding(const string& str);

    void generate_key(); // 生成子密钥
    bitset<64> _encrypt(bitset<64> & plain); // 对 64位 数据进行加密用的函数
    bitset<64> _decrypt(bitset<64> & plain); // 对 64位 数据进行解密用的函数

    string ECB_encryption(const string& plain);
    string ECB_decryption(const string& cipher);

    string CBC_encryption(const string& plain, const string& IV);
    string CBC_decryption(const string& cipher, const string& IV);

    string CFB_encryption(const string& plain, const string& IV, const int& segment_size = 8);
    string CFB_decryption(const string& cipher, const string& IV, const int& segment_size = 8);

    string OFB_encryption(const string& plain, const string& IV);
    string OFB_decryption(const string& cipher, const string& IV);

    string CTR_encryption(const string& plain, const string& nonce);
    string CTR_decryption(const string& cipher, const string& nonce);

public:
    DES();
    explicit DES(const string& key);
    ~DES();
    
    /* 工具函数 */
    static bitset<64> char_to_bitset(const char s[8]);
    static string bitset64_to_string(bitset<64> bit);
    static string bitset64_to_hex(bitset<64> bit);

    bool set_key(const string& _key);
    
    string encrypt(const string& plain, int mode = ECB, const string& IV = "", const int& segment_size = 8);
    string decrypt(const string& cipher, int mode = ECB, const string& IV = "", const int& segment_size = 8);
};

/**
 * \brief string 转换为 hex
 */
inline string string2hex(const string& in)
{
    string res;

    for (auto i : in)
    {
        int tmp = static_cast<int>(i);

        int high = (tmp & 0xf0) >> 4;
        int low = (tmp & 0x0f);

        res += (high < 10 ? '0' + high : 'a' + high - 10);
        res += (low < 10 ? '0' + low : 'a' + low - 10);
    }

    return res;
}

inline string hex2string(const string& in)
{
    // 长度不是偶数说明有问题
    if (in.length() % 2 != 0)
        throw "Hex length error!";

    string res;

    for (auto i = 0; i < in.length(); i += 2)
    {
        int tmp = 0;

        tmp += (in[i] - '0' > 10) ? (in[i] - 'a' + 10) * 16 : (in[i] - '0') * 16;
        tmp += (in[i + 1] - '0' > 10) ? (in[i + 1] - 'a' + 10) : (in[i + 1] - '0');

        res += static_cast<char>(tmp);
    }

    return res;
}

DES.cpp

#include "DES.h"

/**
 * \brief 密码函数 f
 * \param R 输入的 32 位数据
 * \param k 48 位子密钥
 * \return 32 位的输出
 */
bitset<32> DES::f(bitset<32> R, bitset<48> k)
{
    bitset<48> expandR;

    //第一步: 扩展置换 R
    for (auto i = 0; i < 48; i++)
        expandR[i] = R[E[i] - 1];

    //第二步: 异或
    expandR ^= k;

    //第三步: 送入S盒 置换
    int pos = 0; //在 output 中应输入的位置
    bitset<32> output;

    for (auto i = 0; i < 48; i += 6)
    {
        // 计算 S盒 中的行列
        int row = expandR[i] * 2 + expandR[i + 5];
        int column = expandR[i + 1] * 8 + expandR[i + 2] * 4 \
            + expandR[i + 3] * 2 + expandR[i + 4];
        int num = S_BOX[i / 6][row][column];

        bitset<4> tmp(num);

        for (auto j = 0; j < 4; j++)
            output[pos + j] = tmp[3 - j];

        pos += 4;
    }

    // 第四步: P置换
    bitset<32> tmp = output;

    for (auto i = 0; i < 32; i++)
        output[i] = tmp[P[i] - 1];

    return output;
}

/**
 * \brief 对密钥的两部分进行左移
 * \param k 56位密钥的一部分
 * \param shift 左移位数
 * \return 位移后的数据
 */
bitset<28> DES::left_shift(bitset<28> k, int shift)
{
    bitset<28> tmp = k;

    for (auto i = 0; i < 28; i++)
        k[i] = tmp[(i + shift) % 28];

    return k;
}

/**
 * \brief 生成16个48位子密钥
 */
void DES::generate_key()
{
    bitset<56> realKey;
    bitset<28> left, right;
    bitset<48> compressKey;

    // 去除奇偶校验位
    for (auto i = 0; i < 56; i++)
        realKey[i] = key[PC_1[i] - 1];

    // 生成子密钥
    for (auto round = 0; round < 16; round++)
    {
        // 前28位 与 后28位
        for (auto i = 28; i < 56; i++)
            right[i - 28] = realKey[i];

        for (auto i = 0; i < 28; i++)
            left[i] = realKey[i];

        // 左移
        left = left_shift(left, shiftBits[round]);
        right = left_shift(right, shiftBits[round]);

        // 压缩置换
        for (auto i = 28; i < 56; i++)
            realKey[i] = right[i - 28];
        
        for (auto i = 0; i < 28; i++)
            realKey[i] = left[i];

        for (auto i = 0; i < 48; i++)
            compressKey[i] = realKey[PC_2[i] - 1];

        subKey[round] = compressKey;
    }

    return;
}

/**
 * \brief 对明文进行 padding
 */
string DES::padding(string str, const int& unit)
{
    if (unit > 16)
        throw "Unit error!";

    const int n = unit - (str.length() % unit);
    const char pad = static_cast<char>(n);

    for (auto i = 0; i < n; i++)
        str += pad;

    return str;
}

/**
 * \brief 去除 padding
 */
string DES::depadding(const string& str)
{
    string res = str;
    const auto mark = str[str.length() - 1];
    
    for (auto i = 0; i < mark; i++)
        res.pop_back();

    return res;
}

/**
 * \brief DES加密
 * \param plain 输入的明文
 * \param status 真为加密,假为解密
 * \return 加密后的密文
 */
bitset<64> DES::_encrypt(bitset<64> & plain)
{
    bitset<64> cipher, currentBits;
    bitset<32> left, right;

    // 第一步: 置换IP
    for (auto i = 0; i < 64; i++)
        currentBits[i] = plain[IP[i] - 1];

    // 第二步: 获取 L0 和 R0
    for (auto i = 0; i < 32; i++)
        left[i] = currentBits[i];

    for (auto i = 32; i < 64; i++)
        right[i - 32] = currentBits[i];

    // 第三步: 十六轮迭代
    for (const auto k : subKey)
    {
        const auto new_left = right;
        right = left ^ f(right, k);
        left = new_left;
    }

    // 第四步: 合并 R16 和 L16 (顺序不能反)
    for (auto i = 0; i < 32; i++)
        cipher[i] = right[i];

    for (auto i = 32; i < 64; i++)
        cipher[i] = left[i - 32];

    //第五步: 末置换 IP-1
    currentBits = cipher;

    for (auto i = 0; i < 64; i++)
        cipher[i] = currentBits[IP_1[i] - 1];

    //返回密文
    return cipher;
}

bitset<64> DES::_decrypt(bitset<64> & plain)
{
    bitset<64> cipher, currentBits;
    bitset<32> left, right;

    // 第一步: 置换IP
    for (auto i = 0; i < 64; i++)
        currentBits[i] = plain[IP[i] - 1];

    // 第二步: 获取 L0 和 R0
    for (auto i = 0; i < 32; i++)
        left[i] = currentBits[i];

    for (auto i = 32; i < 64; i++)
        right[i - 32] = currentBits[i];

    // 第三步: 十六轮迭代
    for (auto i = 15; i >= 0; i--)
    {
        const auto new_left = right;
        right = left ^ f(right, subKey[i]);
        left = new_left;
    }

    // 第四步: 合并 R16 和 L16 (顺序不能反)
    for (auto i = 0; i < 32; i++)
        cipher[i] = right[i];

    for (auto i = 32; i < 64; i++)
        cipher[i] = left[i - 32];

    //第五步: 末置换 IP-1
    currentBits = cipher;

    for (auto i = 0; i < 64; i++)
        cipher[i] = currentBits[IP_1[i] - 1];

    //返回密文
    return cipher;
}

/**
 * \brief ECB 模式下的 DES 加密
 */
string DES::ECB_encryption(const string& plain)
{
    if (plain.length() % BLOCK_SIZE != 0)
        throw "Plain text's length must be a multiple of 8";

    string res;
    res.clear();

    for (auto i = 0; i < plain.length(); i += 8)
    {
        string subText(plain, i, 8);
        bitset<64> tmp = char_to_bitset(subText.c_str());
        tmp = _encrypt(tmp);
        res += bitset64_to_string(tmp);
    }

    return res;
}

/**
 * \brief ECB 模式下的 DES 解密
 */
string DES::ECB_decryption(const string& cipher)
{
    if (cipher.length() % BLOCK_SIZE != 0)
        throw "Cipher text's length must be a multiple of 8";

    string res;
    res.clear();

    for (auto i = 0; i < cipher.length(); i += 8)
    {
        string subText(cipher, i, 8);
        bitset<64> tmp = char_to_bitset(subText.c_str());
        tmp = _decrypt(tmp);
        res += bitset64_to_string(tmp);
    }

    return res;
}

/**
 * \brief CBC 模式下的 DES 加密
 */
string DES::CBC_encryption(const string& plain, const string& IV)
{
    if (plain.length() % BLOCK_SIZE != 0)
        throw "Plain text's length must be a multiple of 8";

    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    string res;
    res.clear();

    string last = IV; // 需要被异或的字符串

    for (auto i = 0; i < plain.length(); i += BLOCK_SIZE)
    {
        string subText(plain, i, BLOCK_SIZE);

        for (auto i = 0; i < BLOCK_SIZE; i++)
            subText[i] ^= last[i];
        
        bitset<64> tmp = char_to_bitset(subText.c_str());
        tmp = _encrypt(tmp);
        
        // 更新需要异或的字符串
        last = bitset64_to_string(tmp);

        res += last;
    }

    return res;
}

/**
 * \brief CBC 模式下的 DES 解密
 */
string DES::CBC_decryption(const string& cipher, const string& IV)
{
    if (cipher.length() % BLOCK_SIZE != 0)
        throw "Cipher text's length must be a multiple of 8";

    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    string res;
    res.clear();

    string last = IV; // 需要被异或的字符串

    for (auto i = 0; i < cipher.length(); i += BLOCK_SIZE)
    {
        string subText(cipher, i, BLOCK_SIZE);

        for (auto i = 0; i < BLOCK_SIZE; i++)
            subText[i] ^= last[i];

        bitset<64> tmp = char_to_bitset(subText.c_str());
        tmp = _encrypt(tmp);

        // 更新需要异或的字符串
        last = string(cipher, i, BLOCK_SIZE);
        
        res += bitset64_to_string(tmp);
    }

    return res;
}

/**
 * \brief CFB 模式下的 DES 加密
 * \param segment_size 每次加密的长度, 单位是位
 */
string DES::CFB_encryption(const string& plain, const string& IV, const int& segment_size)
{
    if (segment_size % 8 != 0)
        throw "Segment size must be a multiple of 8!";

    if (plain.length() % BLOCK_SIZE != 0)
        throw "Plain text's length must be a multiple of 8";

    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    const int real_size = segment_size / 8;
    string shift_register = IV; // 模拟移位寄存器
    string res;

    for (auto begin = 0; begin < plain.length(); begin += real_size)
    {
        // 对移位器中数据进行加密
        bitset<64> subText = char_to_bitset(shift_register.c_str());
        subText = _encrypt(subText);
        
        // 注意这里不是直接保存在移位寄存器中的
        string encrypted_buffer = bitset64_to_string(subText);

        string tmp;

        // 从Encrypted Buffer左侧取出Real Size个字节,与长度为Real Size的Plain Block进行异或操作
        for (auto i = 0; i < real_size; i++)
            tmp += plain[begin + i] ^ encrypted_buffer[i];

        res += tmp;

        // 将移位器中的数据左移Real Size个字节
        shift_register = string(shift_register, real_size);

        // 将前面的加密结果从右侧移入寄存器
        shift_register += tmp;
    }

    return res;
}

/**
 * \brief CFB 模式下的 DES 解密
 * \param segment_size 每次加密的长度, 单位是位
 */
string DES::CFB_decryption(const string& cipher, const string& IV, const int& segment_size)
{
    if (segment_size % 8 != 0)
        throw "Segment size must be a multiple of 8!";

    if (cipher.length() % BLOCK_SIZE != 0)
        throw "Cipher text's length must be a multiple of 8";

    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    const int real_size = segment_size / 8;
    string shift_register = IV; // 模拟移位寄存器
    string res;

    for (auto begin = 0; begin < cipher.length(); begin += real_size)
    {
        // 对移位器中数据进行加密
        bitset<64> subText = char_to_bitset(shift_register.c_str());
        subText = _encrypt(subText);

        // 注意这里不是直接保存在移位寄存器中的
        string encrypted_buffer = bitset64_to_string(subText);

        string tmp;

        // 从Encrypted Buffer左侧取出Real Size个字节,与长度为Real Size的Plain Block进行异或操作
        for (auto i = 0; i < real_size; i++)
            tmp += cipher[begin + i] ^ encrypted_buffer[i];

        res += tmp;

        // 将移位器中的数据左移Real Size个字节
        shift_register = string(shift_register, real_size);

        // 将密文从右侧移入寄存器
        shift_register += string(cipher, begin, real_size);
    }

    return res;
}

/**
 * \brief OFB 模式下的 DES 加密
 */
string DES::OFB_encryption(const string& plain, const string& IV)
{
    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    string res;

    // 流为空代表之前没有使用过 OFB 加密模式, 需设置 IV
    if (OFB_stream.empty())
        OFB_stream = ECB_encryption(IV);

    // 如果明文长度小于等于流的长度则直接加密
    if (plain.length() <= OFB_stream.length())
    {
        for (auto i = 0; i < plain.length(); i++)
            res += plain[i] ^ OFB_stream[i];

        return res;
    }

    // 如果明文长度大于流的长度则继续生成流
    for (auto i = OFB_stream.length(); i < plain.length(); i += BLOCK_SIZE)
        OFB_stream += ECB_encryption(string(OFB_stream, i - BLOCK_SIZE, BLOCK_SIZE));

    for (auto i = 0; i < plain.length(); i++)
        res += plain[i] ^ OFB_stream[i];

    return res;
}

/**
 * \brief OFB 模式下的 DES 解密。
 * 由于明文和密文只在最终的异或过程中使用, 故加密与解密是对称的
 */
string DES::OFB_decryption(const string& cipher, const string& IV)
{
    if (IV.length() != BLOCK_SIZE)
        throw "Initialization vector's length must be 64 bits!";

    string res;

    // 流为空代表之前没有使用过 OFB 加密模式, 需设置 IV
    if (OFB_stream.empty())
        OFB_stream = ECB_encryption(IV);

    // 如果密文长度小于等于流的长度则直接解密
    if (cipher.length() <= OFB_stream.length())
    {
        for (auto i = 0; i < cipher.length(); i++)
            res += cipher[i] ^ OFB_stream[i];

        return res;
    }

    // 如果密文长度大于流的长度则继续生成流
    for (auto i = OFB_stream.length() - BLOCK_SIZE; i < cipher.length(); i += BLOCK_SIZE)
        OFB_stream += ECB_encryption(string(OFB_stream, i, BLOCK_SIZE));

    for (auto i = 0; i < cipher.length(); i++)
        res += cipher[i] ^ OFB_stream[i];

    return res;
}

/**
 * \brief CTR 模式下的 DES 加密
 */
string DES::CTR_encryption(const string& plain, const string& nonce)
{
    if (nonce.length() >= BLOCK_SIZE)
        throw "Nonce's length must be less than 128 bits!";

    string res;

    // 如果明文长度小于等于流的长度则直接加密
    if (plain.length() <= OFB_stream.length())
    {
        for (auto i = 0; i < plain.length() / BLOCK_SIZE; i++)
        {
            for (auto j = 0; j < BLOCK_SIZE; j++)
                res += plain[i * BLOCK_SIZE + j] ^ CTR_stream[i][j];
        }

        return res;
    }

    // 如果明文长度大于流的长度则继续生成流
    for (auto i = CTR_stream.size(); i < plain.length() / BLOCK_SIZE; i++)
    {
        // 计数器的值即为 vector 下标的 hex 值
        string counter = string2hex(std::to_string(CTR_stream.size()));
        int counter_len = BLOCK_SIZE - nonce.length();

        string input; // 需被加密的输入

        if (counter.length() > counter_len)
            input = nonce + string(counter, counter.length() - counter_len);
        else
        {
            input = nonce;

            // 补零
            for (auto i = 0; i < counter_len - counter.length(); i++)
                input += static_cast<char>(0);

            input += counter;
        }

        CTR_stream.push_back(ECB_encryption(input));
    }

    for (auto i = 0; i < plain.length() / BLOCK_SIZE; i++)
    {
        for (auto j = 0; j < BLOCK_SIZE; j++)
            res += plain[i * BLOCK_SIZE + j] ^ CTR_stream[i][j];
    }

    return res;
}

/**
 * \brief CTR 模式下的 DES 解密
 */
string DES::CTR_decryption(const string& cipher, const string& nonce)
{
    if (nonce.length() >= BLOCK_SIZE)
        throw "Nonce's length must be less than 128 bits!";

    string res;

    // 如果明文长度小于等于流的长度则直接加密
    if (cipher.length() <= OFB_stream.length())
    {
        for (auto i = 0; i < cipher.length() / BLOCK_SIZE; i++)
        {
            for (auto j = 0; j < BLOCK_SIZE; j++)
                res += cipher[i * BLOCK_SIZE + j] ^ CTR_stream[i][j];
        }

        return res;
    }

    // 如果明文长度大于流的长度则继续生成流
    for (auto i = CTR_stream.size(); i < cipher.length() / BLOCK_SIZE; i++)
    {
        // 计数器的值即为 vector 下标的 hex 值
        string counter = string2hex(std::to_string(CTR_stream.size()));
        int counter_len = BLOCK_SIZE - nonce.length();

        string input; // 需被加密的输入

        if (counter.length() > counter_len)
            input = nonce + string(counter, counter.length() - counter_len);
        else
        {
            input = nonce;

            // 补零
            for (auto i = 0; i < counter_len - counter.length(); i++)
                input += static_cast<char>(0);

            input += counter;
        }

        CTR_stream.push_back(ECB_encryption(input));
    }

    for (auto i = 0; i < cipher.length() / BLOCK_SIZE; i++)
    {
        for (auto j = 0; j < BLOCK_SIZE; j++)
            res += cipher[i * BLOCK_SIZE + j] ^ CTR_stream[i][j];
    }

    return res;
}

/**
 * \brief 将 8 位字符数据转换为 bitset 类型
 */
bitset<64> DES::char_to_bitset(const char s[8])
{
    bitset<64> res;

    for (auto i = 0; i < 8; i++)
    {
        for (auto j = 0; j < 8; j++)
            res[8 * i + 7 - j] = (s[i] >> j) & 1;
    }

    return res;
}

/**
 * \brief 将 bitset<64> 类型的数据转换为 string
 */
string DES::bitset64_to_string(bitset<64> bit)
{
    string res;

    for (auto i = 0; i < 64; i+=8)
    {
        char tmp = 0;
        
        for (auto j = 0; j < 8; j++)
        {
            tmp = tmp << 1;
            tmp += bit[i + j];
        }

        res += tmp;
    }

    return res;
}

/**
 * \brief 将 bitset<64> 类型的数据转换为 HEX
 */
string DES::bitset64_to_hex(bitset<64> bit)
{
    string res;

    for (auto i = 0; i < 64; i+=4)
    {
        int tmp = 0;
        
        for (auto j = 0; j < 4; j++)
        {
            tmp = tmp << 1;
            tmp += bit[i + j];
        }

        if (tmp < 10)
            res += '0' + tmp;
        else
            res += 'a' + tmp - 10;
    }

    return res;
}

/**
 * \brief 默认构造函数
 */
DES::DES()
= default;

/**
 * \brief 构造时设置 key
 * \param _key 传入的密钥
 */
DES::DES(const string& _key)
{
    set_key(_key);
}

DES::~DES()
= default;

/**
 * \brief 修改 key 的值
 */
bool DES::set_key(const string& _key)
{
    if (_key.length() != BLOCK_SIZE)
        throw "Key length must be 128 bits!";

    // 密钥更换后需要重置两个流的状态
    OFB_stream.clear();
    CTR_stream.clear();

    generate_key();

    return true;
}

string DES::encrypt(const string& plain, const int mode, const string& IV, const int& segment_size)
{
    switch (mode)
    {
    case ECB:
    {
        try
        {
            return ECB_encryption(plain);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CBC:
    {
        try
        {
            return CBC_encryption(plain, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CFB:
    {
        try
        {
            return CFB_encryption(plain, IV, segment_size);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case OFB:
    {
        try
        {
            return OFB_encryption(plain, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CTR:
    {
        try
        {
            return CTR_encryption(plain, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    default:
    {
        cout << "Unsupported mode!" << endl;
        return string();
    }
    }
}

string DES::decrypt(const string& cipher, const int mode, const string& IV, const int& segment_size)
{
    switch (mode)
    {
    case ECB:
    {
        try
        {
            return ECB_decryption(cipher);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CBC:
    {
        try
        {
            return CBC_decryption(cipher, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CFB:
    {
        try
        {
            return CFB_decryption(cipher, IV, segment_size);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case OFB:
    {
        try
        {
            return OFB_decryption(cipher, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    case CTR:
    {
        try
        {
            return CTR_decryption(cipher, IV);
        }
        catch (const char* msg)
        {
            cout << msg << endl;
            return string();
        }
    }
    default:
    {
        cout << "Unsupported mode!" << endl;
        return string();
    }
    }
}

测试用的 main.cpp

#include <iostream>
#include <fstream>
#include <chrono>
#include "DES.h"
#include "3DES.h"
#include "base64.h"
using namespace std;

DES des;
TDES tdes;
int mode = -1;
string k;
string IV;
string plain_path;
string cipher_path;

const string method[2] = { "DES", "3DES" };
const string MODE[5] = { "ECB", "CBC", "CFB", "OFB", "CTR" };

int main()
{
    ifstream file;
    ofstream out;
    char cmd;
    int status = 0; // 是否使用 3DES

CHANGE_METHOD:
    while (status == 0)
    {
        system("cls");
        cout << "DES or 3DES ?" << endl;
        cout << "1. DES\n2. 3DES" << endl;
        cin >> status;

        if (status != 1 && status != 2)
        {
            cout << "Method not allow!" << endl;
            status = 0;
            system("pause");
        }
        
        system("cls");
    }

CHANGE_MODE:
    while (mode < 0 || mode >= 5)
    {
        cout << "请选择加密模式" << endl;
        cout << "1. ECB\n2. CBC\n3. CFB\n4. OFB\n5. CTR" << endl;
        cin >> mode;
        mode--;

        if (mode < 0 || mode >= 5)
        {
            cout << "不支持的加密模式! 请重试" << endl;
            system("pause");
        }

        system("cls");
    }
    
CHANGE_KEY:
    while (k.empty())
    {
        if (status == 1)
        {
            cout << "请输入你的密钥(8个字符)" << endl;
            cin >> k;

            if (k.length() < 8)
            {
                cout << "密钥不得小于 8 个字符! " << endl;
                system("pause");
                k.clear();
            }

            des.set_key(k);
        }
        else if (status == 2)
        {
            cout << "请输入你的密钥(24个字符)" << endl;
            cin >> k;
            
            if (!tdes.set_key(k))
            {
                cout << "密钥必须是 24 个字符! " << endl;
                system("pause");
                k.clear();
            }
        }
        else
        {
            cout << "不要乱按哦~" << endl;
            system("pause");
            k.clear();
        }

        system("cls");
    }
    
CHANGE_IV:
    while (IV.empty())
    {
        if (mode == ECB)
            break;

        cout << "请输入初始向量 IV: " << endl;
        cin >> IV;

        if (IV.length() != BLOCK_SIZE && mode != CTR)
        {
            cout << "Initialization vector must be 128 bits!" << endl;
            IV.clear();
            system("pause");
        }

        if (mode == CTR && IV.length() >= BLOCK_SIZE)
        {
            cout << "Nonce must be less than 16 bytes!" << endl;
            IV.clear();
            system("pause");
        }

        system("cls");
    }

    system("cls");

    while (true)
    {
        cout << "当前加密方法为: " << method[status - 1] << endl;
        cout << "当前加密模式为: " << MODE[mode] << endl;
        cout << "你的密钥为: " << k << endl;
        cout << "当前 IV 为: " << IV << endl;
        cout << "1. 加密\n2. 解密\n3. 修改密钥\n4. 修改加密方式\n5. 修改加密模式\n6. 修改 IV\n7. 退出" << endl;
        cin >> cmd;

        switch (cmd)
        {
        case '1':
        {
            cout << "输入明文所在文件的绝对路径" << endl;
            cin.get();
            getline(cin, plain_path);

            file.open(plain_path, ios::binary);

            if (!file.is_open())
            {
                cout << "打开明文文件时发生错误!" << endl;
                break;
            }

            cout << "输入密文输出文件的绝对路径" << endl;
            getline(cin, cipher_path);

            out.open(cipher_path, ios::binary);

            if (!out.is_open())
            {
                cout << "打开输出文件时发生错误!" << endl;
                break;
            }

            // 一次读入整个文件,之后再分组加密
            string plain((istreambuf_iterator<char>(file)), istreambuf_iterator<char>());

            auto begin = chrono::system_clock::now();

            string cipher;
                
            if (status == 1)
                cipher = des.encrypt(plain, mode, IV);
            else if (status == 2)
                cipher = tdes.encrypt(plain, mode, IV);
                
            auto end = chrono::system_clock::now();
            auto duration = chrono::duration_cast<chrono::microseconds>(end - begin);

            out.write(cipher.c_str(), cipher.length());

            file.clear();
            file.close();
            out.clear();
            out.close();

            cout << "加密成功!" << endl;
            cout << "一共花费了" << double(duration.count()) * \
                chrono::microseconds::period::num / chrono::microseconds::period::den \
                << "秒" << endl;
                
            break;
        }
        case '2':
        {
            cout << "输入密文所在文件的绝对路径" << endl;
            cin.get();
            getline(cin, cipher_path);

            file.open(cipher_path, ios::binary);

            if (!file.is_open())
            {
                cout << "打开明文文件时发生错误!" << endl;
                break;
            }

            cout << "输入明文输出文件的绝对路径" << endl;
            getline(cin, plain_path);

            out.open(plain_path, ios::binary);

            if (!out.is_open())
            {
                cout << "打开输出文件时发生错误!" << endl;
                break;
            }

            string cipher((istreambuf_iterator<char>(file)), istreambuf_iterator<char>());

            auto begin = chrono::system_clock::now();
                
            string plain;

            if (status == 1)
                cipher = des.decrypt(cipher, mode, IV);
            else if (status == 2)
                cipher = tdes.decrypt(cipher, mode, IV);
                
            auto end = chrono::system_clock::now();
            auto duration = chrono::duration_cast<chrono::microseconds>(end - begin);

            out.write(plain.c_str(), plain.length());

            file.clear();
            file.close();
            out.clear();
            out.close();

            cout << "解密成功!" << endl;
            cout << "一共花费了" << double(duration.count())* \
                chrono::microseconds::period::num / chrono::microseconds::period::den \
                << "秒" << endl;

            break;
        }
        case '3': system("cls"); k.clear(); goto CHANGE_KEY;
        case '4': system("cls"); status = 0; goto CHANGE_METHOD;
        case '5': system("cls"); mode = -1; goto CHANGE_MODE;
        case '6': system("cls"); IV.clear(); goto CHANGE_IV;
        case '7': return 0;
        default: cout << "不要瞎按!" << endl; break;
        }

        system("pause");
        system("cls");
    }

    return 0;
}

里面的 base64 是我网上找的 base64 编码,这里就不放出来了吧。

Referrer

https://www.cnblogs.com/songwenlong/p/5944139.html

Last modification:November 18th, 2019 at 12:55 pm
If you think my article is useful to you, please feel free to appreciate