RSA(第十届蓝桥杯省赛C++A组E题) 解题报告
xzc 2019/4/4 (边看World Final B站直播边写的)
题面:(开始码字)
RSA是一种经典的加密算法。它的基本加密过程如下。
首先生成两个大质数p,q, 令n = p*q,设d与(p-1)*(q-1)互质,则可以找到e,使得d*e除以(p-1)*(q-1)的余数为1
n,d,e组成了私钥,n,d构成了公钥。
当使用公钥加密一个整数X时(X<=n-1),计算C = X^d mod n,则C是加密后的密文。
当收到密文C时,可以使用私钥解开,计算公式为:X = C^e mod n。
例如:当p = 5, q = 11, n = 55, e = 27。
若加密数字24,得24^3 % 55 = 19。
解密数字19,得19^27 % 55 = 24。
现在你知道公钥中n = 1001733993063167141,d = 212353,同时,你截获了别人发送的密文C = 20190324,请问,原文是多少?
码字码得好累啊~
题意:
给一个n,分解质因数成p*q, 然后计算d关于(p-1)*(q-1)的逆元,结果为e,
求C^e % n的结果
n = 1001733993063167141
d = 212353
c = 20190324
解题过程:
- 我们肯定要把n分解质因数,我首先想到了欧拉筛素数,然后晒了一发1E8以内的素数,想用这些素数去分解n
//筛素数
const int maxn = 1e8+10;
int a[maxn];
int sushu[maxn/10];
bool notPrime[maxn]; ///筛素数分解不出来,目测是9位数*10位数
int cnt;
void getPrime(int n)
{
for(int i=2;i<=n;++i)
{
if(!notPrime[i]) sushu[cnt++] = i;
for(int j=0;j<cnt&&1ll*i*sushu[j]<=n;++j)
{
notPrime[i*sushu[j]] = true;
if(i%sushu[j]==0) break;
}
}
}
然后分解质因数:
//#define LL long long
void fenjie(LL x)
{
cout<<x<<" = 1";
for(int i=0;i<cnt&&sushu[i]<=x/sushu[i];++i)
{
while(x%sushu[i]==0)
{
cout<<" * "<<sushu[i];
x /= sushu[i];
}
}
if(x>1) cout<<" * "<<x;
cout<<endl;
}
结果是这样的:1001733993063167141 = 1 * 1001733993063167141
显然没分解出来,就是说p和q都是大于1E8的大质数
欧拉筛筛不出来,我又不会杜教筛,所以还是暴力吧
void BF(LL x) //蛮力法
{
cout<<x<<" = ";
for(LL i=1e8+1;i<x;i+=2)
{
if(x%i==0)
cout<<i<<" * ",x/=i;
}
cout<<x<<endl;
}
结果:
1001733993063167141 = 891234941 * 1123984201
好了,现在我们知道了:
p = 891234941
q = 1123984201
这种题,暴力也用不了10s,以后还是写暴力吧…
LL y = (p-1)*(q-1); //y = 1001733991047948000
接下来我们来求逆元:
因为这个mod = y = 1001733991047948000,它不是个质数
我们要求:
d(212353)关于y(1001733991047948000)的乘法逆元
- 法一:求y的欧拉函数k = euler(y), rev = d^(k-1)%mod
- 法二:用拓展欧几里得算法:exgcd复习<-戳这里
我用的exgcd:
//#define LL long long
void exgcd(LL a,LL b,LL &d,LL &x,LL &y)
{
if(b==0)
{
d = a; x = 1; y = 0; return;
}
exgcd(b,a%b,d,y,x);
y -= (a/b)*x;
}
LL rev(LL t,LL m)
{
LL d,x,y;
exgcd(t,m,d,x,y);
return (x%m+m)%m;
}
LL e = rev(d,y); //e = 823816093931522017
然后,我们求c^e%mod
c = 20190324
e = 823816093931522017
mod = 1001733993063167141
注意:
由于这里的mod>1e18太大,快速幂相乘的时候会爆long long的!
这个时候,我们就要用到快速乘
LL fast_product(LL a,LL b,LL mod)//快速乘
{
LL ans = 0;
while(b)
{
if(b&1) ans = (ans+a)%mod;
a = (a+a)%mod;
b>>=1;
}
return ans;
}
然后,我们用快速乘来写快速幂
LL fast_pow(LL a,LL b,LL mod)//快速幂
{
LL ans = 1;
while(b)
{
if(b&1) ans = fast_product(ans,a,mod);
a = fast_product(a,a,mod);
b>>=1;
}
return ans;
}
最后答案为:579706994112328949
附上完整代码:
///Author: xzc
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e8+10;
const LL n = 1001733993063167141ll;
const LL p = 891234941ll;
const LL q = 1123984201ll;
const LL d = 212353;
const LL c = 20190324;
int a[maxn];
int sushu[maxn/10];
bool notPrime[maxn]; ///筛素数分解不出来,目测是9位数*10位数
int cnt;
void getPrime(int n)
{
for(int i=2;i<=n;++i)
{
if(!notPrime[i]) sushu[cnt++] = i;
for(int j=0;j<cnt&&1ll*i*sushu[j]<=n;++j)
{
notPrime[i*sushu[j]] = true;
if(i%sushu[j]==0) break;
}
}
for(int i=0;i<20;++i) cout<<sushu[i]<<" ";cout<<endl;
}
void fenjie(LL x)
{
cout<<x<<" = 1";
for(int i=0;i<cnt&&sushu[i]<=x/sushu[i];++i)
{
while(x%sushu[i]==0)
{
cout<<" * "<<sushu[i];
x /= sushu[i];
}
}
if(x>1) cout<<" * "<<x;
cout<<endl;
}
void BF(LL x)
{
cout<<x<<" = ";
for(LL i=1e8+1;i<x;i+=2)
{
if(x%i==0)
cout<<i<<" * ",x/=i;
}
cout<<x<<endl;
}
void exgcd(LL a,LL b,LL &d,LL &x,LL &y)
{
if(b==0)
{
d = a; x = 1; y = 0; return;
}
exgcd(b,a%b,d,y,x);
y -= (a/b)*x;
}
LL rev(LL t,LL m)
{
LL d,x,y;
exgcd(t,m,d,x,y);
return (x%m+m)%m;
}
LL fast_product(LL a,LL b,LL mod)
{
LL ans = 0;
while(b)
{
if(b&1) ans = (ans+a)%mod;
a = (a+a)%mod;
b>>=1;
}
return ans;
}
LL fast_pow(LL a,LL b,LL mod)
{
LL ans = 1;
while(b)
{
if(b&1) ans = fast_product(ans,a,mod);
a = fast_product(a,a,mod);
b>>=1;
}
return ans;
}
int main()
{
//getPrime(maxn-5);
//fenjie(n);
BF(n);
LL y = (p-1)*(q-1);
LL e = rev(d,y);
LL answer = fast_pow(c,e,n);
cout<<answer<<endl;
return 0;
}
后记: 大数分解质因数还是很难的,18位的大数暴力分解了出来,位数再多一点儿,没有私钥,是很难解码的~