古典密码

从远古到 1949 年香农发表《保密系统的通信理论》,这期间人类所使用的密码均称为古典密码,本文主要介绍三种古典密码,分别为置换密码,代换密码和轮换密码。

为了写 c++代码图个方便,下面的代码(大部分)都没有强调大小写 因为懒,但是不影响密码的基本思路因为明文密钥之类的都是 String 类型,直接强行转大写或者小写就行

一、置换密码(列置换)

置换密码(Permutation Cipher)又叫换位密码(Transposi-tionCipher),它根据一定的规则重新排列明文,以便打破明文的结构特性。置换密码的特点是保持明文的所有字符不变,只是利用置换打乱了明文字符的位置和次序。
最常见的置换密码有两种:

  • 列置换密码(明文 P 遵照密钥的规程按列换位并且按列读出序列得到密文 C);
  • 周期置换密码(将明文 P 按固定长度 m 分组,然后对每组按 1,2…,m 的某个置换重排位置从而得到密文 C)。

定义

  • 有限集X上的运算σXX被称为一个置换,则σ是一个双射函数,即σ即时单射又是满射,并且σ的定义域和值域相同
  • 即任意 x∈X,存在唯一的 x’∈X,使得 σ(x)=x’
  • 解密的时候会用到逆置换$σ^{-1}$
  • 即任意 x’∈X,存在唯一的 x∈X,使得$σ^{-1}(x’)=x$
  • 从置换 σ 和逆置换$σ^{-1}$的定义可以看出得,若$σ^{-1}(x’)=x$,当且仅当$σ(x)=x’$,并且满足$σσ^{-1}=I=(1)$

有点晕,那么就直接看例子
4.jpg
最后给出公式若置换为 其实找规律也看得出

$$
σ=(x_{11}x_{12}x_{13}···x_{1(l-1)}x_{1l})···(x_{m1}x_{m2}x_{m3}···x_{m(n-1)}x_{mn})
$$

相应的逆置换位

$$
σ^{-1}=(x_{11}x_{1l}x_{1(l-1)}···x_{13}x_{12})···(x_{m1}x_{mn}x_{m(n-1)}···x_{m3}x_{m2})
$$

1.1 列置换

介绍(列置换)

这里只介绍列置换 还有周期置换,但是都大差不差
列置换密码,顾名思义,按列换位并且按列读出明文序列得到密文,具体加密步骤如下

  1. 将明文p以固定分组长度 m 按行写出nxm阶矩阵(若不m倍数,空余部分空格·补充)
  2. (1,2,3…m)的置换σ交换列的位置,σ为密钥
  3. 把新得到的矩阵按列的顺序依次读出得到密文c
    解密过程如下
  4. 将密文c以固定的长度n按列写成nxm阶矩阵
  5. 按逆矩阵 $ σ^{-1} $ 交换列的位置
  6. 把矩阵按着行依次读出为明文

大概意思看下图例子
这里解释一下加密:
(143)(56)的意思是第1列和第4列交换,交换后的第4列和第3列换(即原本的第1列和第3列交换),第5列和第6列交换,即可
1.jpg
2.jpg

C++源码

纯模拟暴力写的,有时间再优化

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
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 110; // 暂定最大矩阵110

string clear_matrix[N][N]; // 明文的矩阵
string secret_matrix[N][N]; // 密文后的矩阵
int res; // 判断是用与加密还是解密
int n, m; // 矩阵的n和m
string cleartext; // 明文
string secret_text; // 密文
string secret_key; // 密钥

void swap_matrix(int a, int b) // 交换第a列和第b列
{
if(res == 1)
{
for(int i = 1; i <= n; i ++ )
{
swap(clear_matrix[i][a], clear_matrix[i][b]);
}
}
else
{
for(int i = 1; i <= n; i ++ )
{
swap(secret_matrix[i][a], secret_matrix[i][b]);
}
}

}

// 加密
void encrypt(string a, string b, int n, int m)
{
for(int i = 1, t = 0; i <= n; i ++)
{
for(int j = 1; j <= m; j ++ )
{
while(a[t] == ' ')
{
t ++ ;
}
clear_matrix[i][j] = a[t ++ ];
}
}
int l = 0, r = 0;
while(b[l] == '(' && b[r] != ')')
{
r ++ ;
if(b[r] == ')')
{
for(int i = l + 1; i < r - 1; i ++ )
{
swap_matrix(b[i] - '0', b[i + 1] - '0');
}
r ++ ;
l = r;
}
}
cout << "以下是加密后的结果(>人<;)" <<endl;
for(int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= m; j ++ )
{
cout << clear_matrix[i][j];
}
}
}

//解密
void decode(string a, string b, int n, int m)
{
for(int i = 1, t = 0; i <= n; i ++ )
{
for(int j =1; j <= m; j ++ )
{
secret_matrix[i][j] = a[t ++ ];
}
}
int l = b.size(), r = b.size();
while(b[r - 1] == ')' && b[l - 1] != '(')
{
l -- ;

if(b[l - 1] == '(')
{
for(int i = r - 2; i > l; i -- )
{
swap_matrix(b[i] - '0', b[i - 1] - '0');
}
r = l - 1;
l = r;
}
}
cout << "以下是解密后的结果 (^^ゞ" << endl;
for(int i = 1; i <= n; i ++ )
{
for(int j = 1; j <= m; j ++ )
{
cout << secret_matrix[i][j];
}
}
}

