凯撒加密与仿射加密
二者都是古典密码学比较有代表的算法。
凯撒加密
加密原理
凯撒加密的原理是约定一个密钥为k
,设明文字母为x
,则密文y
为
y
=
x
+
k
(
m
o
d
26
)
y = x + k (mod 26)
y=x+k(mod26) 。注意当
x
+
k
x + k
x+k 超过字母 z 时就要像循环队列那样中返回头部的字母 a 继续移位。
如:明文是 t ,k = 10,则密文是 d 。
解密原理
将上面的公式
y
=
x
+
k
(
m
o
d
26
)
y = x + k (mod 26)
y=x+k(mod26) 移项即可,目标是已知y
求出x
,因此解密公式为
x
=
y
−
k
(
m
o
d
26
)
x = y - k (mod 26)
x=y−k(mod26)
加密代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int k = 10;
string mingwen, miwen;
getline(cin, miwen);
for (int i = 0; i < mingwen.size(); i++)
{
if (mingwen[i] >= 'A' && mingwen[i] <= 'Z')
miwen += (mingwen[i] + k - 'A') % 26 + 'A';//这一步比较难理解,(mingwen[i] + k - 'A')可以看成是明文向右移位k位之后距离a的位置,模26相当于循环一个周期,此时再用a加上(mingwen[i] + k - 'A') % 26就是密文了
else if (mingwen[i] >= 'a' && mingwen[i] <= 'z')
miwen += (mingwen[i] + k - 'a') % 26 + 'a';
else
miwen += mingwen[i];
}
cout << miwen;
return 0;
}
解密代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int k = 10;
string mingwen, miwen;
getline(cin, miwen);
for (int i = 0; i < miwen.size(); i++)
{
if (miwen[i] >= 'A' && miwen[i] <= 'Z')
mingwen += 'Z' - ('Z' - (miwen[i] - k)) % 26;//这一步比较难理解,可以理解为是用('Z' - (miwen[i] - k))计算出密文向左移位k位后距离字母z的位置,之后再模26,就相当于循环了一整个周期,此时再用字母z减去('Z' - (miwen[i] - k)) % 26的结果就是明文了
else if (miwen[i] >= 'a' && miwen[i] <= 'z')
mingwen += 'z' - ('z' - (miwen[i] - k)) % 26;
else
mingwen += miwen[i];
}
cout << mingwen;
return 0;
}
仿射加密
在这里先放书出通用的加解密公式,再解释其中的原理。
给定密钥
(
α
,
β
)
(\alpha, \beta)
(α,β) 其中
α
,
β
∈
[
0
,
26
)
\alpha,\beta \in [0,26)
α,β∈[0,26) 并为整数,给定明文x
,密文y
:
加密:
𝑦
=
α
𝑥
+
β
(
m
o
d
26
)
𝑦=\alpha𝑥+\beta(mod \ \ 26)
y=αx+β(mod 26)
解密:
x
=
1
α
(
y
−
β
)
(
m
o
d
26
)
x = \frac{1}{\alpha}(y - \beta)(mod \ \ 26)
x=α1(y−β)(mod 26)
其中
1
α
\frac{1}{\alpha}
α1 是逆元,而不是
α
\alpha
α 的倒数。
逆元的定义及求解
α
\alpha
α 对于模数n
的逆元是满足
α
γ
=
1
(
m
o
d
n
)
\alpha\gamma=1(mod\ \ n)
αγ=1(mod n) 的
γ
\gamma
γ 的值,即找出
α
γ
%
n
=
1
\alpha\gamma\%n=1
αγ%n=1 的
γ
\gamma
γ 的值,这个
γ
\gamma
γ 就是
α
\alpha
α 的逆元。
定理:对于 α \alpha α 如果相对模数 n 存在逆元,则需要满足 g c d ( α , n ) = 1 gcd(\alpha, n) = 1 gcd(α,n)=1 。
这里取 n = 26,为了使得 g c d ( α , n ) = 1 gcd(\alpha, n) = 1 gcd(α,n)=1 ,则 α \alpha α 必须与26互质。因为如果不是互质,则在加密的 y = α x + β y = \alpha x+\beta y=αx+β就不是单射。例如如 y = ( 13 x + β ) m o d 26 y = (13x+\beta)mod\ 26 y=(13x+β)mod 26,x 取2、4、6……的字母结果都是 y = β y = \beta y=β ,则就有很多个明文字母同时对应一个密文字母。
由于 α \alpha α 与26互质且小于26,所以 α \alpha α 可能的取值是{1,3,5,7,9,11,15,17,19,21,23,25},而在定义中可知 β ∈ [ 0 , 26 ) \beta \in [0,26) β∈[0,26) 。
所以对于仿射加密所有的密钥可能只有 12x26 = 312 种。
求逆元程序:
int qiuniyuan(int a, int n)
{
for (int r = 0; r < n; r++)
{
if (a * r % n == 1)
return r;
}
return -1;
}
解释为什么 α , β ∈ [ 0 , 26 ) \alpha,\beta \in [0,26) α,β∈[0,26)
模算术运算的性质
(1)[(a mod n) + (b mod n)]mod n = (a + b)mod n
(2)[(a mod n) – (b mod n)]mod n = (a - b)mod n
(3)[(a mod n) x (b mod n)]mod n = (a x b)mod n
具体解释
以加密 𝑦 = α 𝑥 + β ( m o d 26 ) 𝑦=\alpha𝑥+\beta(mod \ \ 26) y=αx+β(mod 26) 为例, 𝑦 = α 𝑥 + β ( m o d 26 ) = α ( % 26 ) x ( % 26 ) + β ( % 26 ) 𝑦=\alpha𝑥+\beta(mod \ \ 26) = \alpha(\%26)\ x(\%26) + \beta(\% 26) y=αx+β(mod 26)=α(%26) x(%26)+β(%26) ,则 α , β \alpha,\beta α,β 超过了26也会被模回去,所以 α , β ∈ [ 0 , 26 ) \alpha,\beta \in [0,26) α,β∈[0,26)
使用暴力的方式对仿射加密进行破解
由于 α \alpha α 可能的取值是{1,3,5,7,9,11,15,17,19,21,23,25},而在定义中可知 β ∈ [ 0 , 26 ) \beta \in [0,26) β∈[0,26) ,所以可以遍历所有得密钥组合 ( α , β ) (\alpha, \beta) (α,β) 看解出得明文哪个比较像人话,对应的密钥就是正确的密钥。
//代码中加入了计算时间的函数
#include <bits/stdc++.h>
using namespace std;
int qiuniyuan(int a, int n)//求逆元的函数
{
for (int r = 0; r < n; r++)
{
if (a * r % n == 1)
return r;
}
return -1;
}
int main()
{
clock_t start,end;
start = clock();
ofstream ofile;//将所有解密出的明文放在文件里
ofile.open("D:\\CPP\\output1.txt");
int A[] = {1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25};
for (int a = 0; a < 12; a++)//遍历密钥(a,b)中的a
{
for (int b = 0; b < 26; b++)//遍历密钥(a,b)中的b
{
ifstream ifile;
ifile.open("input.txt");
ofile << "(" << A[a] << ", " << b << ")" << endl;//输出此时的密钥
string miwen;
int r = qiuniyuan(A[a], 26);
while (getline(ifile, miwen))
{
for (int i = 0; i < miwen.size(); i++)
{
if (miwen[i] >= 'A' && miwen[i] <= 'Z')
ofile << char(r * (miwen[i] - 'A' - b + 26) % 26 + 'A'); //注意这里要+26,因为可能y-b有负数
else if (miwen[i] >= 'a' && miwen[i] <= 'z')
ofile << char(r * (miwen[i] - 'a' - b + 26) % 26 + 'a'); //注意这里要+26,因为可能y-b有负数
else
ofile << miwen[i];
}
ofile << endl;
}
}
}
end = clock();
cout << end - start << "ms" << endl;
return 0;
}
使用词频统计的方式对仿射加密进行破解
这种方式只对于较长的密文有效果,对于很短的密文可能会出错。
step1:统计密文中字母的频率
step2:与标准字母频率比较,找到两个明文-密文的映射(因为仿射加密是线性映射,所以明文和对应(长篇)密文出现的频率是一致的。)
step3:
实际求解思路(代码里的思路)
计算对应的 α 、 β \alpha、\beta α、β ,计算方法如下:
如:明文和密文的对应关系是e->v,t->w
由于:e:4,t:19,v:21,w:22,用 y = α x + β ( m o d 26 ) y=\alpha x+\beta(mod \ \ 26) y=αx+β(mod 26) 加密
所以我们可得两个线性方程组:
{
21
=
4
α
+
β
(
m
o
d
26
)
即
26
m
1
+
21
=
4
α
+
β
22
=
19
α
+
β
(
m
o
d
26
)
即
26
m
2
+
22
=
19
α
+
β
\left\{ \begin{matrix} 21 = 4α +β(mod\ \ 26) \ \ \ \ 即 \ \ \ \ 26m_1 + 21 = 4α +β \\ 22 = 19α+β(mod\ \ 26) \ \ \ \ 即 \ \ \ \ 26m_2 + 22 = 19α +β \\ \end{matrix} \right.
{21=4α+β(mod 26) 即 26m1+21=4α+β22=19α+β(mod 26) 即 26m2+22=19α+β
上下相减得 26 ( m 2 − m 1 ) + 1 = 5 α 26(m_2 - m_1) + 1 = 5\alpha 26(m2−m1)+1=5α ,用 m = m 2 − m 1 m = m_2 - m_1 m=m2−m1 代替,因为 α \alpha α 可能的取值是{1,3,5,7,9,11,15,17,19,21,23,25},所以遍历 α \alpha α 可能的取值来求出整数的解 m m m(因为 m 1 、 m 2 m_1、m_2 m1、m2 也是整数),第一个求解出来整数解 m 的 α \alpha α 就是密钥中的 α \alpha α 。
用
m
m
m 代替
m
1
m_1
m1 ,求出
β
\beta
β。能这么代替的原因是:若不代替则最后的结果是
β
=
(
26
m
1
+
21
−
4
α
)
%
26
\beta = (26m_1 + 21 - 4α) \%26
β=(26m1+21−4α)%26
代替后最后的结果应该是
β
=
(
26
(
m
2
−
m
1
)
+
21
−
4
α
)
%
26
\beta = (26(m_2 - m_1) + 21 - 4α) \%26
β=(26(m2−m1)+21−4α)%26
根据模运算的法则,若
(
26
(
m
2
−
m
1
)
+
21
−
4
α
)
(26(m_2 - m_1) + 21 - 4α)
(26(m2−m1)+21−4α) 小于0,则在模26的时候会先加上一个26的倍数c(即
(
26
(
m
2
−
m
1
)
+
21
−
4
α
+
26
c
)
=
(
26
(
m
2
−
m
1
+
c
)
+
21
−
4
α
)
(26(m_2 - m_1) + 21 - 4α + 26c) = (26(m_2 - m_1 + c) + 21 - 4α)
(26(m2−m1)+21−4α+26c)=(26(m2−m1+c)+21−4α) ),让它变成正的之后再取模,所以
(
26
(
m
2
−
m
1
+
c
)
+
21
−
4
α
)
%
26
=
(
(
26
(
m
2
−
m
1
+
c
)
)
%
26
+
(
21
−
4
α
)
%
26
)
=
(
0
+
(
21
−
4
α
)
%
26
)
(26(m_2 - m_1 + c) + 21 - 4α) \% 26 = ((26(m_2 - m_1 + c))\%26 + (21 - 4\alpha)\%26) = (0 + (21 - 4\alpha)\%26)
(26(m2−m1+c)+21−4α)%26=((26(m2−m1+c))%26+(21−4α)%26)=(0+(21−4α)%26)
若
(
26
(
m
2
−
m
1
)
+
21
−
4
α
)
(26(m_2 - m_1) + 21 - 4\alpha)
(26(m2−m1)+21−4α) 大于0,则
( 26 ( m 2 − m 1 ) + 21 − 4 α ) % 26 = ( ( 26 ( m 2 − m 1 ) ) % 26 + ( 21 − 4 α ) % 26 ) = ( 0 + ( 21 − 4 α ) % 26 ) (26(m_2 - m_1) + 21 - 4\alpha)\%26 = ((26(m_2 - m_1))\%26 + (21 - 4\alpha)\%26) = (0 + (21 - 4\alpha)\%26) (26(m2−m1)+21−4α)%26=((26(m2−m1))%26+(21−4α)%26)=(0+(21−4α)%26)
可以发现代替后求出来的 β \beta β 都一样,所以可以直接代替加快求解速度。
里面的精髓就是26乘任何数的结果再模26都等于0。
综上,可以直接用 m m m 代替 m 1 m_1 m1 带回 26 m 1 + 21 = 4 α + β 26m_1 + 21 = 4α +β 26m1+21=4α+β 求出 β \beta β 。
对于这一题求解出 α = 7 , β = 19 \alpha=7,\beta=19 α=7,β=19。
书面上的书写
上面都是实际求解过程,书面上我觉得应该这么写:
step3:
计算对应的 α 、 β \alpha、\beta α、β ,计算方法如下:
如:明文
和密文
的对应关系是e–>v,t–>w
由于:e:4,t:19,v:21,w:22,用 𝑦 = α x + β ( m o d 26 ) 𝑦=\alpha x+\beta(mod \ \ 26) y=αx+β(mod 26) 加密
所以我们可得两个线性方程组:
{
21
=
4
α
+
β
(
m
o
d
26
)
即
26
m
+
21
=
4
α
+
β
22
=
19
α
+
β
(
m
o
d
26
)
即
26
m
+
22
=
19
α
+
β
\left\{ \begin{matrix} 21 = 4\alpha +\beta(mod\ \ 26) \ \ \ \ 即 \ \ \ \ 26m + 21 = 4\alpha +\beta \\ 22 = 19\alpha+\beta(mod\ \ 26) \ \ \ \ 即 \ \ \ \ 26m + 22 = 19\alpha +\beta \\ \end{matrix} \right.
{21=4α+β(mod 26) 即 26m+21=4α+β22=19α+β(mod 26) 即 26m+22=19α+β
其中: m ∈ [ 0 , 26 ) m \in [0,26) m∈[0,26) ,这是因为若 26 m + 21 = 4 α + β 26m + 21 = 4α +β 26m+21=4α+β ,则 ( 26 m + 21 ) % 26 = ( 4 α + β ) % 26 (26m + 21)\%26 = (4α +β)\%26 (26m+21)%26=(4α+β)%26 ,所以 m 超过了26也会被模回去到 [ 0 , 26 ) [0,26) [0,26) 里。
则遍历 m ,若求出唯一解 ( α , β ) (\alpha, \beta) (α,β) ,则这个 ( α , β ) (\alpha, \beta) (α,β) 就是对应的正确的密钥,若 m m m 从 0~25 如果都求不出 ( α , β ) (\alpha, \beta) (α,β) 说明这个明文-密文映射关系就错了。
对于这一题,当 m = 1 m=1 m=1 时有唯一解即 α = 7 , β = 19 \alpha=7,\beta=19 α=7,β=19。
实际破解时,假定的密钥(a, b)可能不止一组,但是对于当前计算机的计算能力,即使是使用穷举法也很快就能破解仿射加密
//代码还加入了计算时间的函数
#include <bits/stdc++.h>
using namespace std;
int qiuniyuan(int a, int n)//求逆元
{
for (int r = 0; r < n; r++)
{
if (a * r % n == 1)
return r;
}
return -1;
}
int *tongjicipin()//统计密文的词频
{
int cipin[26] = {0};//记录每个字母的词频
ifstream ifile;
ifile.open("input.txt");
string miwen;
while (getline(ifile, miwen))
{
for (int i = 0; i < miwen.size(); i++)
{
if (miwen[i] >= 'A' && miwen[i] <= 'Z')
cipin[miwen[i] - 'A']++;
else if (miwen[i] >= 'a' && miwen[i] <= 'z')
cipin[miwen[i] - 'a']++;
}
}
int a = 0, b = 0;//a是词频最大的字母序号,b是第二大的
for (int i = 1; i < 26; i++)
{
if (cipin[i] > cipin[a])
{
b = a;
a = i;
}
else if (cipin[i] > cipin[b])
b = i;
}
int *result = new int(2);
result[0] = a;
result[1] = b;
return result;
}
int *qiujiemiyao(int x, int y, int z, int t)//求解密钥的函数
{
int A[] = {1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25};
int *result = new int(2);
for (int a = 0; a < 12; a++)
{
if ((A[a] * (t - y) - (z - x)) % 26 == 0)
{
int m = (A[a] * (t - y) - (z - x)) / 26;
result[0] = A[a];
result[1] = 26 * m + x - A[a] * y;
result[1] %= 26;
return result;
}
}
result[0] = result[1] = -1;
return result;
}
int main()
{
clock_t start, end;
start = clock();
int zimucipin[] = {4, 19, 14, 0, 13, 8, 17, 18, 7, 3, 11, 2, 20, 12, 15, 24, 22, 6, 1, 21, 10, 23, 9, 16, 25};
ofstream ofile;
ofile.open("D:\\CPP\\output2.txt");
int *cipin = tongjicipin();
int *miyao = qiujiemiyao(cipin[0], zimucipin[0], cipin[1], zimucipin[1]);
if (miyao[0] != -1 && miyao[1] != -1)
{
ifstream ifile;
ifile.open("input.txt");
ofile << "(" << miyao[0] << ", " << miyao[1] << ")" << endl;
string miwen;
int r = qiuniyuan(miyao[0], 26);
//解密
while (getline(ifile, miwen))
{
for (int i = 0; i < miwen.size(); i++)
{
if (miwen[i] >= 'A' && miwen[i] <= 'Z')
ofile << char(r * (miwen[i] - 'A' - miyao[1] + 26) % 26 + 'A'); //注意这里要+26,因为可能y-b有负数
else if (miwen[i] >= 'a' && miwen[i] <= 'z')
ofile << char(r * (miwen[i] - 'a' - miyao[1] + 26) % 26 + 'a'); //注意这里要+26,因为可能y-b有负数
else
ofile << miwen[i];
}
ofile << endl;
}
}
end = clock();
cout << end - start << "ms" << endl;
return 0;
}