最近好好整理了一下有关RSA的知识点,以及其在CTF比赛中的应用
基础知识
数论知识
在这边简单阐述rsa需要用到的数论知识:
互质关系,两个正整数除了1以外没有其他公因子,就称这两个数为互质关系(coprime)
欧拉函数:任意给定正整数n,计算小于等于n正整数中,与n构成互质关系的个数
欧拉定理:
也就是说,a的φ(n)次方被n除的余数为1。或者说,a的φ(n)次方减去1,可以被n整除。比如,3和7互质,而7的欧拉函数φ(7)等于6,所以3的6次方(729)减去1,可以被7整除(728/7=104)。
关于欧拉函数的结论很多,可以去别的地方查找,但是这边只介绍两个
(1) 如果n是质数,则 φ(n)=n-1
(2) 如果n可以分解成两个互质的整数之积:
n = p1 * p2 ==> φ(n) = φ(p1p2) = φ(p1)φ(p2)
模反元素:如果两个正整数a和n互质,那么一定可以找到整数b,使得ab-1被n整除
比如,3和11互质,那么3的模反元素就是4,因为 (3 × 4)-1 可以被11整除。显然,模反元素不止一个, 4加减11的整数倍都是3的模反元素 {…,-18,-7,4,15,26,…},即如果b是a的模反元素,则 b+kn 都是a的模反元素。
欧拉定理可以用来证明模范元素必然存在
rsa加密算法
随机选择两个不相等的质数p和q
计算p和q的乘积n
1 | n = p * q |
- 计算n的欧拉函数φ(n)
1 | φ(n) = (p-1) (q-1) |
随机选择一个整数e,条件:(1<e<φ(n)),且e和φ(n)互质
计算e对于φ(n)的模反元素d
1 | ed ≡ 1 (mod φ(n)) |
- 将n和e扩展成公钥(n,e),n和d封装成私钥(n,d)
1 | 假设n = 3233,e = 17,d = 2753 |
rsa算法的可靠性
一共出现了6个数字:p/q/φ(n)/e/d,公钥用到了两个,其余四个整数都是不公开的,其中最关键的是d,一旦d泄,就等于私钥泄露
结论:如果n可以被因数分解,d就可以被算出,也就意味这私钥被破解。除了暴力破解,还没有发现别的有效方法,目前被破解的最长RSA密钥就是768位。
加密与解密
(1) 加密需要公钥(n,e),假设m就是要加密的值,比如ascii码值或者Unicode值(m必须为整数,且小于n),那么加密后的值为c
1
m ** e ≡ c (mod n)
爱丽丝的例子,爱丽丝的公钥为(3233,17)
假设鲍勃要加密的值是65,通过下面的计算,2790就是鲍勃发送的值
1
65 ** 17 ≡ 2790 (mod 3233)
(2) 解密需要私钥(n,d),下面的等式一定成立
1
c ** d ≡ m (mod n)
还是爱丽丝的例子,爱丽丝就用自己的私钥(3233,2735)进行解密
1
2790 ** 2753 ≡ 65 (mod 3233)
因此,爱丽丝就知道鲍勃加密前的密文就是65
当然还有关于上述公示的证明,可以参考阮老师的博客:
http://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
http://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
yafu的使用
最常用的方法是在命令行中直接使用factor()函数分解
1 | yafu-x64 factor(n) |
当然,命令行可能不支持太长的n
新建一个pcat.txt,注意最后一定得换行,运行命令
1 | yafu-x64 “factor(@)” -batchfile pcat.txt |
中国剩余定理
我来举个简单的例子来理解一下CRT的代码实现。
1 | import gmpy2 |
结果不唯一,x + kM都可以是结果
所以如果是求最小值,可以x%M
上面的代码简化一下,就可以变成下面的代码:
1 | import gmpy2 |
例题:蓝桥杯2022年python B组试题B:寻找整数
1 | import gmpy2 |
各种题型
基础解法
rsarsa (基础解法)
1 | Math is cool! Use the RSA algorithm to decode the secret message, c, p, q, and e are parameters for the RSA algorithm. |
1 | import gmpy2 |
- 首先我们需要求出d,这样就使得rsa私钥被泄露
介绍一个函数gmpy2.invert()函数,专门用来求模反的函数,需要求出的d就是e关于φ(n)的模反
n = p1 * p2 ==> φ(n) = φ(pq) = φ(p)φ(q) = (p - 1)(q - 1)
ed ≡ 1 (mod φ(n))
- 求出flag,也就是密文,c是明文(被加密之后的),需要求出求得d之后,计算
1 | c ** d ≡ m (mod n) |
转换成代码,m = pow(c,d,n)
RSAROOL (在线分解)
题目给出公钥{n,e},因式分解求p和q
1 | p = 18443 |
当然,因式分解python代码爆破也行:
1 | import time |
完整代码:
1 | import gmpy2 |
RSA (基础解法)
1 | 在一次RSA密钥对生成中,假设p=473398607161,q=4511491,e=17 |
1 | import gmpy2 |
[ACTF新生赛2020]crypto-rsa0 (zip伪加密)
misc zip伪加密
参考链接:
zip格式共有三个数据区:压缩源文件数据区/压缩源文件目录区/压缩源文件目录结束标志
第一区的第三个字节和第二区的第四个字节都显示是否加密的信息.修改两个区域的加密信息,数字是奇数时为加密,数字为偶数时,则不加密。我们修改09 00 改成08 00
winhex打开查看
修改后:
在来看这道题,放在winhex中打开,修改一下
1 | 9018588066434206377240277162476739271386240173088676526295315163990968347022922841299128274551482926490908399237153883494964743436193853978459947060210411 |
给了上述的三串数字,猜测是p/q/n,上代码:
1 | from Crypto.Util import number |
[GUET-CTF2019]BabyRSA (z3)
题目给出了(p+1)(q+1),我们用z3来解就行
上代码:
1 | from z3 import * |
[HDCTF2019]bbbbbbrsa (爆破e)
题目给出了n和q,base64解码一下c,e的范围不是很大,所以直接暴力循环e就可以
1 | import base64 |
RSA (公钥解析1)
题目给出了一个key后缀文件和一个enc后缀的文件
需要进行公钥解析:
上代码:
1 | from Crypto.Util import number |
[AFCTF2018]可怜的RSA (公钥解析2 + PKCS1_OAEP)
我们也可以使用Crypto.PublicKey.RSA包中的函数来导入给出的public.key文件
1 | import base64 |
分解n,当然可以使用在线网站
然后是从flag.ence中读取c,一开始我使用的是平常的m = pow(c,d,n),会出错。
–正解–
打包密钥之后,使用PKCS1_OAEP填充模式来解
1 | from base64 import * |
模不互质
当题目给出多个n,多个c,但只给出一个e,我们可以考虑模不互质攻击
q = gcd(n1,n2)
ezrsa
这题是将一个同一个密文,用不同的模,相同的e加密了两次
1 | import math |
1 | import math |
dp泄露
RSA2 (dp泄露)
题目给了个dp,也不知道是啥,继续因式分解,求p和q
其中解出来的密文m需要用Crypto.Util.number的库中long_to_bytes()函数
1 | import gmpy2 |
–更新–
查看 前方是否可导?大佬的博客之后才发现,dp是用来帮助n来分解p和q的,也就是可以通过dp求得p
1 | 已知: |
直接引用大佬的原话:
“由dp=d%(p-1)我们可以知道dp<p-1,因此我们很容易得到k的上限要小于e,因此只需要遍历range(1,e) ,若n整除p,即可得到p,从而结束循环。”
我们已知e和dp,我们需要遍历循环k,使得k能被(e*dp - 1)整除,若当前k值满足条件,则 p = k + 1
引用大佬代码:
1 | import gmpy2 |
共模攻击
1ABlades的这篇文章讲的非常详细
假设有一条信息m,使用公钥加密信息(使用了相同的模数n):
1 | c1 = m ** e1 mod n |
就可以用密钥d1,d2来求解
1 | m = c1 ** d1 mod n |
因为e1,e2互质,即
1 | gcd(e1,e2)=1 |
根据扩展欧几里得算法(如果gcd(a, b) = c,则存在x, y,使得c = ax + by。)
则有
1 | e1*s1 + e2*s2 = 1 |
于是我们就能得到以下的公式:
1 | c1 ** s1 * c2 ** s2= m |
推算过程:
1 | (c1 ** s1 * c2 ** s2) mod n = ((m ** e1 mod n) ** s1 * (m ** e2 mod n) ** s2) mod n |
并且可以知道s1/s2皆为整数,且一正一负
RSA3 (共模攻击)
题目中给出了两个e和两个c,一个n
了解原理之后,直接上代码:
1 | import gmpy2 |
RSA & what (共模攻击+base64隐写)
1 | import gmpy2 |
以为通过共模攻击求解bases,再base64解码就可以了,没想到还有隐藏的flag,没辙了
–更新–
https://blog.csdn.net/qq_41956187/article/details/105592471
没想到竟然有base64隐写,颠覆了我的认知,怪不得会有那么多的换行……
完整代码:
1 | import gmpy2 |
已知dp、dq
“一切以解题为目的的抄代码都是没有灵魂的,我们还是要从数学理论上去分析解决它,再去写代码。”
1 | 已知条件: |
经过各种推算:
1 | m1 ≡ c ** dp mod p |
正常解法:
1 | import gmpy2 |
RSA1 (已知dp、dq)
1 | import gmpy2 |
低加密指数攻击
dangerous RSA (低加密指数攻击)
1 | #n: 0x52d483c27cd806550fbe0e37a61af2e7cf5e0efb723dfc81174c918a27627779b21fa3c851e9e94188eaee3d5cd6f752406a43fbecb53e80836ff1e185d3ccd7782ea846c2e91a7b0808986666e0bdadbfb7bdd65670a589a4d2478e9adcafe97c6ee23614bcb2ecc23580f4d2e3cc1ecfec25c50da4bc754dde6c8bfd8d1fc16956c74d8e9196046a01dc9f3024e11461c294f29d7421140732fedacac97b8fe50999117d27943c953f18c4ff4f8c258d839764078d4b6ef6e8591e0ff5563b31a39e6374d0d41c8c46921c25e5904a817ef8e39e5c9b71225a83269693e0b7e3218fc5e5a1e8412ba16e588b3d6ac536dce39fcdfce81eec79979ea6872793L |
因为c = m ** e%n,m = c ** d%n
当指数e很小的时候(通常为3),可以分两种情况来讨论
当m ** e<n:
c = m ** e
对c开e次根号
当m ** e>n:
m ** e = k * n+c
循环爆破k,使其满足条件
这边推荐使用gmpy2.iroot函数来开根号,这个函数的返回值为一个(x,y)元组,其中x为结果值,y为一个bool型变量,如果x为整数,y=True,否则y=False
1 | import gmpy2 |
当然这题,这道题的m很小,我只是对m开了三次根号。
而对于m值很大的情况,循环k,使k*n+c开e次根号,iroot返回的第二个值为True即可
1 | from gmpy2 import iroot |
公因数分解n
[BJDCTF2020]RSA(公因数分解n+爆破e)
1 | from Crypto.Util.number import getPrime,bytes_to_long |
关于e的爆破脚本:
1 | p2 = 381631268825806469518166370387352035475775677163615730759454343913563615970881967332407709901235637718936184198930226303761876517101208677107311006065728014220477966000620964056616058676999878976943319063836649085085377577273214792371548775204594097887078898598463892440141577974544939268247818937936607013100808169758675042264568547764031628431414727922168580998494695800403043312406643527637667466318473669542326169218665366423043579003388486634167642663495896607282155808331902351188500197960905672207046579647052764579411814305689137519860880916467272056778641442758940135016400808740387144508156358067955215018 |
其余项打算用z3来解的,但是解不出来……
–更新–
忘了一个最重要的条件:当n1,n2很大的时候,q = gcd(n1,n2)!!!
1 | from gmpy2 import * |
求出q,那接下来就好办了:
1 | from gmpy2 import * |
RSA5(公因数求解)
本来想用CRT的,但是题目中给出的e很大,不适合用。
只能从给出的20个n中找规律了
1 | # 若n1,n2不互质 |
从20个n中找出有公约数的一对,然后找出对应n的p和q,剩余的就顺水推舟了。
1 | import gmpy2 |
[脚本参考](https://blog.csdn.net/weixin_44017838/article/details/104878645?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164958629416780261970846%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=164958629416780261970846&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-2-104878645.142 ** v7 ** pc_search_result_control_group,157 ** v4 ** control&utm_term=RSA5&spm=1018.2226.3001.4187)
低加密指数广播攻击(中国剩余定理)
RSA4 (中国剩余定理+低加密指数攻击)
来复习一下加密和解密的规则
1 | c = m ** e % n |
题目给了三个N和三个C,考虑用中国剩余定律(CRT)求出m ** e。
这题给出的数据仔细观察,可以知道是五进制数据,所以还需要转化,int(x,5)即可
题目中还没有给出e,这种题目中e通常都很小,低指数加密,只需要遍历求出的m ** e,开对应的根号即可。
1 | import gmpy2 |
rsa-wiener-attack
rsa2
方法一:在线分解
1 |
|
在线网站分解出p和q
脚本:
1 | import gmpy2 |
注意该脚本需要在python2环境中跑出来,如果在python3中,则会提醒需要编码,而且结果不一致……
方法二:rsa-wiener-attack
当e非常大,和n差不多大的时候,就能考虑使用wiener-attack脚本来解题了。
使用的脚本https://github.com/pablocelayes/rsa-wiener-attack
需要将脚本放在rsa-wiener-attack-master的目录下。
1 | from Crypto.Util.number import * |
注意该脚本也需要在python2环境中跑
数学推理
[BJDCTF2020]easyrsa (反三角函数求导)
1 | from Crypto.Util.number import getPrime,bytes_to_long |
其中Fraction()是分数,Fraction(1,x)就是求倒数
Derivative(f(x),x)是求f(x)关于x的导数
反正切函数arctanx的求导公式:1 /( 1 + x^2 )
反双曲正切 arthx的求导公式:1 /(1 - x^2 )
所以 z = p ^ 2 + q ^ 2
因为p*q = n,所以我们通过z3来求解p和q
1 | from z3 import * |
然后就顺水推舟了
1 | import gmpy2 |
[RoarCTF2019]RSA (推导+爆破e)
1 | A=(((y%x)**5)%(x%y))**2019+y**316+(y+1)/x |
这题首先是需要求出x和y的值,通过计算可以列出以下的等式
1 | import gmpy2 |
p和q是通过x,y,z生成的,可以确定的是,p约是q的166倍,这样我们大概可以求出z的范围,进而可以通过爆破,确定q的值,
1 | z = gmpy2.iroot(n//166,2)[0] |
题目中没有给出我们e值,我们选择爆破e
1 | q = 842868045681390934539739959201847552284980179958879667933078453950968566151662147267006293571765463137270594151138695778986165111380428806545593588078365331313084230014618714412959584843421586674162688321942889369912392031882620994944241987153078156389470370195514285850736541078623854327959382156753458569 |
这题看似简单,其实很头疼,如何通过给出的A,推断出x,y,再推断出z的范围,小范围爆破出q,最后还需要爆破e,花的时间挺久的……
当然,我尝试了一下在线分解,成功了。但是我认为,比赛中一般不会给出可以轻易分解的n,所以需要锻炼自己数学推理的能力。
过程中自己还编写了一个isPrime函数,和next_prime(n)函数来测试,贴在这边备用(当然使用sympy.nextprime()函数更佳)
1 | import gmpy2 |
[RoarCTF2019]babyRSA (威尔逊定理)
1 | import sympy |
513位数字的阶乘?蒙了,看大佬的博客才知道原来使用的是威尔逊定律,强到窒息……
简单阐述一下威尔逊定律,就是对于素数n来说,(n - 1)!%n = -1 (A - 1)
由题目可以得知:
p 约等于 (B1!)%A1
A1是质数,由威尔逊定律可以得出:(A1 - 1)!%A1 = -1 % A
而B1 = A1 - randint(1e3,1e5)
(A1 - 1) * (A1 - 2) * (A1- 3) …… (B1+1) * B1!% A1 = -1 % A
推算一下:
(A1 - 2) * (A1- 3) …… (B1+1) * B1!% A1 = 1 % A1
(A1 - 2) * (A1- 3) …… (B1+1)与 B1!% A1是关于A1的模逆,这样就能求出B1!% A1的值了
然后通过在通过sympy.nextprime()函数算出p
1 | import sympy |