int main()
{
cout << "如果需要加密选择1, 解密选择2 -O-" << endl;
cin >> res;
if(res == 1)
{
cout << "请依次输入明文,密钥,以及矩阵大小n,m \(@^0^@)/" << endl;

getline(cin, cleartext);
getline(cin, cleartext);
cin >> secret_key;
cin >> n >> m;
encrypt(cleartext, secret_key, n ,m);

}
else
{
cout << "请依次输入密文,密钥,以及矩阵大小n,m \(@^0^@)/" << endl;

getline(cin, secret_text);
getline(cin, secret_text);
cin >> secret_key >> n >> m;

decode(secret_text, secret_key, n ,m);
}

}

运行结果截图
1.png
2.png

1.2 周期置换

介绍

周期置换密码是将明文 p 串按固定长度 m 分组,然后对每组中的字串按 1,2,3…,m 的某个置换重新排列位置从而得到密文,其中密钥σ包含分组长度信息。解密同样对密文c按长度m分组,并按σ的在逆置换$ σ^{-1} $把每组子串重新排列位置从而得到明文p
3.jpg

C++源码

代码和列置换的一样就行 就是速度不行 😪
注意一下输入就行,这里直接贴结果图
3.png > 4.png

二、代换密码

1. 单表代换密码

单表代换密码指明文消息中相同的字母,在加密时都使用同一固定的字母来代换。单表代换密码又分为移位密码、基于密钥的单表代换密码和仿射密码 3 类,由于移位密码可以看作仿射密码的特例,所以下面只介绍基于密钥的单表密码和仿射密码。

1.1 基于密钥的单表代换密码

介绍

1.基于密钥的单表代换密码
基于密钥的单表代换密码很多,其基本思想是类似的,下面通过一个具体实例来介绍单表代换密码。首先选取一个英文单词或字母串作为密钥,去掉其中重复的字母得到一个无重复字母的字母序列,然后将字母表中的其他字母按字母顺序依次写在此字母序列后面,如果密钥中的字母序列有重复则后出现的字母不再出现,从而使所有的字母建立一一对应关系,也就是字母代换表。这种单表代换密码破译的难度稍高,而且密钥更改便捷,因此增加了单表代换密码体制的灵活性。
5.jpg

C++源码

大小写和上面的图片比太一样,但应该不是什么大问题 才不是懒

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
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

string cleartext; // 明文
string secret_text; // 密文
string secret_key; // 密钥
string clear_key; // 明钥(用于解密)
int res; // 判断加密还是解密
char encrypt_list[26];
char encode_list[26];

void get_encrypt_list(string b)
{
int len = b.size();
for(int i = 0; i < 26; i ++ )
{
encrypt_list[i] = i + 'a';
}
for(int i = 0; i < len; i ++ )
{
encrypt_list[b[i] - 'a'] = 'a';
}
sort(encrypt_list, encrypt_list + 26);
for(int i = 0; i < len; i ++ )
{
encrypt_list[i] = b[i];
}
}

void get_encode_list(string b)
{
for(int i = 0; i < 26; i ++ )
{
encode_list[encrypt_list[i] - 'a'] = 'a' + i;
}

}

void encrypt(string a, string b)
{
int len = a.size();
cout << "加密后的密文是:" << endl;
for(int i = 0; i < len; i ++ )
{
cout << encrypt_list[a[i] - 'a'];
}
cout << endl;
}

void encode(string a, string b)
{
get_encode_list(b); // 获取解密代换表
int len = a.size();
cout << "解密后的明文是:" << endl;
for(int i = 0; i <= len; i ++ )
{
cout << encode_list[a[i] - 'a'];
}
cout << endl;
}

