前言

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


代码仓库


代码特点

纯C++语言:

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

详细注释:

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

代码

des.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//预处理指令——————————
//预定义宏
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
//预处理指令——————————
//标准库头文件
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//预处理指令——————————
//自定义头文件
#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

作者的话

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