最近在学习CTF中Crypto,整理一些关于RSA的知识点,以及在以往比赛中出现的题目。
完美的密码技术因为有不完美的人类参与而无法实现完美的安全性。
简单介绍RSA
RSA是1977年由 罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼 (Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的,三个人名字要有印象,有些题目会用这三个名字,这样可以帮助我们快速判断题目。
RSA公开密钥密码体制的原理是:基于数论,我们能知道随便找两个大素数比较简单,但是如果给定两个大素数的乘积来进行因式分解是及其困难的,因此可以将乘积公开作为加密密钥。
即使 RSA 算法目前来说是安全可靠的,但是错误的应用场景,错误的环境配置,以及错误的使用方法都会导致 RSA 的算法体系出现问题,从而也诞生出针对各种特定场景下的 RSA 攻击方法
RSA公钥密码
RSA公钥密码体制描述如下:
1)选取两个大素数p和q,p和q保密
2)计算n=pq,φ(n)=(p-1)(q-1),n公开,φ(n)(欧拉函数值)保密
3)随机选取正整数 1<e<φ(n),满足gcd(e,φ(n)) = 1,e是公开的加密密钥
4)计算d,满足ed≡1(modφ(n)),d是保密的解密密钥
加密变换:c = m^e mod n
解密变换:m = c^d mod n
虽然n,e 是公开的,但是要想知道 d 的值,必须要将 n 分解来计算出 n 的欧拉函数值,而 n 是两个大素数 p,q 的积, 前面说过分解是及其困难的。 但是对于特定的情况可以使用特殊方法进行分解。
上文以及下文会涉及到一些具体的数论知识,例如:中国剩余定理,同余求余运算,欧拉函数等,大家可以自行查询或者通过《信息安全数学基础》或《现代密码学》进行自学,本文主要针对CTF比赛中关于RSA常见的题型讲解。
gmpy2
python中的一个数论库,本文一些题目的脚本几乎都是基于这个进行编写。可以自行查询。
安装方法pip install gmpy2
CTF中RSA题目常见类型
最最最简单
解决 RSA 题目最简单,最暴力,就是直接分解 n。如果能够将 n 分解成功,得到 p,q 的取值,那么可求φ(n). φ(n)=(p-1)(q-1)
利用e,d 与 n 的欧拉函数满足 ed≡1(modφ(n))
通过欧几里得算法可以通过 e 与 n 的欧拉函数值从而求出 d,计算出解密密钥。
所以在知道 e,p,q 的情况下,就可以解出 d(其实啥都给了,就让你代数计算了)
例题
p = 3487583947589437589237958723892346254777
q = 8767867843568934765983476584376578389
e = 65537 求d?
import gmpy2
p = 3487583947589437589237958723892346254777
q = 8767867843568934765983476584376578389
e = 65537
print gmpy2.invert(e, (p-1)*(q-1))
直接分解 n
素数分解问题是困难的,但是可以暴力分解。通常意义上来说,一般认为 2048bit 以上的 n 是安全的。现在一般的公钥证书都是 4096bit 的证书。
针对大整数的分解有很多种算法,性能上各有优异,有 Fermat 方法,Pollard rho 方法,试除法,以及椭圆曲线法,连分数法,二次筛选法,数域分析法等
推荐一个大整数素分解的网站http://www.factordb.com/
通过在网站上输入 n 的值,如果可以分解或者之前分解成功过,那么我们可以直接得到 p 和 q。然后利用前面方法求解得到密文
例题
2020-CSICTF-Crypto-little RSA
此题可以在CTF-hub上找到
题目包含一个flag.zip 有加密密码,一个enc.txt
ent.txt中的数据
c=32949
n=64741
e=42667
那么我们就可以利用网站将n分解然后使用脚本解密
import gmpy2
c = 32949
n = 64741
e = 42667
p = 101
q = 641
phi = (p - 1) * (q - 1) # φ(n)
d = gmpy2.invert(e, phi)
a = pow(c, d, n) # c的d次方 mod n
print(a)
输出a的结果18429
便是flag.zip的解压密码,然后解压后就是flag的文本文件
flagcsictf{gr34t_m1nds_th1nk_4l1ke}
利用公约数分解 n
如果在两次加密过程中使用的n1和n2具有相同的素因子,那么可以利用欧几里得算法直接将n1和n2分解。通过欧几里得算法可以直接求出n1和n2的一个最大公约数p:
gcd(n1,n2)=p
这样的话
q1 = n1 / p
q2 = n2 / p
例题
2020-BJDCTF-Crypto-rsa
因为题目已经提示了e=52361 那么我们先直接用
从题目附件的task,py提取所需数据
import gmpy2
c1 = 12641635617803746150332232646354596292707861480200207537199141183624438303757120570096741248020236666965755798009656547738616399025300123043766255518596149348930444599820675230046423373053051631932557230849083426859490183732303751744004874183062594856870318614289991675980063548316499486908923209627563871554875612702079100567018698992935818206109087568166097392314105717555482926141030505639571708876213167112187962584484065321545727594135175369233925922507794999607323536976824183162923385005669930403448853465141405846835919842908469787547341752365471892495204307644586161393228776042015534147913888338316244169120
n1 = 13508774104460209743306714034546704137247627344981133461801953479736017021401725818808462898375994767375627749494839671944543822403059978073813122441407612530658168942987820256786583006947001711749230193542370570950705530167921702835627122401475251039000775017381633900222474727396823708695063136246115652622259769634591309421761269548260984426148824641285010730983215377509255011298737827621611158032976420011662547854515610597955628898073569684158225678333474543920326532893446849808112837476684390030976472053905069855522297850688026960701186543428139843783907624317274796926248829543413464754127208843070331063037
c2 = 97915337055253515349847745972087732981120468820838754382612258213240421484845495472248708665806140879522380502220299761352201473698345
n2 = 12806210903061368369054309575159360374022344774547459345216907128193957592938071815865954073287532545947370671838372144806539753829484356064919357285623305209600680570975224639214396805124350862772159272362778768036844634760917612708721787320159318432456050806227784435091161119982613987303255995543165395426658059462110056431392517548717447898084915167661172362984251201688639469652283452307712821398857016487590794996544468826705600332208535201443322267298747117528882985955375246424812616478327182399461709978893464093245135530135430007842223389360212803439850867615121148050034887767584693608776323252233254261047
e = 52361
p = gmpy2.gcd(n1, n2)
q = n1 // p
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
flag = pow(c1, d, n1)
print(bytes.fromhex(hex(flag)[2:]))
最终flag为BJD{p_is_common_divisor}
当然e如果没有提示呢,e小于100000,又有pow(294,e,n)
,可以通过爆破e的取值很快得到e,代码如下
output=381631268825806469518166370387352035475775677163615730759454343913563615970881967332407709901235637718936184198930226303761876517101208677107311006065728014220477966000620964056616058676999878976943319063836649085085377577273214792371548775204594097887078898598463892440141577974544939268247818937936607013100808169758675042264568547764031628431414727922168580998494695800403043312406643527637667466318473669542326169218665366423043579003388486634167642663495896607282155808331902351188500197960905672207046579647052764579411814305689137519860880916467272056778641442758940135016400808740387144508156358067955215018
for i in range(100000):
res=pow(294,i,n1)
if (res==output):
print(i)
e=i
break
共模攻击
如果使用了相同的模 n 对相同的明文 m 进行了加密,我们就可以在不分解 n 的情况下还原出明文 m 的值。即
c1 = m^e1 mod n
c2 = m^e2 mod n
此时不需要分解n,也不需要求解私钥,如果e1和e2互素,那么就可以利用共模攻击利用两个密文和公钥给出的情况下还原密文m
简单的过程,前提条件两个加密指数互质
gcd(e1,e2) = 1,那么s2存在,则:
s1 * e1 + s2 * e2 = 1
因为c1 = m^e1 mod n,c2 = m^e2 mod n
代入化简可以得出 c1^s2 * c2^s2 ≡ m mod n
例题
2020-第五空间智能安全大赛-Crypto-rosb
根据题目给出的py文件,提取所需参数,写出脚本
import gmpy2
n = 0xa1d4d377001f1b8d5b2740514ce699b49dc8a02f12df9a960e80e2a6ee13b7a97d9f508721e3dd7a6842c24ab25ab87d1132358de7c6c4cee3fb3ec9b7fd873626bd0251d16912de1f0f1a2bba52b082339113ad1a262121db31db9ee1bf9f26023182acce8f84612bfeb075803cf610f27b7b16147f7d29cc3fd463df7ea31ca860d59aae5506479c76206603de54044e7b778e21082c4c4da795d39dc2b9c0589e577a773133c89fa8e3a4bd047b8e7d6da0d9a0d8a3c1a3607ce983deb350e1c649725cccb0e9d756fc3107dd4352aa18c45a65bab7772a4c5aef7020a1e67e6085cc125d9fc042d96489a08d885f448ece8f7f254067dfff0c4e72a63557
e1 = 0xf4c1158f
c1 = 0x2f6546062ff19fe6a3155d76ef90410a3cbc07fef5dff8d3d5964174dfcaf9daa003967a29c516657044e87c1cbbf2dba2e158452ca8b7adba5e635915d2925ac4f76312feb3b0c85c3b8722c0e4aedeaec2f2037cc5f676f99b7260c3f83ffbaba86cda0f6a9cd4c70b37296e8f36c3ceaae15b5bf0b290119592ff03427b80055f08c394e5aa6c45bd634c80c59a9f70a92dc70eebec15d4a5e256bf78775e0d3d14f3a0103d9ad8ea6257a0384091f14da59e52581ba2e8ad3adb9747435e9283e8064de21ac41ab2c7b161a3c072b7841d4a594a8b348a923d4cc39f02e05ce95a69c7500c29f6bb415c11e4e0cdb410d0ec2644d6243db38e893c8a3707
e2 = 0xf493f7d1
c2 = 0xd32dfad68d790022758d155f2d8bf46bb762ae5cc17281f2f3a8794575ec684819690b22106c1cdaea06abaf7d0dbf841ebd152be51528338d1da8a78f666e0da85367ee8c1e6addbf590fc15f1b2182972dcbe4bbe8ad359b7d15febd5597f5a87fa4c6c51ac4021af60aeb726a3dc7689daed70144db57d1913a4dc29a2b2ec34c99c507d0856d6bf5d5d01ee514d47c7477a7fb8a6747337e7caf2d6537183c20e14c7b79380d9f7bcd7cda9e3bfb00c2b57822663c9a5a24927bceec316c8ffc59ab3bfc19f364033da038a4fb3ecef3b4cb299f4b600f76b8a518b25b576f745412fe53d229e77e68380397eee6ffbc36f6cc734815cd4065dc73dcbcb
s = gmpy2.gcdext(int(e1), int(e2)) # 扩展欧几里得算法
s1 = s[1]
s2 = s[2]
# 求模反元素
if s1 < 0:
s1 = -s1
c1 = gmpy2.invert(c1, n)
elif s2 < 0:
s2 = -s2
c2 = gmpy2.invert(c2, n)
m = pow(c1, s1, n) * pow(c2, s2, n) % n
print(bytes.fromhex(hex(m)[2:]))
flagg0od_go0d_stu4y_d4yd4y_Up
以上是RSA简单入门,当然常见类型还有低加密指数攻击,低解密指数攻,Coppersmith 相关攻击,侧信道,选择明密文攻击等等,我们会在后续文章逐步补充并且会再多整理几道CTF出现过的例题,还有一些常见类型的变形,比如RSA共模 e1,e2不互素怎么办,我们都会在后续介绍。