Intro

互联网上看到的一个简单的游戏:

接下来我们将进行一个加密&解密的游戏,按照以下操作步骤来进行:

  1. 取明文(你想要加密的内容):任意一个6位数(或者6位以下)
  2. 已知公钥:381523
  3. 密文生成算法:(明文×公钥)→截取后6位
  4. 告诉我密文,我可以通过私钥得出明文
    例如:明文123456,则123456*381523=47101303488
    那么密文就是:303488

这只是一个入门的小游戏,由于本例采用的模数规模不大,容易被暴力算法攻破,下面来对这个游戏进行一定的解析。

不难看出,截取后六位即模数Mod=1e6Mod=1e6,设明文为A,公钥为E,密文为B,则有AE%Mod=BA*E\%Mod=B。在我得到密文后,我会根据手中的私钥D与密文B相乘截取后六位,即可以得到明文A,即BD%Mod=AB*D\%Mod=A

在这个过程中,不妨将第一个式子代入上式,则有(AE%Mod)D%Mod=A(A*E\%Mod)*D\%Mod=A,利用模运算的法则,等价于求(ED)%Mod=1(E*D)\%Mod=1,用数学语言表示为ED1(mod Mod)E*D\equiv1(mod \ Mod)

这时我们使用扩展欧几里得算法即可求解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <cstdio>
typedef long long LL;
LL ExtGCD(LL a, LL b, LL &x, LL &y){
// x, y 为引用传参,故最终程序结束后,x,y会被赋值为可行解
if(b==0){
// 递归终点,ax+by=GCD(a,b)的b为0,故方程变为
// ax=a,则可行解可以是 x=1, y=0
x=1, y=0;
return a;
}
LL d=ExtGCD(b, a%b, x, y), t=x;
x=y, y=t-a/b*x;
return d; // 这里返回值是GCD(a,b)的结果,即最大公约数
}
LL x,y;
int main(){
printf("%lld\n",ExtGCD(381523,1000000,x,y));
printf("%lld, %lld",x,y);
return 0;
}

ExtGCD算法是用来计算ax+by=GCD(a,b)ax+by=GCD(a,b)的一组可行解。因此上述代码中公钥E是方程中的a,解得x为对应的私钥D。这是因为(ED)%Mod=1(E*D)\%Mod=1

等价于EDkMod=GCD(E,Mod)ED-k*Mod=GCD(E,Mod),要求的参数即可与通式一一对应。

由于该例中模数为1e6,我们只需要构造一个形如Nn1的合数,其中N为任意正整数,n为多个0,0的个数取决于你想要加密的最大位数减一……再把Nn1因式分解,真因子分为两堆分别相乘得到互为公私钥的两个数就可以了……
当然,这个所谓的加密,攻击起来不难,不需要找到真正的私钥,只需要尝试找1n1、2n1……这类数,可得到完全不一样的私钥,但可以解密!本例中私钥为2587,则在对1e6取模的意义下,私钥1002587、2002587等等都可以使用,且不影响解密结果。这里是一个爆破该私钥的代码:

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
#include <cstdio>
typedef long long LL;
const LL E=381523;
int mod=1e6,n=1e7,c,a[100000];

void work1(){
LL tmp=1;
for(int i=1;i<=n;i++){
tmp=1ll*i*E%mod;
if(tmp==1) a[++c]=i;
}
printf("%d\n",c);
for(int i=1;i<=c;i++){
printf("%d ",a[i]);
}
}

void work2(){
LL tmp=0;
for(int i=1;i<=n;i++){
tmp=1ll*i*1000000+1;
if(tmp%E==0) a[++c]=i;
}
printf("%d\n",c);
for(int i=1;i<=c;i++){
printf("%d ",a[i]);
}
}

int main(){
work2();
return 0;
}

work1函数可以暴力解出符合条件的私钥,work2函数模仿上述假私钥构建思路进行爆破,运行结果为:

1
2
3
27
987 382510 764033 1145556 1527079 1908602 2290125 2671648 3053171 3434694 3816217 4197740 4579263 4960786 5342309 5723832 6105355 6486878 6868401 7249924 7631447 8012970 8394493 8776016 9157539 9539062 9920585
....

再根据这些数据可以逆向解得多个私钥。

RSA算法初探

密钥计算方法

  1. 选择两个大质数p和q
  2. 计算n=p×qt=(p-1)×(q-1)// z表示欧拉函数
  3. 选择一个与z互质的数,令其为d(私钥)
  4. 找到一个公钥 e 使其满足e*d=1 (mod t)1<e<t
  5. 公开密钥为(e, n),私有密钥为(d, n)

加密解密方法

  1. 将明文看成比特串,将明文划分成k位的块P即可,这里k是满足 2*k<n 的最大整数。
  2. 对每个数据块 P,计算 C=P^e (mod n),C即为P的密文。
  3. 对每个密文块 C,计算P=C^d (mod n),P即为明文
  • 例:公钥(7, 33),私钥(D,33),要加密数字为15,15→27,27经解密得到15(私钥D=3不公开)