本篇介绍RSA的基本原理
小知识 RSA加密算法是一种非对称加密算法 。
对称加密 指的就是加密和解密使用同一个秘钥,所以叫对称加密。 对称加密只有一个秘钥 ,作为私钥。 加密:原文+密钥 = 密文 解密:密文-密钥 = 原文
非对称加密 指的是加密和解密使用不同的秘钥,一把作为公开的公钥 ,另一把作为私钥 。 公钥加密的信息,只有私钥才能解密,私钥加密的信息,只有公钥才能解密。
RSA基本原理 公钥与私钥 现寻找两个不同的大质数$p,q$。
计算乘积$n$并计算$n$的欧拉函数:
寻找一个公钥$e,1<e<phi$,而且$e$和$phi$互质,即为$gcd(e,phi)=1$
两个正整数,除了$1$以外,没有其他公因子,我们就称这两个数是互质关系。
任何两个质数构成互质。
如何寻找私钥?
这时我们可以得到私钥$d$,这个$d$被称为$e$在模$phi$上的逆元。
在python中,求解d通常使用gmpy2库的公式: d=gmpy2.invert(e,phi)
这样,我们得到公钥是$(n,e)$ 私钥是$(n,d)$
加解密原理 我们定义c为密文 ,有:
我们定义m为明文 ,有:
通常在CTF中,RSA题目就是把我们想要获得的flag表示为m,求解m以获得flag。
解密公式证明 1.如果有$gcd(m,n)=1$,根据欧拉定理:
对于同余式左边:
由①②得到$m\equiv c^d(\bmod n)$
2.如果$gcd(m,n)≠1$,由于n的因子只有p和q,所以这意味着m是p或者q的倍数, 要继续证明上面的结论成立,可以想办法构造出和mn互质时一样的结论式,设:
此时一定有$gcd(m,q)=1$,否则m就是pq的倍数,出现$m>pq=n$,和加密原理模n下相矛盾。
根据欧拉定理:
我们一开始设了$m=tp$,上式左右分别乘$m$得到:
就又得到了前面探讨得到的公式,化简即可:
RSA解密 在正式开始解密RSA之前,需要先给python安装一些最基本的模块:
终端输入:
1 2 3 4 pip install gmpy2 pip install libnum pip install pycryptodome pip install sympy
例题:(新生平台)ezRSA
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from Crypto.Util.number import *import gmpy2flag=b"??????????????????" m=bytes_to_long(flag) p=getPrime(1024 ) q=gmpy2.next_prime(p) n=p*q print (n)e=65537 c=pow (m,e,n) print (c)
本题属于最简单的RSA类型,先来研究一下python程序:
1 2 3 4 5 6 7 8 9 10 flag=b"??????????????????" m=bytes_to_long(flag) p=getPrime(1024 ) q=gmpy2.next_prime(p) n=p*q print (n)e=65537 c=pow (m,e,n) print (c)
最后给的两个大数就是本题的n和c,按照题目来看,我们得到p和q就能得到结果,这里我用 yafu 来直接分解n(p和q的值相差较小,或者较大,都会造成n更容易分解的结果,pq十分接近直接用yafu分解)
接下来就可以写出一个最简单的解密程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 from Crypto.Util.number import *import gmpy2p=128235294540203581029047676162247366128839525658441198177720129020350584213274392709067603428263076308036887962218910138038784424416187587894606769170370149162445474355193439948724930611675316254756336226495927881968020301435278378994971503843642245934159260243081588578609535490180200771930999370475784587089 q=128235294540203581029047676162247366128839525658441198177720129020350584213274392709067603428263076308036887962218910138038784424416187587894606769170370149162445474355193439948724930611675316254756336226495927881968020301435278378994971503843642245934159260243081588578609535490180200771930999370475784586669 c=1284007090394457534559591594595629376699681571664262027316039076071127133553113804312097564534186553286181100295542762054904507160534115459034859529895228266518139889225143801112811652825545423485433717838183378906000090670988933570733287614474386186118182201238405665780330282651740116754494850587336498823593620498503772628476280777662952161833142843139074010273350737764004864124892855357753095590507865064342868432121011721451480068849838063160870299677163798703446178122788747518207483770828599815523899779256838027454254819374562664956652947709575838384379737444889258723770115780426248541188582596648143419009 e=65537 n=p*q phi=(p-1 )*(q-1 ) d=gmpy2.invert(e,phi) m=pow (c,d,n) print (long_to_bytes(m))
在sympy库中,如果n很容易分解成p和q,我们可以直接用totient()函数直接求欧拉函数 ,这题就可以直接跳过求p和q的阶段,进一步简化:
1 2 3 4 5 6 7 8 9 10 11 12 from Crypto.Util.number import *import gmpy2import sympyn=16444290765812766358045411541972043758507252774922733402779063666741231070051848803596262388656037842384774122623370953835717357292994833086138897316967231932779532066742701825253957718174353161576654267003021633880907529038587200831657439743361393893720984485872371990817328126194530198205130922860459255017808956073618574623643252666919670263828954200552572941584097493158154451750538573929515907904179521184425112562150877600561219439919175034792678805953915082105020914160484723393970487843131880235389809136552508591804757901416435646720738025971378694995189412099375969306739538017331768924489505571620698916541 c=1284007090394457534559591594595629376699681571664262027316039076071127133553113804312097564534186553286181100295542762054904507160534115459034859529895228266518139889225143801112811652825545423485433717838183378906000090670988933570733287614474386186118182201238405665780330282651740116754494850587336498823593620498503772628476280777662952161833142843139074010273350737764004864124892855357753095590507865064342868432121011721451480068849838063160870299677163798703446178122788747518207483770828599815523899779256838027454254819374562664956652947709575838384379737444889258723770115780426248541188582596648143419009 e=65537 phi=sympy.totient(n) d=gmpy2.invert(e,int (phi)) m=pow (c,d,n) print (long_to_bytes(m))
gmpy2库函数 gmpy2.gcd( )
此函数可以直接用来求两整数的最大公因数。
1 2 import gmpy2print (gmpy2.gcd(3 ,12 ))
gmpy2.invert( )
此函数可以求一个数模的逆元,例如在RSA中求d。
gmpy2.powmod( )
此函数可以求一个整数的x次幂模取余。
1 2 import gmpy2print (gmpy2.powmod(3 ,3 ,4 ))
gmpy2.iroot( )
此函数可以求一个数开n次方。
1 2 import gmpy2print (gmpy2.iroot(8 ,3 ))
gmpy2.is_even/odd/prime( )
可以判断一个数是否为偶数(even)或者奇数(odd)或者质数(prime)。
1 2 3 4 import gmpy2print (gmpy2.is_prime(13 ))print (gmpy2.is_odd(13 ))print (gmpy2.is_even(13 ))
gmpy2.gcdext(e1,e2)
拓展欧几里得算法:
1 2 3 4 5 import gmpy2e1=11187289 e2=9647291 s,s1,s2=gmpy2.gcdext(e1,e2) print (s1,s2)
libnum库函数 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 import libnump=libnum.generate_prime(1024 ) q=libnum.generate_prime(1024 ) flag="flag" a=libnum.s2n(flag) b=libnum.n2s(a) print (a)print (b)flag="flag" a=libnum.s2b(flag) b=libnum.b2s(a) libnum.invmod(e,phi) libnum.gcd(a,b) libnum.lcm(a,b) libnum.nroot(a,b) libnum.solve_crt() libnum.xgcd(a,b)
欧几里得算法 欧几里得算法(辗转相除法求最大公约数):用除数和余数反复做除法运算,当余数为 0 时,取当前算式除数为最大公约数。
例:求1997 和 615 两个正整数的最大公约数: 1997 / 615 = 3 (余 152) 615 / 152 = 4(余7) 152 / 7 = 21(余5) 7 / 5 = 1 (余2) 5 / 2 = 2 (余1) 2 / 1 = 2 (余0) 至此,得到最大公约数为1。
在RSA中,我们有的时候会遇到多对公钥n共用同一个因子q (此时q为公约数),可以尝试使用公约数分解直接获得q的值:
1 2 3 4 5 6 p1=getPrime(256 ) p2=getPrime(256 ) q=getPrime(256 ) n1=p1*q n2=p2*q
在 python 中使用辗转相除法,我们可以定义函数:
1 2 3 4 5 6 7 def gcd (a,b ): if a<b: a,b=b,a while b!=0 : c=a%b a,b=b,c return a
快速幂算法 在RSA的计算中,经常需要用到一个大整数的整数次幂后取模的操作,如果只是按照数学方式进行计算,中间结果会非常大,可以对其算法进行优化,根据模运算性质:
考虑提高指数运算效率,可以采用以下方式:
为了求$a^m$($a,m$为正整数),先把m表示为二进制形式$b_kb_{k-1}…b_0$,可以用二进制形式表示原式:
所以,对于$a^m$,有:
例如19的2进制为10011,可得:
以下是两种快速幂运算的代码,两种运算的顺序不一样,但是结果是相同的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def qpow (a, b, m ): base = a exponent = b result = 1 while exponent > 0 : if exponent & 1 : result = result * base % m base = base * base % m exponent >>= 1 return result def bpow (a, b, m ): base = a bb=bin (b)[2 :] for i in range (1 ,len (bb)): if bb[i]=='1' : base = base * base * a % m else : base = base * base % m return base print (qpow(7 , 560 , 561 ))print (bpow(7 , 560 , 561 ))