int main()
{
cout << "请输入密钥" << endl;
cin >> secret_key;
get_encrypt_list(secret_key); // 获取加密代换表
cout << "请选择解密还是加密,加密选1,解密选2" << endl;
cin >> res;
if(res == 1)
{
cout << "请输入明文" << endl;
cin >> cleartext;
encrypt(cleartext, secret_key);
}
else if(res == 2)
{
cout << "请输入密文" << endl;
cin >> secret_text;
encode(secret_text, secret_key);
}

return 0;
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 加密
请输入密钥
iscbupt
请选择解密还是加密,加密选1,解密选2
1
请输入明文
cybergreatwall
加密后的密文是:
cysuntnuiqwigg

// 解密
请输入密钥
iscbupt
请选择解密还是加密,加密选1,解密选2
2
请输入密文
cysuntnuiqwigg
解密后的明文是:
cybergreatwall

1.2 仿射密码

介绍

仿射密码的加密算法就是一个线性变换,即对任意的明文字符x,对应的密文字符为 $y≡e(x)≡ax+b(mod 26)$,其中$a,b∈ Z_{26 }$,且要求 $gcd(a,26)=1$,函数 e(x)称为仿射加密函数。

  1. 仿射加密函数要求 $gcd(a,26)=1$,即要求 a 26 互素,否则$ e(x)≡ax+b(mod 26)$就不是一个单射函数。例如,当 $gcd(8,26)=2$ 时,因为对 x∈$Z_{26 }$,$8(x+13)+5=8x+109≡8x+5 (mod 26)$,所以和xx+13 将被加密为相同的密文。故$ e(x)≡ 8x 十 5(mod 26)$不是一个有效的仿射加密函数。而当 $gcd(a,26)=1$ 时,仿射加密函数的解必然唯一。

    证明如下:

    设存在 $x_1,x_2∈Z_{26}$,使得 $e(x)≡ ax_1 +b≡ax_2+b(mod 26)$,则必然有 $ax_1 ≡ax_2(mod 26)$,从而可以得到整除式 $26|a(x_1-x_2)$;又因为 $gcd(a,26)=1$,所以得 $26(x_1-x_2)$,由题设知 $x_1,z_2 ∈ Z*{26} $,所以必然得结论 $x_1 = x_2$。综上可知,前提 gcd(a,26)=1 保证了仿射加密函数是一个双射函数。

  2. a=1,b=3 时,这种仿射密码就是著名的恺撒密码。

由仿射加密函数可以看出仿射加密的密钥空间大小为 12×26=312。因为由 $b∈ Z_{26} $ 知,b 有 0,1…25 共 26 种不同取值;而 $a∈ Z_{26} $ 且 $gcd(a,26)=1$,由欧拉函数易知 a 的取值个数是 $φ (26)=φ (2×13)=φ (2)×φ (13)=1×12=12$,即 1,3,5,7,9,11,15,17,19,21,23,25。
由仿射加密函数 $e(x)≡ ax+b(mod 26)$可得 $ax ≡ e(x)-b(mod 26)$,因为 $gcd(a,26)=1$,可知 a 在$Z_{26}$ 上一定存在乘法逆元 $a^{-1}∈ Z_{26} $,使得 $aa^{-1}≡1(mod 26)$。在$ ax ≡ e(x)-b(mod 26)$两边同时左乘$a^{-1}$得$a^{-1}ax≡(a^{-1}-a)x≡x≡a^{-1}(e(x)-b)(mod 26)$,由此可得仿射加密的逆变换,即仿射解密函数为$x≡d(e(x))≡a^{-1}(e(x)-b)(mod 26)$。
5.png

6.jpg

C++源码
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
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e6+10;

// (1-26) 内与26互素元素的乘法逆元
int multiplicative_inverse[26] =
{0, 1, 0, 9, 0,
21, 0, 15, 0, 3,
0, 19, 0, 0, 0,
7, 0, 23, 0, 11,
0, 5, 0, 17, 0, 25};

string cleartext; // 明文
string secret_text; // 密文
string secret_key; // 密钥
string clear_key; // 明钥(用于解密)
int res; // 判断加密还是解密
int a, b; //仿射加密函数的a,b
int tmp[N]; //用于存放明文字母转换后的数字


void encrypt(string m)
{
int len = m.size();
for(int i = 0; i < len; i ++ )
{
tmp[i] = (a * (m[i] - 'a') + b) % 26;
cout << (char)(tmp[i] + 'A');
}
}

void encode(string m)
{
int ma = multiplicative_inverse[a];// a乘法逆元的ma
// 求mb的时候因为要-b,c++在mod的时候需要注意比如说(-6mod26)=-6
//所以需要在加一次26再mod
int mb = (((-b) * ma) % 26 + 26) % 26;
int len = m.size();
for(int i = 0; i < len; i ++ )
{
tmp[i] = (ma * (m[i] - 'A') + mb) % 26;
cout << (char)(tmp[i] + 'a');
}
}

int main()
{
cout << "请输入仿射加密函数的a,b" << endl;
cin >> a >> b;
cout << "请选择解密还是加密,加密选1,解密选2" << endl;
cin >> res;
if(res == 1)
{
cout << "请输入明文" << endl;
cin >> cleartext;
encrypt(cleartext);
}
else if(res == 2)
{
cout << "请输入密文" << endl;
cin >> secret_text;
encode(secret_text);
}

return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//加密
请输入仿射加密函数的a,b
11 6
请选择解密还是加密,加密选1,解密选2
1
请输入明文
sorcery
获取的密文
WELCYLK


// 解密
请输入仿射加密函数的a,b
11 6
请选择解密还是加密,加密选1,解密选2
2
请输入密文
WELCYLK
获取的明文
sorcery

2. 多表代换密码

多表代换密码是以一系列代换表依次对明文消息的字母序列进行代换的加密方法,即明文消息中出现的同一个字母,在加密时不是完全被同一固定的字母代换,而是根据其出现的位置次序用不同的字母代换。如果代换表序列是非周期的无限序列,则相应的密码称为非周期多表代换密码,这类密码对每个明文都采用了不同的代换表进行加密,故称为一次一密密码.它是理论上不可破译的密码体制。但实际应用中经常采用的是周期多表代换密码,它通常使用有限个代换表,代换表被重复使用以完成消息的加密,它是一种比单表密码体制更为安全的密码体制。
不妨设明文序列为$m=m_1m_2$···,代换表序列为 $\pi=\pi_1,\pi_2,···,\pi_d,\pi_1,\pi_2,···,\pi_d,··· $则使用周期为d的代换序列(加密密)加密明文序列m得密文序列为:

$$c=\pi(m)=\pi_1(m_1),\pi_2(m_2),···,\pi_d(m_d),\pi_1(m_{d+1}),\pi_2(m_{d+2}),···,\pi_d(m_{2d}),···$$

显然,当d=1时,多表代换密码退化为单表代换密码。
多表代换密码利用从明文字符到密文字符的多个映射隐藏单字母出现的统计特性(频率特性)。它将明文字符划分为长度相同的明文组,然后再对明文组进行替换。这样同一字母在明文序列中的位置不同就具有不同的密文,从而能更好地抵抗统计密码分析。
多表代换密码体制有很多,常见且比较典型的有 3 种:Playfair 密码Vigenere 密码和Hill 密码。

2.1 Playfair 密码

介绍

Playfair 密码(Playfair Cipher)是 1854 年由 Charles Wheatstone 提出的,此后由他的朋友 Lyon Playfair 将该密码公布,所以就称为 Playfair 密码
Playfair 密码将明文字母按两个字母一组分成若干个单元,然后将这些单元替换为密文字母组合,替换时基于一个5X5字母矩阵,该矩阵使用一个选定的关键词来构造,其构造方法如下:从左到右,从上到下依次填入关键词的字母,若关键词中有重复字母,则第二次出现时略过,然后将字母表中剩下的字母按字母顺序依次填入矩阵中,其中字母ij看作是同一个字符(PS:25 的矩阵只能存储 25 个字母)。同时约定如下规则:表中的第一列看作是第五列的右边一列,第一行看作是第五行的下一行。

对每一对明文字母$p_1,p_2$:、加密时根据它们在 5X5 字母矩阵中的位置分别处理如下。

(1)加密方法

  1. 将明文两两分组

    • 明文个数为偶数,以下例子可以得到:
      PLAYFAIRCIPHER=PL——AY——FA——IR——CI——PH——ER

    • 若明文字母数为奇数,则在明文的末端添加一个事先约定好的字母(X)进行填充。示例:AB——C,补完变为 AB——CX

    • 若$p_1,p_2$: 相同,则插人一个事先约定好的字母,并用上述方法处理 AA——BC 插完变为 AX——AX——CD(这里执行了一次缺补)

      同插缺补操作不分先后,执行结果相同

  2. 移位和替换

    • 若$p_1,p_2$:在同一行,则对应的密文分别是紧靠$p_1,p_2$右端的字母
    • 若$p_1,p_2$:在同一列,则对应的密文分别是紧靠 $p_1,p_2$下端的字母。
    • 若$p_1,p_2$: 不在同一行,也不在同一列,则对应的密文为以$p_1,p_2$: 为对角顶点确定的矩形的另外的两个顶点字母,按同行的原则对应。
  3. 案例

    假设密钥是:PLAYFAIR IS A DIGRAM CIPHER

    第一步:填入不重复的密钥(如果有重复则跳过) 6.png

    第二部:将剩未出现的字母依次写入
    7.png

    示例: P=PL——AY——FA——IR——CI——PH——ER

    前四个都符合规则的第一条:
    8.png
    得到前四组的密文 C:
    C=LA——YF——PY——RS
    再看剩余几组:满足变换规则第三条(本质:构建子矩阵并找到反对角)
    9.png
    PS:一定要注意字母对应的顺序:明文对应同行的才是密文

    所以得到:C=LA——YF——PY——RS——MR——AM——CD

(2) 解密方法

Playfair 密码在解密时,同样是将密文分为两个字母一组,然后根据密钥产生的字母矩阵进行解密。解密过程与加密过程基本相似,只是把其中的右边改为左边,把其中的下面改为上面即可

C++源码
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
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e6+10;

//字符串总是以'\0'字符结束,所以在声明字符数组时,需要考虑到这个额外的字符。
char letters[26] = "abcdefghiklmnopqrstuvwxyz"; // 用于填充矩阵
char text_matrix[5][5];
string other;

string cleartext; // 明文
string secret_text; // 密文
string secret_key; // 密钥
string clear_key; // 明钥(用于解密)
int res; // 判断加密还是解密
int cnt[N]; // 用于辅助去重

void init_matrix()
{
/*
* 处理好字母矩阵
*/
string tmp;
for(int i = 0; i < secret_key.size(); i ++ ) // 去重
{
if(secret_key[i] == 'j') secret_key = 'i'; // 因为数组不方便存i/j,因此统一用i表示
cnt[secret_key[i] - 'a'] ++ ;
if(cnt[secret_key[i] - 'a'] == 1)
{
if(secret_key[i] - 'a' < 9)
{
letters[secret_key[i] - 'a'] = '0';
}
else
{
letters[secret_key[i] - 'a' - 1] = '0';
}
tmp += secret_key[i];
}
}
// 排序,密钥中出现过的字母变成0,排序后会在前面,未出现的自动会排好序在后面
sort(letters, letters + 25);
int len = tmp.size();
for(int i = 0; i < len; i ++ )
{
letters[i] = tmp[i];
}
cout << letters << endl;
for(int i = 0, t = 0; i < 5; i ++ )
{
for(int j = 0; j < 5; j ++ )
{
text_matrix[i][j] = letters[t ++ ];
}
}
}

void init_text(string& m)
{
int len = m.size();
for(int i = 0; i < len; i ++ )
{
if (m[i] == m[i + 1])
{
m.insert(i + 1, other);
}
}
if(m.size() % 2 == 1)
{
m += (m[m.size() - 1] + 1);
}
}

void encrypt(string m)
{
for(int i = 0; i < m.size(); i += 2 )
{
int x1, x2, y1, y2; // 寻找一组明文再矩阵的位置
bool flag1 = false, flag2 = false;
for(x1 = 0; x1 < 5; x1 ++ ) // 找到后需要连续break两次才能跳出来
{
for(y1 = 0; y1 < 5; y1 ++ )
{
if(m[i] == text_matrix[x1][y1])
{
flag1 = true;
break;
}
}
if(flag1) break;
}
for(x2 = 0; x2 < 5; x2 ++ )
{
for(y2 = 0; y2 < 5; y2 ++ )
{
if(m[i + 1] == text_matrix[x2][y2])
{
flag2 = true;
break;
}
}
if(flag2) break;
}
if(x1 == x2)
{
// 防止越界,mod5
secret_text += text_matrix[x1][(y1 + 1) % 5];
secret_text += text_matrix[x2][(y2 + 1) % 5];
}
else if (y1 == y2)
{
secret_text += text_matrix[(x1 + 1) % 5][y1];
secret_text += text_matrix[(x2 + 1) % 5][y2];
}
else
{
secret_text += text_matrix[x1][y2];
secret_text += text_matrix[x2][y1];
}
}

cout << "加密后的密文为:" << secret_text << endl;
}

void encode(string m)
{
for(int i = 0; i < m.size(); i += 2 )
{
int x1, x2, y1, y2; // 寻找一组明文再矩阵的位置
bool flag1 = false, flag2 = false;
for(x1 = 0; x1 < 5; x1 ++ ) // 找到后需要连续break两次才能跳出来
{
for(y1 = 0; y1 < 5; y1 ++ )
{
if(m[i] == text_matrix[x1][y1])
{
flag1 = true;
break;
}
}
if(flag1) break;
}
for(x2 = 0; x2 < 5; x2 ++ )
{
for(y2 = 0; y2 < 5; y2 ++ )
{
if(m[i + 1] == text_matrix[x2][y2])
{
flag2 = true;
break;
}
}
if(flag2) break;
}
if(x1 == x2)
{
// 防止越界,mod5
cleartext += text_matrix[x1][(y1 - 1) % 5];
cleartext += text_matrix[x2][(y2 - 1) % 5];
}
else if (y1 == y2)
{
cleartext += text_matrix[(x1 - 1) % 5][y1];
cleartext += text_matrix[(x2 - 1) % 5][y2];
}
else
{
cleartext += text_matrix[x1][y2];
cleartext += text_matrix[x2][y1];
}
}

cout << "解密后的明文为:" << cleartext << endl;
}

int main()
{
cout << "请选择解密还是加密,加密选1,解密选2" << endl;
cin >> res;
cout << "请输入密钥和一个无关字符" << endl;
cin >> secret_key >> other;
init_matrix(); // 初始化源于密钥构建的字母矩阵
if(res == 1)
{
cout << "请输入明文" << endl;
cin >> cleartext;
init_text(cleartext);// 处理好的明文
encrypt(cleartext);
}
else if(res == 2)
{
cout << "请输入密文" << endl;
cin >> secret_text;
init_text(secret_text);// 处理好的明文
encode(secret_text);
}

return 0;
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 加密
请选择解密还是加密,加密选1,解密选2
1
请输入密钥和一个无关字符
iscbupt x
iscbuptadefghklmnoqrvwxyz
请输入明文
steganographia
加密后的密文为:tgtltonhoeafcp

// 解密
请选择解密还是加密,加密选1,解密选2
2
请输入密钥和一个无关字符
iscbupt x
iscbuptadefghklmnoqrvwxyz
请输入密文
tgtltonhoeafcp
解密后的明文为:steganographia

2.2Vigenere 密码

介绍

维吉尼亚密码(Vigenere Cipher)是由法国密码学家 Blaise de Vigenere 于 1858 年提出的种代换密码,它是多表代换密码的典型代表。
定义:设m为某一固定的正整数,PCK 分别为明文空间、密文空间和密钥空间,并且 $P=K=C=(Z_{26})^m$,对一个密钥 $k=(k_1,k_2…k_m)$,定义维吉尼亚密码的加密函数为:

$$
e_k(x_1,x_2…x_m)=(x_1+k_1,x_2 + k_2…x_m+k_m)
$$

与之对应的解密函数为:

$$
d_k(y_1,y_2…y_m)=(y_1-k_1,y_2-k_2…y_m-k_m)
$$

其中$k=(k_1,k_2…k_m)$是一个长为m 的密钥字,密空间的大小为 $26^m$,所以对一个相对小的m,穷举密钥也需要很长的时间。如m=7,则密空间大小超过$8×10^9$,所以手工搜索非常困难。当明文的长度超过m时,可将明文串按长度m分组,然后对每一组使用密钥k加密。

设密钥为iscbupt,则对应的数字化的密钥k=(8,18,2,1,20,15,19),待加密的明文是cyber greatwall corporation,首先把明文字母转换为数字,然后把明文字母每7个分为一组,使用密钥字进行模26下的加密操作,具体计算过程如下所示。
10.png
加密表中第一行为已分组明文字母,每组之间用空格隔开;第二行是与明文字母对应的数字;第三行是加密密钥;第四行为加密后的密文对应的数字,即第二行数字与第三行对应的数字模26和的结果;最后一行是得到的密文KQDFLVKMSVXUAEKGTQIGTBAQO。同样把密文按每组 7个进行分组,然后进行解密得如下解密表。
11.png
解密表与加密表的第四行计算操作是不同的,解密表中第四行是由第二行与第三行的模26 差得到的,其他行的操作与加密表基本相同。由解密表可以明显看出,明文字符通过解密函数得到恢复。
为了更快根据明文找出密文,或者依据密文推出明文,我们构造了下表,表中第一行为26 个明文字符,第一列代表26个密钥字符,根据下表进行的加密解密过程如下。
(1)加密过程:明文字母力对应的列和密钥字母k对应的行的交叉点就是加密后的密文字母c
(2)解密过程:在密钥字母 k 对应的行找到相应的密文字母 c,则c 所在列对应的明文字母即是p

设密钥为iscbupt,需加密的明文字母是sorcery,则根据维吉尼亚表和加密规则易得密文为AGTDYGR。同样根据上述维吉尼亚表和解密规则易知明文为sorcery根据维吉尼亚表的启发,还可以设计出其他多表密码体制,如把维吉尼亚表对应的行逆序排列,就可以得到博福特密码(Beaufort Cipher)表

7.jpg

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
//体贴的我打出来了表单(当然是计算机算的,下面代码有)
ABCDEFGHIJKLMNOPQRSTUVWXYZ
BCDEFGHIJKLMNOPQRSTUVWXYZA
CDEFGHIJKLMNOPQRSTUVWXYZAB
DEFGHIJKLMNOPQRSTUVWXYZABC
EFGHIJKLMNOPQRSTUVWXYZABCD
FGHIJKLMNOPQRSTUVWXYZABCDE
GHIJKLMNOPQRSTUVWXYZABCDEF
HIJKLMNOPQRSTUVWXYZABCDEFG
IJKLMNOPQRSTUVWXYZABCDEFGH
JKLMNOPQRSTUVWXYZABCDEFGHI
KLMNOPQRSTUVWXYZABCDEFGHIJ
LMNOPQRSTUVWXYZABCDEFGHIJK
MNOPQRSTUVWXYZABCDEFGHIJKL
NOPQRSTUVWXYZABCDEFGHIJKLM
OPQRSTUVWXYZABCDEFGHIJKLMN
PQRSTUVWXYZABCDEFGHIJKLMNO
QRSTUVWXYZABCDEFGHIJKLMNOP
RSTUVWXYZABCDEFGHIJKLMNOPQ
STUVWXYZABCDEFGHIJKLMNOPQR
TUVWXYZABCDEFGHIJKLMNOPQRS
UVWXYZABCDEFGHIJKLMNOPQRST
VWXYZABCDEFGHIJKLMNOPQRSTU
WXYZABCDEFGHIJKLMNOPQRSTUV
XYZABCDEFGHIJKLMNOPQRSTUVW
YZABCDEFGHIJKLMNOPQRSTUVWX
ZABCDEFGHIJKLMNOPQRSTUVWXY
C++源码

因为这个算法不难,所以就不模拟了,直接用表了(~ ̄ ▽  ̄)~

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
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e6+10;

char vigenere[26][26];

string cleartext; // 明文
string secret_text; // 密文
string secret_key; // 密钥
string clear_key; // 明钥(用于解密)
int res; // 判断加密还是解密

void init()
{
for(int i = 0, t = 0; i < 26; i ++ )
{
for(int j = 0; j < 26; j ++ )
{
int k = (j + t) % 26;
vigenere[i][j] = 'A' + k;
}
t ++ ;
}
}

void encrypt(string m)
{
for(int i = 0; i < m.size(); i ++ )
{
int k = i % secret_key.size();
secret_text += vigenere[m[i] - 'a'][secret_key[k] - 'a'];
}
cout << "加密后的密文为:" << secret_text << endl;
}

void encode(string m)
{
for(int i = 0; i < m.size(); i ++ )
{
int k = i % secret_key.size();
for(int j = 0; j < 26; j ++ )
{
if(vigenere[secret_key[k] - 'a'][j] == m[i]) cleartext += ('a' + j);
}
}
cout << "解密后的明文为:" << cleartext << endl;
}

int main()
{
cout << "请选择解密还是加密,加密选1,解密选2" << endl;
cin >> res;
cout << "请输入密钥符" << endl;
cin >> secret_key;
init();//初始化表单(外挂)
if(res == 1)
{
cout << "请输入明文" << endl;
cin >> cleartext;
encrypt(cleartext);
}
else if(res == 2)
{
cout << "请输入密文" << endl;
cin >> secret_text;
encode(secret_text);
}

return 0;
}

结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 加密
请选择解密还是加密,加密选1,解密选2
1
请输入密钥符
iscbupt
请输入明文
cybergreatwallcorporation
加密后的密文为:KQDFLVKMSVXUAEKGTQIGTBAQO

// 解密
请选择解密还是加密,加密选1,解密选2
2
请输入密钥符
iscbupt
请输入密文
KQDFLVKMSVXUAEKGTQIGTBAQO
解密后的明文为:cybergreatwallcorporation

2.3 Hill 密码

介绍

希尔密码(Hill Cipher)是由数学家$ Lester Hil$于 1929 在 $American MathematicalMonthly$ 杂志上首次提出的。Hill 密码的基本思想是利用 $Z_{26}$上的线性变换把n个连续的明文字母替换为n 个密文字母。这个替换是由密钥决定的,而这个密钥是一个变换矩阵,解密时只需对密文做一次逆变换即可。其实 Hill 密码实质上就是通过一个变换矩阵把明文变换为密文的一种密码体制。

定义:设n为某一固定的正整数,PCK 分别为明文空间、密文空间和密钥空间,并且$P=C=(Z_{26})$,密钥$k=(k_{ij}){n×n}$,是一个n×n 的非奇异矩阵(行列式 $det(k)\not=0$),且满足gcd(det(k),26)=1,即满足 $Z{26}$上 det(k)26 互素,从而保证了密钥矩阵的逆矩阵存在。对明文序列$p=(p_1,p_2…p_n)\in P$,其对应密文记为$c=(c_1,c_2…c_n)\in C$,则 Hill 密码的加密函数定义为:
12.png

写成矩阵简化形式为:
13.png

在 Hill 密码的加密函数等式的两端分别乘以 $k ^{-1}$,则得到其解密函数的解析式:
14.png

写成矩阵简化形式为:
15.png

设待加密的明文是cyber,数字化后为 2,24,1,4,17,使用的密钥为
16.png
则密文为WRTRV。同理由于k是非奇异的,所以在$ Z_{26} $上必然存在逆矩阵:
17.png
则明文为cyber
Hill 密码将长消息分组,分组的长度由矩阵的维数决定。它与 Playfair 密码相比,更好地隐藏了单字母的统计特性,所以 Hill 密码能较好地抵抗统计分析法,对抗惟密文攻击的强度较高,但易受到已知明文攻击。

C++源码

这个算法难就难在算逆元,因为我没学过,直接去网上找了一篇,有兴趣就看下吧 点击这里直达

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
#include<iostream>
#include<string>
#include<vector>
#include<math.h>
using namespace std;

const int N = 1e6+10;
#define mod(a) (((a % 26) + 26) % 26) //把负数的模计算成正数

string cleartext; // 明文
string secret_text; // 密文

vector<int> secret_key; // 密钥
vector<int> clear_key; // 明钥(密钥逆矩阵)

int res; // 判断加密还是解密

int divmod(int a, int b) { //模26除法
for(int i = 0; i < 10000; i++) {
if((a + 26 * i) % b == 0) {
return mod((a + 26 * i) / b);
}
}
return 0; //其实是10000以内没有找到🤣,还可以用直接乘逆元的方式求模的除法
}

int det(vector<int> arr, int len) { //行列式求值
int ans = 0;
if(len == 1) ans = arr[0];
else {
vector<int> yuzi((len - 1) * (len - 1)); //创建余子式矩阵
int move = 0;
for(int i = 0; i < len; i++) {
for(int j = 0; j < len - 1; j++) {
move = i > j ? 0 : 1;
for(int k = 0; k < len - 1; k ++) {
yuzi[j * (len - 1) + k] = arr[(j + move) * len + k + 1];
}
}
int flag = (i % 2 == 0 ? 1 : -1);
ans += flag * arr[i * len] * det(yuzi, len - 1);
}
}
return mod(ans);
}


vector<int> inverse(vector<int> arr) { //求mod26的逆矩阵
vector<int> inver;
int len = sqrt(arr.size());
if(det(arr, len) == 0) cout << "秘钥矩阵的行列式值为0,无法解密" << endl;
int _det = det(arr, len);
vector<int> bansui(len * len); //伴随矩阵
vector<int> yuzi((len - 1) * (len - 1)); //余子式
int pi, pj, q;
for(int ai = 0; ai < len; ai++) {
for(int aj = 0; aj < len; aj++) {
for(int bi = 0; bi < len - 1; bi++) {
for(int bj = 0; bj < len - 1; bj++) {
if(ai > bi) pi = 0;
else pi = 1;
if(aj > bj) pj = 0;
else pj = 1;
yuzi[bi * (len - 1) + bj] = arr[(bi + pi) * len + bj + pj];
}
}
if((ai + aj) % 2 == 0) q = 1;
else q = -1;
bansui[ai * len + aj] = q * det(yuzi, len - 1);
}
}
for(int i = 0; i < len; i++) {
for(int j = 0; j < len;j++) {
arr[i * len + j] = divmod(bansui[i * len + j], _det);
inver.push_back(mod(arr[i * len + j]));
}
}
for(int i = 0; i < len; i++) { //转置一下输出顺序
for(int j = 0; j < i; j++) {
int temp = inver[i * len + j];
inver[i * len + j] = inver[ j * len + i];
inver[j * len + i] = temp;
}
}
return inver;
}

void encrypt(string m)
{
int y = secret_key.size() / m.size();
int x = m.size();

for(int i = 0; i < y; i ++ )
{
int cnt = 0;
for(int j = 0; j < x; j ++ )
{
cnt += (m[j] - 'a') * secret_key[j * x + i];
}
cnt %= 26;
secret_text += (cnt + 'A');
}
cout << "加密后的密文:" << secret_text << endl;
}

void encode(string m)
{
clear_key = inverse(secret_key);
int y = secret_key.size() / m.size();
int x = m.size();

for(int i = 0; i < y; i ++ )
{
int cnt = 0;
for(int j = 0; j < x; j ++ )
{
cnt += (m[j] - 'A') * clear_key[j * x + i];
}
cnt %= 26;
cleartext += (cnt + 'a');
}
cout << "加密后的密文:" << cleartext << endl;
}

int main()
{
cout << "请选择解密还是加密,加密选1,解密选2" << endl;
cin >> res;
cout << "请输入密钥,全部输入到本行" << endl;
int n;
while(cin >> n)
{
secret_key.push_back(n);
if(cin.get() == '\n')
break;
}
if(res == 1)
{
cout << "请输入明文" << endl;
cin >> cleartext;
encrypt(cleartext);
}
else if(res == 2)
{
cout << "请输入密文" << endl;
cin >> secret_text;
encode(secret_text);
}

return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//加密
请选择解密还是加密,加密选1,解密选2
1
请输入密钥,全部输入到本行
10 5 12 0 0 3 14 21 0 0 8 9 11 0 0 0 0 0 11 8 0 0 0 3 7
请输入明文
cyber
加密后的密文:WRTRV

//解密
请选择解密还是加密,加密选1,解密选2
2
请输入密钥,全部输入到本行
10 5 12 0 0 3 14 21 0 0 8 9 11 0 0 0 0 0 11 8 0 0 0 3 7
请输入密文
WRTRV
加密后的密文:cyber

三、轮换密码

介绍

从 19 世纪 20 年代开始,人们逐渐发明各种机械加解密设备用来处理数据的加密解密运算,起初普遍使用的设备是转轮密码机。转轮密码机是由一个用于输入的键盘和一组转轮组成,每个转轮上有26个字母的任意组合,转轮之间由齿轮进行连接,当一个转轮转动时,可以将一个字母转化成为另一个字母。为了使转轮更安全,人们还把几种转轮和移动齿轮结合起来,所有转轮以不同的速度转动,并且通过调整转轮上字母的位置和速度为破译设置障碍。转轮密码机原理如图 3-1 所示,它是一个三转轮密码机模型,3 个带有数字的矩形框代表 3 个转轮,从左到右分别称为慢轮子、中轮子和快轮子。转轮内部相当于一个单表代换。当按下某一键时,电信号从慢轮子的输人引脚进人,经过内部连线流经每个转轮,最后从快轮子的输出引脚输出密文。如在图 3-1(a)中,如果按下字母键 A,则一个电信号被加到慢轮子的输入引脚 24 并通过内部连线连接到慢轮子的输出引脚 24,经过中轮子的输入引脚 24 和输出引脚 24,连接到快轮子的输人引脚 18,最后从快轮子的输出引脚 18 输出密文字母 B

图片

8.jpg

如果转轮机始终保持图 3-1(a)的连接状态,则按下字母键 A,输出的密文永远是字母 B,这显然是单表代换密码。转轮密码机的设计目的是通过转轮的转动来实现复杂的多表代换,从而打破明文与密文之间的固定代替关系。所以,转轮密码机在每次击键并输出密文以后,快轮子要转动一个位置,以改变中轮子与快轮子之间的对应关系。如在图 3-1(a)所示状态下,如果按下任意一个键(如 A 键),转轮密码机输出密文(如 B),然后快轮子转动一个位置,即快轮子的所有引脚向下移动一个位置,原最下边的引脚移至顶端,此时转轮密码机的状态如图 3-1(b)所示,显然,此时若再按个 A 键,则一个电信号被加到慢轮子的输入引脚 24 并通过内部连线连接到慢轮子的输出引脚 24,经过中轮子的输人引脚 24 和输出引脚 24,连接到快轮子的输出引脚 17,最后从快轮子的输出引脚 17 输出密文字母 E,显然,两次的输出结果是完全不同的,快轮子转动一圈(26 个位置),中轮子转动一个位置;中轮子转动一圈(26 个位置),慢轮子转动一个位置。因此,在加密或解密 26×26×26 个字母以后,所有转轮都恢复到初始状态。由此可知,一个有 3 个转轮的转轮密码机是一个密周期为 26×26×26=17 576 的多表代换密码机械装置。一个 5 转轮密码机的密钥周期是 265=11881 376,一般地,一个有 m 个转轮的密码机其周期是 $26^m$,所以转轮密码机是一种长周期的多表代换密码机。

总结

转轮密码机的使用大大提高了密码加解密速度,同时其抗攻击性有很大的提高,在第二次世界大战中有着广泛的应用,它是密码学发展史上的一个里程碑。

总结

写了好久,总结懒得写了,让 AI 写的 😋

古代密码体制的学习让我对古代的通信方式和信息安全有了更深入的了解。

古代加密方法,如古希腊战争中的隐写术,以及中国古代的藏头诗、藏尾诗、漏格诗等,都是古人为了保护信息不被敌方获取而发明的方法。这些方法虽然简单,但体现了古人的智慧和对信息安全的重视。

古代密码体制的发明和进步,反映了人类对信息安全的不断追求。同时,这些加密方法也为我们现代人提供了宝贵的启示,让我们看到了信息安全的重要性,以及在信息安全领域中不断探索和创新的精神。

学习古代密码体制,不仅让我们了解了古代的通信历史和文化,更让我们对信息安全有了更深入的认识和理解。在当今这个信息化的时代,信息安全已经成为我们生活中不可或缺的一部分,我们每个人都应该加强对信息安全的重视和保护。