这学期选了一门课: 计算机安全导论。据说主讲密码学,所以就先学习一下相关的知识啦~ 这几天学的是 DES算法。
初学只写了 ECB 模式下的 DES。其他的等以后再说吧 XD
1. 算法原理
1.1 什么是 DES ?
数据加密标准(英语:Data Encryption Standard,缩写为 DES)是一种对称密钥加密块密码算法,1976年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),随后在国际上广泛流传开来。它基于使用56位密钥的对称算法。这个算法因为包含一些机密设计元素,相对短的密钥长度以及怀疑内含美国国家安全局(NSA)的后门而在开始时有争议,DES因此受到了强烈的学院派式的审查,并以此推动了现代的块密码及其密码分析的发展。——Wikipedia
1.2 算法流程
DES 是一个分组加密算法,典型的 DES 以64位为分组对数据加密,加密和解密用的是同一个算法。其大致加密流程如下图:
下面我来慢慢分析这些都是什么意思吧~
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位。
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位子密钥,确定这些子密钥的方式如下:
- 将56位的密钥分成两部分,每部分28位。
- 根据轮数,这两部分分别循环左移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 };
经过以上步骤即可得到密文。
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;
}