RSA总结
RSA总结
基本工具#
-
大整数分解
sage (
divisors(n)
)(小素数)Pollard’s p−1 (
python -m primefac -vs -m=p-1 xxxxxxx
)(光滑数)Williams’s p+1(
python -m primefac -vs -m=p+1 xxxxxxx
)(光滑数)yafu
可用命令行也可从文件导入
bash>>>./yafu-x64.exe "factor(77)"
bash>>>./yafu-x64.exe "factor(@)" -batchfile n.txt
查看文件与解密
具体参考:https://jwt1399.top/posts/49954.html
openssl rsa -pubin -in pubkey.pem -text -modulus
openssl rsautl -decrypt -inkey private.pem -in flag.enc -out flag
-
生成解密密钥:
python rsatool.py -f PEM -o key.key -p 1 -q 1 -e 1 openssl rsautl -decrypt -inkey key.pem -in flag.enc -out flag openssl rsautl -decrypt -oaep -inkey key.pem -in flag.enc -out flag (OAEP方式)
脚本生成解密密钥:
# coding=utf-8 import math import sys from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP rsa_components = (n1, e, int(d1), p, q1) myrsa = RSA.construct(rsa_components) private = open('private.pem', 'w') private.write(myrsa.exportKey()) private.close() rsakey = RSA.importKey(myrsa.exportKey()) rsakey = PKCS1_OAEP.new(rsakey) decrypted = rsakey.decrypt(c_bytes)
-
脚本集
-
https://github.com/Ganapati/RsaCtfTool
#用法一:已知公钥(自动求私钥) $ python3 RsaCtfTool.py --publickey 公钥文件 --uncipherfile 加密文件 #用法二:已知公钥求私钥 $ python3 RsaCtfTool.py --publickey 公钥文件 --private #用法三:密钥格式转换 #把PEM格式的公钥转换为n,e $ python3 RsaCtfTool.py --dumpkey --key 公钥文件 #把n,e转换为PEM格式 $ python3 RsaCtfTool.py --createpub -n 782837482376192871287312987398172312837182 -e 65537
-
-
python库
gmpy2
pycryptodome
pip install pycryptodome
factordb
pip install factordb-pycli
$ factordb 16 \\可以命令行
In [1]: from factordb.factordb import FactorDB \\ 也可以导入
In [2]: f = FactorDB(16)
In [3]: f.get_factor_list()
Out[3]: []
In [4]: f.connect()
Out[4]: <Response [200]>
In [5]: f.get_factor_list()
Out[5]: [2, 2, 2, 2]
In [6]: f.get_factor_from_api()
Out[6]: [['2', 4]]
primefac
整数分解库,包含了很多整数分解的算法。
Some conclusions#
- 若p,q为质数,有
- 若a,b互素,有
- 若p为质数,有
- 若p为质数,且
,有
- 二次剩余
def powp(a, b, e, mod, base):
ansa = 1
ansb = 0
while e:
if e & 1:
ansa, ansb = (ansa * a + ansb * b * base) % mod, (ansa * b + ansb * a) % mod
e >>= 1
a, b = (a * a + b * b * base) % mod, (a * b + b * a) % mod
return ansa
def Cipolla(a, q):
# assert isPrime(q) and q&1
# get m s.t. m**2 % q == a
t = random.randint(2, q - 1)
while pow(t**2 - a, q - 1 >> 1, q) == 1:
t = random.randint(2, q - 1)
base = t**2 - a
m = powp(t, 1, q + 1 >> 1, q, base)
return m
证书格式#
“ PKCS1_OAEP”是一种基于RSA的密码,使用OAEP(最佳非对称加密填充)填充来为加密带来不确定性和更高的安全性。
PEM#
PEM 以 -----BEGIN
开头,以 -----END
结尾,中间包含 ASN.1 格式的数据。ASN.1 是经过 base64 转码的二进制数据。Wikipedia 上有完整 PEM 文件的例子。
用 Python 3 和 PyCryptodome 库可以与 PEM 文件交互并提取相关数据。例如我们想提取出模数 n
:
#!/usr/bin/env python3
from Crypto.PublicKey import RSA
with open("certificate.pem","r") as f:
key = RSA.import_key(f.read())
print(key.n)
DER#
DER 是 ASN.1 类型的二进制编码。后缀 .cer
或 .crt
的证书通常包含 DER 格式的数据,但 Windows 也可能会接受 PEM 格式的数据。
我们可以用 openssl
将 PEM 文件转化为 DER 文件:
openssl x509 -inform DER -in certificate.der > certificate.pem
现在问题被简化成了如何读取 PEM 文件,所以我们可以重复使用上一小节中的 Python 代码。
其他格式转换#
openssl x509 -outform der -in certificate.pem -out certificate.der
openssl x509 -inform der -in certificate.cer -out certificate.pem
模数Attacks
模数攻击也是最直接的攻击方式,如果能够分解rsa中的n那么可以直接获得私钥从而破解。
直接分解模数#
模数比较小,那么我们可以直接分解
通过yafu(直接计算),或者factordb(并不是直接计算,而是保存了很多分解的结果)
http://www.factordb.com/index.php
from Cryoto.Util.number import *
import gmpy2 as gp
p =
q =
e =
c =
n = p*q
phi = (p-1)*(q-1)
d = gp.invert(e,phi)
m = pow(c,d,n)
print(long_to_bytes(m))
#print(bytes.fromhex(hex(m)[2:]))
关于更多的一些分解模数 N 的方法可以参考 https://en.wikipedia.org/wiki/Integer_factorization。
p & q 不当分解 N#
|p-q| 很大#
当 p-q 很大时,一定存在某一个参数较小,这里我们假设为 p,那么我们可以通过穷举的方法对模数进行试除,从而分解模数。
|p-q| 较小#
首先
既然 |p-q| 较小,那么
- 顺序检查
的每一个整数 x,在根号n的附近直到找到一个 x 使得 是平方数,记为 - 那么
, $x2-y2=n=(x+y)\times(x-y) $进而根据平方差公式即可分解 N
此方法为费马分解法
复杂度$ O({\Delta^2\over 4n^{1\over 2} }),\Delta=\arrowvert p- q \arrowvert,
\Delta < n^{1\over 4} $都能很快分解 n
可能可以使用 Pollard's p − 1,Williams's p + 1 算法来分解 N,但是也不是完全可以成功的。
pq的值相近#
#题目
from Crypto.Util.number import getPrime
import gmpy2
p = getPrime(512)
q = gmpy2.next_prime(p)
n=p*q
print('n =',n)
#exp
import gmpy2
n =
tmp=gmpy2.iroot(n,2)[0]
p=gmpy2.next_prime(tmp)
q=n//p
print("p=",p)
print("q=",q)
平方差遍历法#
核心总结就是:令a是n的"中间值"(
),然后让a以步长为1自增遍历,直到pow(a,2)-n的结果可以正好开方为止。那个结果开方就是b。
'''
p = getPrime(512)
q = gmpy2.next_prime(p)
n=p*q
'''
def factor(n):
a = gmpy2.iroot(n, 2)[0]
while 1:
B2 = pow(a, 2) - n
if gmpy2.is_square(B2):
b = gmpy2.iroot(B2, 2)[0]
p = a + b
q = a - b
return p, q
a += 1 # 千万别忘了a的自增步长为1
p,q=factor(n)
fermat#
from Crypto.Util.number import *
from gmpy2 import next_prime
import random
# p = getPrime(512)
# q = next_prime(next_prime(p) + random.randint(2 ** 10, 2 ** 15))
def fermat_factors(n):
"""
Factor given number using Fermat approach, starting from sqrt(n)
:param n: modulus to factor
:return: p, q
"""
assert n % 2 != 0
import gmpy2
a = gmpy2.isqrt(n)
b2 = gmpy2.square(a) - n
while not gmpy2.is_square(b2):
a += 1
b2 = gmpy2.square(a) - n
factor1 = a + gmpy2.isqrt(b2)
factor2 = a - gmpy2.isqrt(b2)
return int(factor1), int(factor2)
n =
e =
c =
q, p = fermat_factors(n)
d = inverse(e, (p - 1) * (q - 1))
print(long_to_bytes(pow(c, d, n)))
2021年“绿城杯”网络安全大赛-Crypto-RSA2-PLUS
题目:
from Crypto.Util.number import *
import gmpy2
from flag import flag
assert flag[:5]==b'flag{'
m1 = bytes_to_long(flag[:20])
p = getPrime(512)
p1 = gmpy2.next_prime(p)
q = getPrime(512)
q1 = gmpy2.next_prime(q)
n1 = p*q*p1*q1
print('n1 =',n1)
e = 0x10001
c1 = pow(m1,e,n1)
print('c1 =',c1)
#n1 =
#c1 =
由题可知,这几个素数不会相差很大,因此
from Crypto.Util.number import *
from gmpy2 import *
n =
c =
e = 0x10001
def fermat_factorization(n):
factor_list = []
get_context().precision = 2048
sqrt_n = int(sqrt(n))
c = sqrt_n
while True:
c += 1
d_square = c**2 - n
if is_square(d_square):
d_square = mpz(d_square)
get_context().precision = 2048
d = int(sqrt(d_square))
factor_list.append([c+d,c-d])
if len(factor_list)==2:
break
return factor_list
factor_list = fermat_factorization(n)
[X1,Y1] = factor_list[0]
[X2,Y2] = factor_list[1]
assert X1*Y1 == n
assert X2*Y2 == n
p1 = gcd(X1,X2)
q1 = X1 // p1
p2 = gcd(Y1,Y2)
q2 = Y1 // p2
Fai = (p1-1)*(q1-1)*(p2-1)*(q2-1)
d = invert(e,Fai)
print long_to_bytes(pow(c,d,n))
p - 1 光滑#
-
光滑数 (Smooth number):指可以分解为小素数乘积的正整数
-
当
是 的因数,并且 是光滑数,可以考虑使用Pollard's p-1
算法来分解NN -
根据费马小定理有
则有
即
- 根据
Pollard's p-1
算法如果
如果
使得
成立,则有
如果结果不为
因为我们只关心最后的N
只包含两个素因子,则我们不需要计算
- 在具体计算中,可以代入降幂进行计算
- Python 代码实现
from gmpy2 import *
a = 2
n = 2
while True:
a = powmod(a, n, N)
res = gcd(a-1, N)
if res != 1 and res != N:
q = N // res
d = invert(e, (res-1)*(q-1))
m = powmod(c, d, N)
print(m)
break
n += 1
参考链接:
p + 1 光滑#
-
当
是 的因数,并且 是光滑数,可以考虑使用Williams's p+1
算法来分解N -
已知
的因数 ,且 是一个光滑数 即第$i q_i^{\alpha_i}\le B_1, \beta_i$ 使得让 且 然后令显然有$p-1\mid R
(N, a) = 1 a^{p-1}\equiv 1 \pmod{p} a^R\equiv 1\pmod{p}$,即 -
同时我们有如果
则有 以及 ,即根据扩展卢卡斯定理
-
第一种情况
:已知 N 的因数 p,且 p+1 是一个光滑数为了找到
,Guy
和Conway
提出可以使用如下公式但是上述公式值太大了,不便运算,我们可以考虑如下方法
如果
,根据公式2.3
有 ,所以根据公式2.8
有 ,设 则有即,如果
第一种情况可以归纳为:
让
,同时找到 使得 定义 且根据
公式2.7
,有要计算
可以用如下公式根据
公式2.2
,公式2.3
,公式2.4
有令
则 则最终公式为第二种情况
:已知 是一个光滑数如果
则根据公式2.7
和公式3.1
有令
计算 通过计算
通过
公式2.6
,公式2.7
和公式3.1
有即
计算
然后计算 -
python 代码实现
def mlucas(v, a, n):
""" Helper function for williams_pp1(). Multiplies along a Lucas sequence modulo n. """
v1, v2 = v, (v**2 - 2) % n
for bit in bin(a)[3:]: v1, v2 = ((v1**2 - 2) % n, (v1*v2 - v) % n) if bit == "0" else ((v1*v2 - v) % n, (v2**2 - 2) % n)
return v1
for v in count(1):
for p in primegen():
e = ilog(isqrt(n), p)
if e == 0: break
for _ in xrange(e): v = mlucas(v, p, n)
g = gcd(v-2, n)
if 1 < g < n: return g # g|n
if g == n: break
参考链接
模不互素#
适用情况:存在两个或更多模数 ,且gcd(N1,N2)!=1 。
当存在两个公钥的 N 不互素时,我们显然可以直接对这两个数求最大公因数,然后直接获得 p,q,进而获得相应的私钥。
多个模数n共用质数,则可以很容易利用欧几里得算法求得他们的质因数之一gcd(N1,N2) ,然后这个最大公约数可用于分解模数分别得到对应的p和q,即可进行解密。
SCTF RSA2
#脚本1
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, PKCS1_OAEP
import gmpy2
from base64 import b64decode
n1 =
n2 =
p1 = gmpy2.gcd(n1, n2)
q1 = n1 / p1
e = 65537
phin = (p1 - 1) * (q1 - 1)
d = gmpy2.invert(e, phin)
cipher =
plain = gmpy2.powmod(cipher, d, n1)
plain = hex(plain)[2:]
if len(plain) % 2 != 0:
plain = '0' + plain
print plain.decode('hex')
#sH1R3_PRlME_1N_rsA_iS_4ulnEra5le
#脚本2
import gmpy2 as gp
n=[]
for i in n:
for j in n:
if (i<>j):
pub_p=gp.gcdext(i,j)
if (pub_p[0]<>1)&(i>j):
print(i)
print(j)
print(pub_p[0])
a=i,p=pub_p[0]
q=a//p
p =
q =
e =
c =
n = p*q
phi = (p-1) * (q-1)
d = gp.invert(e, phi)
m = pow(c, d, n)
print(hex(m)[2:])
print(bytes.fromhex(hex(m)[2:]))
共模攻击#
当两个用户使用相同的模数 N、不同的私钥时,加密同一明文消息时即存在共模攻击。
设两个用户的公钥分别为
当攻击者截获
#脚本1
import gmpy2
from Crypto.Util.number import *
def Commodulus(e1, e2, n, c1, c2):
g, s, t = gmpy2.gcdext(e1, e2)
m = gmpy2.powmod(c1, s, n) * gmpy2.powmod(c2, t, n) % n
print(long_to_bytes(m))
n =
e1 =
e2 =
c1 =
c2 =
Commodulus(e1, e2, n, c1, c2)
#脚本2
import gmpy2 as gp
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
n =
c1 =
c2 =
e1 =
e2 =
s = egcd(e1, e2)
s1 = s[1]
s2 = s[2]
if s1<0:
s1 = - s1
c1 = gp.invert(c1, n)
elif s2<0:
s2 = - s2
c2 = gp.invert(c2, n)
m = pow(c1,s1,n)*pow(c2,s2,n) % n
print(hex(m)[2:])
print(bytes.fromhex(hex(m)[2:]))
公钥 Attacks
低公钥指数#
如果公钥e,和消息m都很小时
由加密公式得
所以当
k = 0
while True:
# c + k * n 如果能被开e次方根的话
if iroot(c + k * n, e)[1]:
print(long_to_bytes(iroot(c + k * n, e)[0]))
break
k += 1
Basic Broadcast Attack#
攻击条件
如果一个用户使用同一个加密指数 e 加密了同一个密文,并发送给了其他 e 个用户。那么就会产生广播攻击。这一攻击由 Håstad 提出。
攻击原理
这里我们假设 e 为 3,并且加密者使用了三个不同的模数
这里我们假设
既然他们互素,那么我们可以根据中国剩余定理,可得
此外,既然
对于较大的 e 来说,我们只是需要更多的明密文对。
#sage
def chinese_remainder(modulus, remainders):
Sum = 0
prod = reduce(lambda a, b: a*b, modulus)
for m_i, r_i in zip(modulus, remainders):
p = prod // m_i
Sum += r_i * (inverse_mod(p,m_i)*p)
return Sum % prod
chinese_remainder([3,5,7],[2,3,2]) #23
#sage
crt([2,3,2],[3,5,7])
#BroadcastAttack
from Crypto.Util.number import *
from libnum import solve_crt
import gmpy2
def BroadcastAttack(N, C, e):
m = solve_crt(C, N)
ans = gmpy2.iroot(m, e)
if ans[1] == True:
return ans[0]
else:
return False
if __name__ == '__main__':
n1 =
c1 =
n2 =
c2 =
n3 =
c3 =
N = [n1, n2, n3]
C = [c1, c2, c3]
for i in range(3, 100):
if BroadcastAttack(N, C, i):
print(long_to_bytes(BroadcastAttack(N, C, i)), i)
Rabin 算法#
密文:
解密:
- 计算出
和 :
- 用扩展欧几里得计算出
和 : [LRX.py](..........\Downloads\WeChat Files\wxid_f7zahwf7mk5q22\FileStorage\File\2020-10\LRX.py)
- 解出四个明文:
注意:如果
而一般情况下,
#脚本1
from Crypto.Util.number import *
from Algorithm import tonelli_shanks
from libnum import solve_crt
def Rabin(p, q, c):
t = tonelli_shanks(c, p)
qlist = [-t, t]
t = tonelli_shanks(c, q)
plist = [-t, t]
for i in qlist:
for j in plist:
print(long_to_bytes(solve_crt([i, j], [p, q])))
#脚本2
import gmpy2
def rabin_decrypt(c, p, q, e=2):
n = p * q
mp = pow(c, (p + 1) // 4, p)
mq = pow(c, (q + 1) // 4, q)
yp = gmpy2.invert(p, q)
yq = gmpy2.invert(q, p)
r = (yp * p * mq + yq * q * mp) % n
rr = n - r
s = (yp * p * mq - yq * q * mp) % n
ss = n - s
return (r, rr, s, ss)
c =
p =
q =
m = rabin_decrypt(c,p,q)
for i in range(4):
try:
print(bytes.fromhex(hex(m[i])[2:]))
except:
pass
这里我们以 XMan 一期夏令营课堂练习(Jarvis OJ 有复现)为例,读一下公钥。
➜ Jarvis OJ-hard RSA git:(master) ✗ openssl rsa -pubin -in pubkey.pem -text -modulus
Public-Key: (256 bit)
Modulus:
00:c2:63:6a:e5:c3:d8:e4:3f:fb:97:ab:09:02:8f:
1a:ac:6c:0b:f6:cd:3d:70:eb:ca:28:1b:ff:e9:7f:
be:30:dd
Exponent: 2 (0x2)
Modulus=C2636AE5C3D8E43FFB97AB09028F1AAC6C0BF6CD3D70EBCA281BFFE97FBE30DD
writing RSA key
-----BEGIN PUBLIC KEY-----
MDowDQYJKoZIhvcNAQEBBQADKQAwJgIhAMJjauXD2OQ/+5erCQKPGqxsC/bNPXDr
yigb/+l/vjDdAgEC
-----END PUBLIC KEY-----
p=275127860351348928173285174381581152299
q=319576316814478949870590164193048041239
编写代码
#!/usr/bin/python
# coding=utf-8
import gmpy2
import string
from Crypto.PublicKey import RSA
# 读取公钥参数
with open('pubkey.pem', 'r') as f:
key = RSA.importKey(f)
N = key.n
e = key.e
with open('flag.enc', 'r') as f:
cipher = f.read().encode('hex')
cipher = string.atoi(cipher, base=16)
# print cipher
print "please input p"
p = int(raw_input(), 10)
print 'please input q'
q = int(raw_input(), 10)
# 计算yp和yq
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)
# 计算mp和mq
mp = pow(cipher, (p + 1) / 4, p)
mq = pow(cipher, (q + 1) / 4, q)
# 计算a,b,c,d
a = (inv_p * p * mq + inv_q * q * mp) % N
b = N - int(a)
c = (inv_p * p * mq - inv_q * q * mp) % N
d = N - int(c)
for i in (a, b, c, d):
s = '%x' % i
if len(s) % 2 != 0:
s = '0' + s
print s.decode('hex')
拿到 flag,PCTF{sp3ci4l_rsa}
。
Boneh and Durfee attack#
$e
设$ A=\frac{N+1}{2},y=\frac{-p-1}{2},x=2k,
如果在模
#2k [(N + 1)/2 + (-p -q)/2] + 1 = 0 mod e
#Boneh and Durfee attack:
f(x,y) = 1 + x * (A + y)
ed = x [(N + 1)/2 + y] + 1
x = 2k
y = (-p-q)/2
-
变种1:
很大, 很小,且 。May’s Attack
假设
,因 ,有 ,对于
,有 ,即 。设 x,y 为参数,则多项式
在模 下存在根$ (x_0,y_0)=(k-1,q) coppersmith attack$可解。
Wiener's Attack#
在 d 比较小(
适用情况:已知 N,e,且 e 过大或过小。
$\because p, q
因为
在
攻击原理
- https://en.wikipedia.org/wiki/Wiener's_attack
- https://sagi.io/2016/04/crypto-classics-wieners-rsa-attack/
工具:
Continued Fractions(连分数)#
连分数就是一个数的连续分式展开,它的式子长这样:
(其中
通常我们用更简单的数组方式来描述,对任何有理数
计算连分数我们可以使用欧几里得算法:
参考:https://zh.wikipedia.org/w/index.php?title=连分数&oldid=65444935
Convergent(收敛)#
我们定义
对于每一个 $ \mathrm{c}{\mathrm{i}}
Legendre' s theorem#
这是
也就是说,当满足
根据这个定理,我们可以通过计算一个数
#脚本1
import gmpy2
def transform(x,y): #使用辗转相处将分数 x/y 转为连分数的形式
res=[]
while y:
res.append(x//y)
x,y=y,x%y
return res
def continued_fraction(sub_res):
numerator,denominator=1,0
for i in sub_res[::-1]: #从sublist的后面往前循环
denominator,numerator=numerator,i*numerator+denominator
return denominator,numerator #得到渐进分数的分母和分子,并返回
#求解每个渐进分数
def sub_fraction(x,y):
res=transform(x,y)
res=list(map(continued_fraction,(res[0:i] for i in range(1,len(res))))) #将连分数的结果逐一截取以求渐进分数
return res
def get_pq(a,b,c): #由p+q和pq的值通过维达定理来求解p和q
par=gmpy2.isqrt(b*b-4*a*c) #由上述可得,开根号一定是整数,因为有解
x1,x2=(-b+par)//(2*a),(-b-par)//(2*a)
return x1,x2
def wienerAttack(e,n):
for (d,k) in sub_fraction(e,n): #用一个for循环来注意试探e/n的连续函数的渐进分数,直到找到一个满足条件的渐进分数
if k==0: #可能会出现连分数的第一个为0的情况,排除
continue
if (e*d-1)%k!=0: #ed=1 (mod φ(n)) 因此如果找到了d的话,(ed-1)会整除φ(n),也就是存在k使得(e*d-1)//k=φ(n)
continue
phi=(e*d-1)//k #这个结果就是 φ(n)
px,qy=get_pq(1,n-phi+1,n)
if px*qy==n:
p,q=abs(int(px)),abs(int(qy)) #可能会得到两个负数,负负得正未尝不会出现
d=gmpy2.invert(e,(p-1)*(q-1)) #求ed=1 (mod φ(n))的结果,也就是e关于 φ(n)的乘法逆元d
return d
print("该方法不适用")
e =
n =
d=wienerAttack(e,n)
print("d=",d)`
#脚本2
#Sage
def rational_to_contfrac(x,y):
# Converts a rational x/y fraction into a list of partial quotients [a0, ..., an]
a = x // y
pquotients = [a]
while a * y != x:
x, y = y, x - a * y
a = x // y
pquotients.append(a)
return pquotients
def convergents_from_contfrac(frac):
# computes the list of convergents using the list of partial quotients
convs = [];
for i in range(len(frac)): convs.append(contfrac_to_rational(frac[0 : i]))
return convs
def contfrac_to_rational (frac):
# Converts a finite continued fraction [a0, ..., an] to an x/y rational.
if len(frac) == 0: return (0,1)
num = frac[-1]
denom = 1
for _ in range(-2, -len(frac) - 1, -1): num, denom = frac[_] * num + denom, num
return (num, denom)
n =
e =
c =
def egcd(a, b):
if a == 0: return (b, 0, 1)
g, x, y = egcd(b % a, a)
return (g, y - (b // a) * x, x)
def mod_inv(a, m):
g, x, _ = egcd(a, m)
return (x + m) % m
def isqrt(n):
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
def crack_rsa(e, n):
frac = rational_to_contfrac(e, n)
convergents = convergents_from_contfrac(frac)
for (k, d) in convergents:
if k != 0 and (e * d - 1) % k == 0:
phi = (e * d - 1) // k
s = n - phi + 1
# check if x*x - s*x + n = 0 has integer roots
D = s * s - 4 * n
if D >= 0:
sq = isqrt(D)
if sq * sq == D and (s + sq) % 2 == 0: return d
d = crack_rsa(e, n)
m = hex(pow(c, d, n))[2:]
print(bytes.fromhex(m))
#脚本3
from Crypto.Util.number import long_to_bytes
e =
n =
c =
#将分数x/y展开为连分数的形式
def transform(x,y):
arr=[]
while y:
arr+=[x//y]
x,y=y,x%y
return arr
#求解渐进分数
def sub_fraction(k):
x=0
y=1
for i in k[::-1]:
x,y=y,x+i*y
return (y,x)
data=transform(e,n)
for x in range(1,len(data)+1):
data1=data[:x]
d = sub_fraction(data1)[1]
m = pow(c,d,n)
flag = long_to_bytes(m)
if b'flag{' in flag:
print(flag)
break
#脚本4
import sympy
def fractions(x,y):
ans=[y//x]
if y%x==0: return ans
else:
ans.extend(fractions(y%x,x))
return ans
def continued_fractions(e,n):
ans=[]
x= fractions(e,n)
for i in range(1,len(x)):
k, d= 1, x[i-1]
for j in x[:i-1][::-1]:
k, d = d, d*j+k
ans.apped((k, d))
return ans
def Wiener(e,n):
for k, d in continued_fractions(e,n):
phi=(e*d-1)//k
#x**2 -(n-phi+1)x+n=0
if d == int(sympy.invert(e,phi)):
return d
break
#脚本5
def wiener(e, n):
m = 12345
c = pow(m, e, n)
q0 = 1
list1 = continued_fraction(Integer(e) / Integer(n))
conv = list1.convergents()
for i in conv:
k = i.numerator()
q1 = i.denominator()
for r in range(20):
for s in range(20):
d = r * q1 + s * q0
m1 = pow(c, d, n)
if m1 == m:
return d
q0 = q1
n =
e =
print (wiener(e, n))
- 变种1:
Paper: https://eprint.iacr.org/2015/399.pdf
2020年羊城杯 - RRRRRRRSA
P1 = getPrime(1038)
P2 = sympy.nextprime(P1)
assert(P2 - P1 < 1000)
Q1 = getPrime(512)
Q2 = sympy.nextprime(Q1
N1 = P1 * P1 * Q1
N2 = P2 * P2 * Q2
尝试对$ \cfrac{N_1}{N_2}
from gmpy2 import *
from Crypto.Util.number import *
N1=
c1=
E1=
N2=
c2=
E2=
def continuedFra(x, y): #不断生成连分数的项
cF = []
while y:
cF += [x // y]
x, y = y, x % y
return cF
def Simplify(ctnf): #化简
numerator = 0
denominator = 1
for x in ctnf[::-1]: #倒叙遍历
numerator, denominator = denominator, x * denominator + numerator
return (numerator, denominator) #把连分数分成分子和算出来的分母
def getit(c):
cf=[]
for i in range(1,len(c)):
cf.append(Simplify(c[:i])) #各个阶段的连分数的分子和分母
return cf #得到一串连分数
def wienerAttack(e, n):
cf=continuedFra(e,n)
for (Q2,Q1) in getit(cf):#遍历得到的连分数,令分子分母分别是Q2,Q1
if Q1 == 0:
continue
if N1%Q1==0 and Q1!=1:#满足这个条件就找到了
return Q1
print('not find!')
Q1=wienerAttack(N1,N2)
print(N1%Q1)
Q2=next_prime(Q1)
P1=iroot(N1//Q1,2)[0]
P2=next_prime(P1)
phi1=(P1)*(P1-1)*(Q1-1)
phi2=(P2)*(P2-1)*(Q2-1)
d1=invert(E1,phi1)
d2=invert(E2,phi2)
flag1=long_to_bytes(pow(c1,d1,N1))
flag2=long_to_bytes(pow(c2,d2,N2))
print(flag1+flag2)
-
变种2:
Wiener’s:
,连分数方法,用 逼近求出$ \cfrac{k}{x} y$。Lattice:
,记为 ,w 有很大概率为 里的最短向量,使用LLL算法求出最短向量,即解出 。
Extending Wiener 's attack#
扩展维纳攻击
来自《Extending Wiener's Attack in the Presence of Many Decrypting Exponents》
,相关题目在 CTF 中已经出现了,例如 2020 羊城杯的 Simple。论文下载链接
维纳Wiener
提出了一种关于私钥过小时对
满足时 (还应满足
Wiener 's Approach#
已知
这里
将两边同时除以
我们知道这里有
时则
所以当
时有
注意这里前面所说的范围和后面的范围并不矛盾
这里对一些参数的值的近似并不严格,所以和维纳攻击的严格范围有出入,具体细节可参考维纳攻击的证明。
Guo 's Approach With Two Exponents#
假设对于一个$ N
化简后可得:
两边同时除以
设
则当
时
所以,如果 $ 2\left(\mathrm{k}{2}-\mathrm{k}\right) \mathrm{d}{1} \mathrm{k}<\mathrm{e}{2}
Extending Wiener 's attack#
Howgrave-Graham 和 Jean-Pierre Seifert 结合了Wiener 和Guo的想法,通过多个等式来构造格利用LLL化简来解决这个问题。
-
为了将分析扩展到$n
e_i d_i $很小),我们同时使用维纳和郭的方法,我们将关系记为维纳等式
,同样我们可以得到关系记为郭等式
。我们假设
和$k_i N^{\alpha_n} g$ 很小, 。可以注意到$W_i G_i N^{1/2 + \alpha} N^\alpha$。最后,我们考虑复合关系式比如
,显然大小为 。 -
原文中这里是定义了两个关系式以及指出了他们的大小范围,这个范围很重要也容容易分析处理,之后我们所做的其实就是使用这两个式子的不同复合关系去构造一个格,然后通过求其基向量得到
,从而可以算得 并可以进一步的对N 进行分解。 -
其实到这里原理分析已经结束,关于格的构造其实也并不复杂,但是核心是这里的复合关系的选取,以及对于最后
大小的分析。
两个小解密指数的情况#
-
我们选取关系
, 这样便有我们对第一个关系式乘上
,这样左边便全是由 构成,这样我们便可以用已知内容构造格将上述式子转化为矩阵运算等式右边向量的大小为
, 为了让大小相等,我们可以考虑构造一个 矩阵。最终我们构造的矩阵为
这样向量
便有这也就是为什么前面需要构造
矩阵的原因,给定 矩阵后,我们可以得到一个上界,这样问题可以转化为类 SVP 问题。那么这里的$ b
b b_2/b_1 d_1g/k_1$之后我们就可以得到
我们假设这些格中最短向量长度为
,其中 。如果这些格是随机的,我们甚至几乎可以肯定没有格点比闵可夫斯基界 ,所以 是最短向量当对于一些小的
,如果有则我们可以通过格基规约找到向量b。
-
上述内容是原文中给出的当两个小解密指数是进行的攻击细节,并且分析了
的大小关系。
分析#
-
扩展维纳攻击结合上述三个例子已经详细的阐明了方法细节,但是其中没有讲解如何选取复合关系。其实在原文的附录中给出了复合关系的选取,以及给出了
的表达式。 -
在原文附录部分,考虑
个指数 ,这样则有 个不同的量 (一个表达式$e_i L_n$ 在乘上 之前,矩阵$L_n N{n2{n-1}}$这样最后一个关系
最大为 ,这样我们便知道了任意情况的最大界值,我们只需要让其他值增加到这么多即可(即构造 矩阵)引入了新的关系式
其中$i_1,\dots,i_u,j_1,\dots,j_u,l_1,\dots,l_v
u + 2v e_i R_{u,v}$ 最多为 ,同时注意我要需要所有系数的大小大致相同,所以我们在某些等式乘上 ,使得关系 。最后我们再计算所有的大小与最大大小$N^{n/2 + n\alpha_n}
D$。这样我们便完成了矩阵$D
D \beta_n = x+y\alpha_n$,这样有则有
对于小
,有所以我们要想让$\alpha_n
x y v$ 和更小的 。比如在$n=2 W_1, G_{1, 2}, W_1W_2 W_1, W_2, W_1W_2 \beta_2 = 5/2 + \alpha \beta_2 = 2$。 -
到这里,其实已经讲清楚了扩展维纳攻击的整个流程,如何选择复合关系,如何构造格,如何构造矩阵
以及如何求解。
这里给出n=2时候的EXP,2020羊城杯 Simple
from Crypto.Util.number import *
from gmpy2 import invert
c =
e1 =
e2 =
N =
a = 5/14
D = diagonal_matrix(ZZ, [N, int(N^(1/2)), int(N^(1+a)), 1])
M = matrix(ZZ, [[1, -N, 0, N^2], [0, e1, -e1, -e1*N], [0, 0, e2, -e2*N], [0, 0, 0, e1*e2]])*D
L = M.LLL()
t = vector(ZZ, L[0])
x = t * M^(-1)
phi = int(x[1]/x[0]*e1)
d = invert(0x10001,phi)
m=pow(c,d,N)
print(long_to_bytes(m))
参考文献#
- Extending Wiener's Attack in the Presence of Many Decrypting Exponents
- 并行 LLL 算法研究综述
- Factoring Polynomials with Rational Coefficients
- A PARALLEL JACOBI-TYPE LATTICE BASIS REDUCTION ALGORITHM
e与phi(n)不互质#
一般来说,RSA的公钥选取时,都会选择一个与
有一道CTF题是比较出名的,NCTF2019
#
这种情况时比较好处理的,我们令
然后将
这个就是一个正常的RSA解密了,不过解出来的不是明文
#
这就是最简单的了,直接开
from gmpy2 import iroot
from Crypto.Util.number import *
p =
q =
c =
n = p*q
e =
phi = (p - 1)*(q - 1)
t = GCD(e, phi)
d = inverse(e // t, phi)
_m = pow(c, d, n)
m = int(iroot(_m, t)[0])
#
如果
from gmpy2 import iroot
p =
q =
c =
n = p*q
e =
k = 0
while not iroot(_m + k*n, t)[1]:
k += 1
m = iroot(_m + k*n, t)[0]
其实就相当于转换成了一个低加密指数的题
- 也可以选择结合中国剩余定理
在不同的域上开根,然后把得到的结果进行CRT组合
#sage
from Crypto.Util.number import *
import gmpy2
p =
q =
c =
n =
e =
phi = (p - 1) * (q - 1)
R.<x> = Zmod(p)[]
f = x ^ e - c
f = f.monic()
res1 = f.roots()
R.<x> = Zmod(q)[]
f = x ^e - c
f = f.monic()
res2 = f.roots()
for i in res1:
for j in res2:
ai = [i[0],j[0]]
mi = [p,q]
flag = CRT_list(ai,mi)
flag = long_to_bytes(flag)
if b'flag' in flag:
print(flag)
#
这个时候就不能用上面的类似RSA的方法解决了,我们就只能在有限域上开根了。
谈论有限域开根问题,AMM算法是绕不过的。AMM算法在RSA中适用于指数e整除phi的情况,也就是说phi % e == 0。其中有详细的论文,AMM算法具体的作用就是在有限域中开出一个根。具体实现论文也明确地给出了。
代码实现:
# sage
def AMM(o, r, q):
start = time.time()
print('\n----------------------------------------------------------------------------------')
print('Start to run Adleman-Manders-Miller Root Extraction Method')
print('Try to find one {:#x}th root of {} modulo {}'.format(r, o, q))
g = GF(q)
o = g(o)
p = g(random.randint(1, q))
while p ^ ((q-1) // r) == 1:
p = g(random.randint(1, q))
print('[+] Find p:{}'.format(p))
t = 0
s = q - 1
while s % r == 0:
t += 1
s = s // r
print('[+] Find s:{}, t:{}'.format(s, t))
k = 1
while (k * s + 1) % r != 0:
k += 1
alp = (k * s + 1) // r
print('[+] Find alp:{}'.format(alp))
a = p ^ (r**(t-1) * s)
b = o ^ (r*alp - 1)
c = p ^ s
h = 1
for i in range(1, t):
d = b ^ (r^(t-1-i))
if d == 1:
j = 0
else:
print('[+] Calculating DLP...')
j = - discrete_log(d, a)
print('[+] Finish DLP...')
b = b * (c^r)^j
h = h * c^j
c = c^r
result = o^alp * h
end = time.time()
print("Finished in {} seconds.".format(end - start))
print('Find one solution: {}'.format(result))
return result
其中
就是在有限域
from sympy.ntheory.residue_ntheory import nthroot_mod
nthroot_mod(a,n,p)

私钥 Attack
D Leaking Attack#
首先当
我们知道
又
其中,
成立。如果
参考文献:
给e,d,n#
计算$ k=ed-1
$k
如果这样的
def divide_pq(e, d, n):
k = e*d - 1
while True:
g = random.randint(2, n-1)
t = k
while True:
if t % 2 != 0:
break
t //= 2
x = pow(g, t, n)
if x > 1 and gmpy2.gcd(x-1, n) > 1:
p = gmpy2.gcd(x-1, n)
return (p, n//p)
DP Leaking Attack#
这类问题是给出了
由
根据已知条件得到方程组
尝试联立
因为
我们知道
所以我们在小于
from Crypto.Util.number import *
import gmpy2
def RSA_dp(e, n, c, dp):
for i in range(1, e):
if (e * dp - 1) % i == 0:
p = (e * dp - 1) // i + 1
if n % p == 0:
q = n // p
phin = (p - 1) * (q - 1)
d = inverse(e, phin)
print(long_to_bytes(pow(c, d, n)))
break
更直观的推导思路:
两边同乘e:
则有:
把
因为
变种1#
-
Hensel lifting for Takagi's scheme (p.189) :
from Crypto.Util.number import *
import gmpy2
p =
dp =
c =
b =
e =
mp1 = pow(c, dp, p)
mp = pow(c, dp - 1, p)
for i in range(1, b - 2):
x = pow(c - pow(mp1, e), 1, p**(i + 1))
y = pow(x * mp * (gmpy2.invert(e, p)), 1, p**(i + 1))
mp1 = mp1 + y
print(long_to_bytes(mp1))
变种2#
(Coppersmith攻击, 已知dp高位攻击)
#Sage
dp0 =
e =
n =
F.<x> = PolynomialRing(Zmod(n))
d = inverse_mod(e, n)
for k in range(1, e):
f = (secret << 200) + x + (k - 1) * d
x0 = f.small_roots(X=2 ** (200 + 1), beta=0.44, epsilon=1/32)
if len(x0) != 0:
dp = x0[0] + (secret << 200)
for i in range(2, e):
p = (e * Integer(dp) - 1 + i) // i
if n % p == 0:
break
if p < 0:
continue
else:
print('k = ',k)
print('p = ',p)
print('dp = ',dp)
break
变种3#
-
变种 3 : 给 n, e, d p, c , 其中 d p 很小, e 很大。
变种4#
- 变种4 : 给 N, e, c , 其中 d p 过小。
情形1: #
确定
beta =
delta =
n = round((1-2*beta-2*delta)/((1-beta)^2-2*delta-beta),6)
m = (1-beta)*n
print(m,n)
构造多项式,分解多项式为
# 脚本1
# Sage
def getC(Scale):
C = [[0 for __ in range(Scale)] for _ in range(Scale)]
for i in range(Scale):
for j in range(Scale):
if i == j or j == 0:
C[i][j] = 1
else:
C[i][j] = C[i-1][j-1] + C[i-1][j]
return C
def getMatrix(Scale, Mvalue, N, E, Del, Bet):
M = [[0 for __ in range(Scale)] for _ in range(Scale)]
C = getC(Scale)
X, Y = int(pow(N,Del)*(Scale+1)//2), int(pow(N,(Del+Bet))*(Scale+1)//2)
for i in range(Scale):
for j in range(Scale):
M[i][j] = N**max(Mvalue-i,0)*E**(max(i-j,0))*X**(Scale-1-j)*Y**j*C[i][j]*(-1)**j
return M
N =
E =
delta = 0.01
beta = 0.37
Scale = 35
Mvalue = 22
M = getMatrix(Scale,Mvalue,N,E,delta,beta)
M = matrix(ZZ,M)
A = M.LLL()[0]
p = []
X = int(pow(N,delta)*(Scale+1)//2)
Y = int(pow(N,(delta+beta))*(Scale+1)//2)
for i in range(Scale):
p.append(A[i]//(X**(Scale-1-i)*Y**i))
PR.<x,y> = PolynomialRing(ZZ)
f = 0
for i in range(Scale):
f += p[i]*x^(Scale-1-i)*y^i
print(f.factor())
# 脚本2
# Sage
N =
e =
n = 12
beta = 0.36
delta = 0.02
X = int(N ** delta*(n+1)/2)
Y = int(N ** (delta + beta)*(n+1)/2)
def C(a,b):
ret=1
for i in range(b):
ret *= (a-i)
ret /= (b-i)
return ret
def get_Matrix(n,m):
MM=[[0 for __ in range(n)] for _ in range(n)]
for j in range(n):
pN = max(0,m-j)
for i in range(j+1):
MM[j][i] = pow(N,pN)*pow(X,n-i-1)*pow(Y,i)*pow(e,j-i)*C(j,i)*pow(-1,i)
MM = Matrix(ZZ,MM)
return MM
M = get_Matrix(n,n//2+1)
L = M.LLL()[0]
x,y = var('x'),var('y')
f = 0
for i in range(n):
f += x**(n-i-1) * y**i * (L[i] // pow(X,n-i-1) // pow(Y,i))
print(f.factor())
参考:
Cryptanalysis of Unbalanced RSA with Small CRT-Exponent
https://hash-hash.github.io/2022/05/14/Unbalanced-RSA-with-Small-CRT-Exponent/#An-Approach-Modulo-e
NSSCTF Round#3 - Secure_in_N
情形2 : #
from copy import deepcopy
# https://www.iacr.org/archive/pkc2006/39580001/39580001.pdf
# Author: ZM__________J, To1in
N =
e =
alpha = log(e, N)
beta =
delta =
P.<x,y,z>=PolynomialRing(ZZ)
X = ceil(2 * N^(alpha + beta + delta - 1))
Y = ceil(2 * N^beta)
Z = ceil(2 * N^(1 - beta))
def f(x,y):
return x*(N-y)+N
def trans(f):
my_tuples = f.exponents(as_ETuples=False)
g = 0
for my_tuple in my_tuples:
exponent = list(my_tuple)
mon = x ^ exponent[0] * y ^ exponent[1] * z ^ exponent[2]
tmp = f.monomial_coefficient(mon)
my_minus = min(exponent[1], exponent[2])
exponent[1] -= my_minus
exponent[2] -= my_minus
tmp *= N^my_minus
tmp *= x ^ exponent[0] * y ^ exponent[1] * z ^ exponent[2]
g += tmp
return g
m = 5 # need to be adjusted according to different situations
tau = ((1 - beta)^2 - delta) / (2 * beta * (1 - beta))
sigma = (1 - beta - delta) / (2 * (1 - beta))
print(sigma * m)
print(tau * m)
s = ceil(sigma * m)
t = ceil(tau * m)
my_polynomials = []
for i in range(m+1):
for j in range(m-i+1):
g_ij = trans(e^(m-i) * x^j * z^s * f(x, y)^i)
my_polynomials.append(g_ij)
for i in range(m+1):
for j in range(1, t+1):
h_ij = trans(e^(m-i) * y^j * z^s * f(x, y)^i)
my_polynomials.append(h_ij)
known_set = set()
new_polynomials = []
my_monomials = []
# construct partial order
while len(my_polynomials) > 0:
for i in range(len(my_polynomials)):
f = my_polynomials[i]
current_monomial_set = set(x^tx * y^ty * z^tz for tx, ty, tz in f.exponents(as_ETuples=False))
delta_set = current_monomial_set - known_set
if len(delta_set) == 1:
new_monomial = list(delta_set)[0]
my_monomials.append(new_monomial)
known_set |= current_monomial_set
new_polynomials.append(f)
my_polynomials.pop(i)
break
else:
raise Exception('GG')
my_polynomials = deepcopy(new_polynomials)
nrows = len(my_polynomials)
ncols = len(my_monomials)
L = [[0 for j in range(ncols)] for i in range(nrows)]
for i in range(nrows):
g_scale = my_polynomials[i](X * x, Y * y, Z * z)
for j in range(ncols):
L[i][j] = g_scale.monomial_coefficient(my_monomials[j])
# remove N^j
for i in range(nrows):
Lii = L[i][i]
N_Power = 1
while (Lii % N == 0):
N_Power *= N
Lii //= N
L[i][i] = Lii
for j in range(ncols):
if (j != i):
L[i][j] = (L[i][j] * inverse_mod(N_Power, e^m))
L = Matrix(ZZ, L)
nrows = L.nrows()
L = L.LLL()
# Recover poly
reduced_polynomials = []
for i in range(nrows):
g_l = 0
for j in range(ncols):
g_l += L[i][j] // my_monomials[j](X, Y, Z) * my_monomials[j]
reduced_polynomials.append(g_l)
# eliminate z
my_ideal_list = [y * z - N] + reduced_polynomials
# Variety
my_ideal_list = [Hi.change_ring(QQ) for Hi in my_ideal_list]
for i in range(len(my_ideal_list),3,-1):
print(i)
V = Ideal(my_ideal_list[:i]).variety(ring=ZZ)
print(V)
参考:
New Attacks on RSA with Small Secret CRT-Exponents
DP DQ Leaking Attack#
首先需要知道,
他们对加密过程没有影响.下面给出解释
对于解密过程我们有
我们设
由欧拉定理
所以我们设
再由加纳公式(
即可得到明文
python代码:
def RSA_dpdq(p, q, c, dp, dq):
# 原来计算的是pow(c,d,n )
# 相比于pow(c, d >> dp, n >> p)
m1 = pow(c, dp, p)
m2 = pow(c, dq, q)
qinv = inverse(q, p)
h = (qinv * (m1 - m2)) % p
m = m2 + h * q
print(long_to_bytes(m))
Coppersmith 相关攻击
基本原理#
Coppersmith 相关攻击与Don Coppersmith 紧密相关,他提出了一种针对于模多项式(单变量,二元变量,甚至多元变量)找所有小整数根的多项式时间的方法。
这里我们以单变量为主进行介绍,假设
- 模数为 N ,N 具有一个因子
- 多项式 F 的次数为
那么该方法可以在
在这个问题中,我们的目标是找到在模 N 意义下多项式所有的根,这一问题被认为是复杂的。Coppersmith method 主要是通过 Lenstra–Lenstra–Lovász lattice basis reduction algorithm(LLL)方法找到
- 与该多项式具有相同根
- 更小系数
- 定义域为整数域
的多项式 g,由于在整数域上找多项式的根是简单的(Berlekamp–Zassenhaus),从而我们就得到了原多项式在模意义下的整数根。
那么问题的关键就是如何将 f 转换到 g 呢?Howgrave-Graham 给出了一种思路
也就是说我们需要找到一个具有“更小系数”的多项式 g,也就是下面的转换方式
在 LLL 算法中,有两点是非常有用的
- 只对原来的基向量进行整数线性变换,这可以使得我们在得到 g 时,仍然以原来的
为根。 - 生成的新的基向量的模长是有界的,这可以使得我们利用 Howgrave-Graham 定理。
在这样的基础之上,我们再构造出多项式族 g 就可以了。
需要注意的是,由于 Coppersmith 根的约束,在 RSA 中的应用时,往往只适用于 e 较小的情况。
Coppersmith攻击(已知p的高位攻击)#
知道
#Sage
from sage.all import *
n =
p4 =
#p去0的剩余位
e =
pbits = 1024
kbits = pbits - p4.nbits()
print(p4.nbits())
p4 = p4 << kbits
PR.<x> = PolynomialRing(Zmod(n))
f = x + p4
roots = f.small_roots(X=2^kbits, beta=0.4)
#经过以上一些函数处理后,n和p已经被转化为10进制
if roots:
p = p4+int(roots[0])
print("n: "+str(n))
print("p: "+str(p))
print("q: "+str(n//p))
参考 http://ohroot.com/2016/07/11/rsa-in-ctf。
题目
- 2017 WHCTF OldDriver
- 2018 N1CTF easy_fs
Coppersmith攻击(已知m的高位攻击)#
这里我们假设我们首先加密了消息 m,如下
并且我们假设我们知道消息
可以参考 https://github.com/mimoo/RSA-and-LLL-attacks 。
$e
当
#Sage
n =
e =
c =
mbar =
kbits =
beta = 1
nbits = n.nbits()
print("upper {} bits of {} bits is given".format(nbits - kbits, nbits))
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
x0 = f.small_roots(X=2^kbits, beta=1)[0] # find root < 2^kbits with factor = n
print("m:", mbar + x0)
Broadcast Attack with Linear Padding#
对于具有线性填充的情况下,仍然可以攻击,这时候就会使用 Coppersmith method 的方法了,这里暂不介绍。可以参考
相关消息#
攻击条件
当 Alice 使用同一公钥对两个具有某种线性关系的消息 M1 与 M2 进行加密,并将加密后的消息 C1,C2 发送给了 Bob 时,我们就可能可以获得对应的消息 M1 与 M2。这里我们假设模数为 N,两者之间的线性关系如下
其中 f 为一个线性函数,比如说
在具有较小错误概率下的情况下,其复杂度为
这一攻击由 Franklin,Reiter 提出。
攻击原理
首先,我们知道
这里我们关注一下
那么我们有
我们需要明确一下我们想要得到的是消息 m,所以需要将其单独构造出来。
首先,我们有式 1
再者我们构造如下式 2
根据式 1 我们有
继而我们有式 3
那么我们根据式 2 与式 3 可得
进而我们有
进而
进而
上面的式子中右边所有的内容都是已知的内容,所以我们可以直接获取对应的消息。
有兴趣的可以进一步阅读 A New Related Message Attack on RSA 以及 paper 这里暂不做过多的讲解。
SCTF RSA3 中的 level3
#脚本1
import gmpy2
id1 = 1002
id2 = 2614
c1 =
c2 =
n =
a = 1
b = id1 - id2
def getmessage(a, b, c1, c2, n):
b3 = gmpy2.powmod(b, 3, n)
part1 = b * (c1 + 2 * c2 - b3) % n
part2 = a * (c1 - c2 + 2 * b3) % n
part2 = gmpy2.invert(part2, n)
return part1 * part2 % n
message = getmessage(a, b, c1, c2, n) - id2
message = hex(message)[2:]
if len(message) % 2 != 0:
message = '0' + message
print (message.decode('hex'))
#脚本2
#sage
import binascii
def attack(c1, c2, b, e, n):
PR.<x>=PolynomialRing(Zmod(n))
g1 = x^e - c1
g2 = (x+b)^e - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
return -gcd(g1, g2)[0]
c1 =
c2 =
n =
e=3
a = 1
id1 = 1002
id2 = 2614
b = id2 - id1
m1 = attack(c1,c2, b,e,n)
print (binascii.unhexlify("%x" % int(m1 - id1)))
Coppersmith’s short-pad attack#
攻击条件
目前在大部分消息加密之前都会进行 padding,但是如果 padding 的长度过短,也有可能被很容易地攻击。
这里所谓 padding 过短,其实就是对应的多项式的根会过小。
攻击原理
我们假设爱丽丝要给鲍勃发送消息,首先爱丽丝对要加密的消息 M 进行随机 padding,然后加密得到密文 C1,发送给鲍勃。这时,中间人皮特截获了密文。一段时间后,爱丽丝没有收到鲍勃的回复,再次对要加密的消息 M 进行随机 padding,然后加密得到密文 C2,发送给 Bob。皮特再一次截获。这时,皮特就可能可以利用如下原理解密。
这里我们假设模数 N 的长度为 k,并且 padding 的长度为
消息 M2 的 padding 方式类似。
那么我们可以利用如下的方式来解密。
首先定义
其中
#脚本1
#Sage
import binascii
def attack(c1, c2, n, e):
PR.<x>=PolynomialRing(Zmod(n))
# replace a,b,c,d
g1 = (a*x+b)^e - c1
g2 = (c*x+d)^e - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
return -gcd(g1, g2)[0]
c1 =
c2 =
n =
e =
m1 = attack(c1, c2, n, e)
print(binascii.unhexlify("%x" % int(m1)))
#脚本2
#Sage
def short_pad_attack(c1, c2, e, n):
PRxy.<x,y> = PolynomialRing(Zmod(n))
PRx.<xn> = PolynomialRing(Zmod(n))
PRZZ.<xz,yz> = PolynomialRing(Zmod(n))
g1 = x^e - c1
g2 = (x+y)^e - c2
q1 = g1.change_ring(PRZZ)
q2 = g2.change_ring(PRZZ)
h = q2.resultant(q1)
h = h.univariate_polynomial()
h = h.change_ring(PRx).subs(y=xn)
h = h.monic()
kbits = n.nbits()//(2*e*e)
diff = h.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
return diff
def related_message_attack(c1, c2, diff, e, n):
PRx.<x> = PolynomialRing(Zmod(n))
g1 = x^e - c1
g2 = (x+diff)^e - c2
def gcd(g1, g2):
while g2:
g1, g2 = g2, g1 % g2
return g1.monic()
return -gcd(g1, g2)[0]
if __name__ == '__main__':
n =
e =
c1 =
c2 =
diff = short_pad_attack(c1, c2, e, n)
print("difference of two messages is %d" % diff)
m1 = related_message_attack(c1, c2, diff, e, n)
print("m1:", m1)
print("m2:", m1 + diff)
- 变种1:
用CRT计算系数 使得 ,
则可建立多项式为 ,符合 ,
故 是 的一个根,其中 。
如果 ,可以使用coppersmith找出 。
参考:
Security Fest 2022 - really_sick_æsthetic
PlaidCTF 2017 - Multicast
CakeCTF 2021 - Party Ticket
已知m高位#
攻击条件
这里我们假设我们首先加密了消息 m,如下
并且我们假设我们知道消息 m 的很大的一部分
可以参考 https://github.com/mimoo/RSA-and-LLL-attacks 。
当
这里给出了
这个方程的根很小,可以直接求解。
#脚本1
#Sage
n =
e =
c =
mbar =
kbits =
beta = 1
nbits = n.nbits()
print("upper {} bits of {} bits is given".format(nbits - kbits, nbits))
PR.<x> = PolynomialRing(Zmod(n))
f = (mbar + x)^e - c
x0 = f.small_roots(X=2^kbits, beta=1)[0] # find root < 2^kbits with factor = n
print("m:", mbar + x0)
#脚本2
#sage
def phase2(high m,n, c):
R.<x> = PolynomialRing(Zmod(n),implementation='NTL')
m = high m + x
M = m((m^3 - c).small roots()[])
print(hex(int(M))T2:1)
n =
c =
high_m =
phase2(high_m, n, c)
已知p高位#
攻击条件
当我们知道一个公钥中模数 N 的一个因子的较高位时,我们就有一定几率来分解 N。
题目给出p的高位,该后门算法依赖于Coppersmith partial information attack算法, sage实现该算法
在模
得到
#脚本1
def phase3(high_p, n, c):
R.<x> = PolynomialRing(Zmod(n), implementation='NTL')
p = high_p + x
x0 = p.small_roots(X = 2^128, beta = 0.1)[0]
P = int(p(x0))
Q = n // P
assert n == P*Q
d = inverse_mod(65537, (P-1)*(Q-1))
print(hex(power_mod(c, d, n)))
n =
c =
high_p =
phase3(high_p, n, c)
#脚本2
#Sage
from sage.all import *
n =
p4 =
#p去0的剩余位
e =
pbits = 1024
kbits = pbits - p4.nbits()
print(p4.nbits())
p4 = p4 << kbits
PR.<x> = PolynomialRing(Zmod(n))
f = x + p4
roots = f.small_roots(X=2^kbits, beta=0.4)
#经过以上一些函数处理后,n和p已经被转化为10进制
if roots:
p = p4+int(roots[0])
print("n: "+str(n))
print("p: "+str(p))
print("q: "+str(n//p))
已知d低位#
既然已知
两边对
以
这个方程是模意义下的一元二次方程,是可解的。解出来之后得到了
#脚本1
def getFullP(low_p, n):
R.<x> = PolynomialRing(Zmod(n), implementation='NTL')
p = x*2^512 + low_p
root = (p-n).monic().small_roots(X = 2^128, beta = 0.4)
if root:
return p(root[0])
return None
def phase4(low_d, n, c):
maybe_p = []
for k in range(1, 4):
p = var('p')
p0 = solve_mod([3*p*low_d == p + k*(n*p - p^2 - n + p)], 2^512)
maybe_p += [int(x[0]) for x in p0]
print(maybe_p)
for x in maybe_p:
P = getFullP(x, n)
if P: break
P = int(P)
Q = n // P
assert P*Q == n
d = inverse_mod(3, (P-1)*(Q-1))
print(hex(power_mod(c, d, n))[2:])
n =
c =
low_d =
phase4(low_d, n, c)
#脚本2
#Sage
def partial_p(p0, kbits, n):
PR.<x> = PolynomialRing(Zmod(n))
nbits = n.nbits()
f = 2^kbits*x + p0
f = f.monic()
roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.4) # find root < 2^(nbits//2-kbits) with factor >= n^0.4
if roots:
x0 = roots[0]
p = gcd(2^kbits*x0 + p0, n)
return ZZ(p)
def find_p(d0, kbits, e, n):
X = var('X')
for k in range(1, e+1):
results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
for x in results:
p0 = ZZ(x[0])
p = partial_p(p0, kbits, n)
if p and p != 1:
return p
if __name__ == '__main__':
n =
e =
c =
d0 =
beta = 0.5
nbits = n.nbits()
kbits = d0.nbits()
print("lower %d bits (of %d bits) is given" % (kbits, nbits))
p = int(find_p(d0, kbits, e, n))
print("found p: %d" % p)
q = n//int(p)
print("d:", inverse_mod(e, (p-1)*(q-1)))
- 变种1:
,已知
联立可得,
即求解同余方程可得
参考: Dragon CTF 2019 - RSA Chained
#Sage
def find_p(d0, kbits, e, n, p):
X = var('X')
for k in range(1, e + 1):
k_dot = k * (p - 1)
results = solve_mod([e * d0 * X - k_dot * X * (n - X + 1) + k_dot * n == X], 2^kbits)
for x in results:
q = ZZ(x[0])
if n % q == 0:
return q
return None
n = # q * r
p =
c =
d0 =
e =
kbits = d0.nbits()
q = find_p(d0, kbits, e, n, p)
phi = (p - 1) * (q - 1) * (n // q - 1)
d = inverse_mod(e, phi)
print(bytes.fromhex(hex(pow(c, d, p * n))[2:]))
已知N一个因子的高位,部分p#
当我们知道一个公钥中模数 $N
参考 https://github.com/mimoo/RSA-and-LLL-attacks 。
beta = 0.5
dd = f.degree()
epsilon = beta / 7
mm = ceil(beta**2 / (dd * epsilon))
tt = floor(dd * mm * ((1/beta) - 1))
XX = ceil(N**((beta**2/dd) - epsilon)) + 1000000000000000000000000000000000
roots = coppersmith_howgrave_univariate(f, N, beta, mm, tt, XX)
其中,
- 必须满足
,所以这里给出了 beta ,显然两个因数中必然有一 个是大于的。 是 在模 意义下的根的上界,自然我们可以选择调整它, 这里其实也表明了我们已知的 与因数 之间可能的差距
#Sage
n =
e =
c =
pbar =
kbits =
print("upper %d bits (of %d bits) is given" % (pbar.nbits()-kbits, pbar.nbits()))
PR.<x> = PolynomialRing(Zmod(n))
f = x + pbar
x0 = f.small_roots(X=2^kbits, beta=0.4)[0] # find root < 2^kbits with factor >= n^0.4
p = x0 + pbar
print("p:", p)
q = n // int(p)
d = inverse_mod(e, (p-1)*(q-1))
print("m:", pow(c, d, n))
Boneh and Durfee attack#
攻击条件
攻击原理
首先
进而有
即
又
所以
假设
其中
如果我们求得了该二元方程的根,那么我们自然也就可以解一元二次方程
更加具体的推导,参考 New Results on the Cryptanalysis of Low Exponent RSA.
攻击工具
请参考 https://github.com/mimoo/RSA-and-LLL-attacks 。上面有使用教程。
2015 PlaidCTF Curious
首先题目给了N,e,c。简单看一下可以发现 e 比较大。考虑使用 Wiener's Attack,使用更强的目前介绍的攻击。
代码如下:
nlist = list()
elist = list()
clist = list()
with open('captured') as f:
# read the line {N : e : c} and do nothing with it
f.readline()
for i in f.readlines():
(N, e, c) = i[1:-2].split(" : ")
nlist.append(long(N,16))
elist.append(long(e,16))
clist.append(long(c,16))
for i in range(len(nlist)):
print 'index i'
n = nlist[i]
e = elist[i]
c = clist[i]
d = solve(n,e)
if d==0:
continue
else:
m = power_mod(c, d, n)
hex_string = "%x" % m
import binascii
print "the plaintext:", binascii.unhexlify(hex_string)
return
结果如下
=== solution found ===
private key found: 23974584842546960047080386914966001070087596246662608796022581200084145416583
the plaintext: flag_S0Y0UKN0WW13N3R$4TT4CK!
-
变种1:
很大, 很小,且 。May’s Attack
假设$ e<\varphi(N),q \le N^{\beta}
\beta \le \frac{1}{2}$,因 ,有 ,对于$ k \in \mathbb{N}
ed_p=(k-1)(p-1)+p ed_pq=(k-1)(N-q)+N$。设$ x,y
f(x,y)=x(N-y)+N (x_0,y_0)=(k-1,q)$,用coppersmith attack可解。
RSA Hastad Attack with non-linear padding and different public keys(带非线性padding和不同公钥的广播攻击)#
适用情况:m 经 k 次非线性padding处理后,分别用 k 组 (N_i,e_i) 加密后得 k 组 c_i。
参考:2020年羊城杯 - Invitation
#Sage
#e=3, padding: m²+(3^431)k
def linearPaddingHastads(cArray,nArray,aArray,bArray,eArray,eps):
if(len(cArray) == len(nArray) == len(aArray) == len(bArray) == len(eArray)):
for i in range(4):
cArray[i] = Integer(cArray[i])
nArray[i] = Integer(nArray[i])
aArray[i] = Integer(aArray[i])
bArray[i] = Integer(bArray[i])
eArray[i] = Integer(eArray[i])
TArray = [-1]*4
for i in range(4):
arrayToCRT = [0]*4
arrayToCRT[i] = 1
TArray[i] = crt(arrayToCRT,nArray)
P.<x> = PolynomialRing(Zmod(prod(nArray)))
gArray = [-1]*4
for i in range(4):
gArray[i] = TArray[i]*(pow(aArray[i]*x**2 + bArray[i],eArray[i]) - cArray[i])
g = sum(gArray)
g = g.monic()
roots = g.small_roots(epsilon=eps)
if(len(roots)== 0):
print("No Solutions found!")
return -1
return roots
else:
print("Input error!")
def nonLinearPadding():
eArr = [3 for i in range(4)]
nArr = []
cArr = []
aArr = [1 for i in range(4)]
bArr = [i * 3 ** 431 for i in [3,8,10,11]]
msg = linearPaddingHastads(cArr,nArr,aArr,bArr,eArr,eps=1/20)
for i in msg:
print(bytes.fromhex(hex(i)[2:]))
if __name__ == '__main__':
nonLinearPadding()
RSA 选择明密文攻击
选择明文攻击#
这里给出一个例子,假如我们有一个加密 oracle,但是我们不知道
- 我们可以通过加密 oracle 获取
。 - 在
比较小 时,我们可以利用 Pollard's kangaroo algorithm 算法获取 。这一点比较显然。
我们可以加密 2,4,8,16。那么我们可以知道
那么
故而
我们可以求出
import gmpy2
def get_n():
nset = []
c2 = server_encode(2)
c4 = server_encode(4)
c8 = server_encode(8)
nset.append(c2 * c2 - c4)
nset.append(c2 * c2 * c2 - c8)
c3 = server_encode(3)
c9 = server_encode(9)
c27 = server_encode(27)
nset.append(c3 * c3 - c9)
nset.append(c3 * c3 * c3 - c27)
c5 = server_encode(5)
c25 = server_encode(25)
c125 = server_encode(125)
nset.append(c5 * c5 - c25)
nset.append(c5 * c5 * c5 - c125)
n = nset[0]
for x in nset:
n = gmpy2.gcd(x, n)
while n % 2 == 0:
n //= 2
while n % 3 == 0:
n //= 3
while n % 5 == 0:
n //= 5
print('n =', n)
return n
任意密文解密#
假设爱丽丝创建了密文
- 选择任意的
,即 与 互素 - 计算
- 由于我们可以进行选择密文攻击,那么我们求得
对应的解密结果 - 那么,由于
,由于 与 互素,我们很容易求得相应的逆 元,进而可以得到
from Crypto.Util.number import *
def get_M():
X = getPrime(5)
Y = (c * (X ** e)) % n
Z = server_decode(Y)
i = 0
while True:
M = (n * i + Z) // X
if 'flag' in long_to_bytes(M):
print(long_to_bytes(M))
break
RSA parity oracle#
LSB Oracle Attack(Least Significant Bit Oracle Attack )#
假设目前存在一个 Oracle,它会对一个给定的密文进行解密,并且会检查解密的明文的奇偶性, 并根据奇偶性返回相应的值,比如 1 表示奇数, 0 表示偶数。那么给定一个加密后的密文,我们只 需要
原理
假设
第一次时,我们可以给服务器发送
服务器会计算得到
这里
-
是偶数,它的幂次也是偶数。 -
是奇数,因为它是由两个大素数相乘得到。
那么 -
服务器返回奇数,即
为奇数,则说明 大于 ,且减去了奇数个 ,又因为 ,因此减去了一个 ,即 ,我们还可以考虑向下取整。 -
服务器返回偶数,则说明
小于 。即 ,我们还可以向下取整。
这里我们使用数学归纳法,即假设在第 次时,
进一步,在第 次时,我们可以发送
服务器会计算得到
根据第
那么
- 服务㗊返回奇数,则
必然是一个奇数, ,那么 。与此同时,由于 必然存 在,所以第 得到的这个范围和第 次得到的范围必然存在交集。所以 必然与 相等。 - 服务器返回偶数,则
必然是一个偶数, ,此时 必然也与 相等,那么
进一步我们可以这么归纳
lb = 0
ub = N
if server returns 1
lb = (lb+ub)/2
else:
ub = (lb+ub)/2
这里虽然是整除, 即下取整,但是无所谓我们在最初时已经分析了这个问题。
由于此处有大量整除运算,所以最好用 decimal
库进行精确计算,否则最后结果很可能会出错。decimal.getcontext().prec
用来设定精度。
from Crypto.Util.number import *
import decimal
def get_flag():
k = n.bit_length()
decimal.getcontext().prec = k
L = decimal.Decimal(0)
R = decimal.Decimal(int(n))
for i in range(k):
c = (c * pow(2, e, n)) % n
recv = server_decode(c)
if recv == 1:
L = (L + R) // 2
else:
R = (L + R) // 2
print(long_to_bytes(int((R))))
更多信息可参考:RSA Least-Significant-Bit Oracle Attack 和 RSA least significant bit oracle attack 。
import decimal
def oracle():
return lsb == 'odd'
def partial(c, e, n):
k = n.bit_length()
decimal.getcontext().prec = k # for 'precise enough' floats
lo = decimal.Decimal(0)
hi = decimal.Decimal(n)
for i in range(k):
if not oracle(c):
hi = (lo + hi) // 2
else:
lo = (lo + hi) // 2
c = (c * pow(2, e, n)) % n
# print i, int(hi - lo)
return int(hi)
MSB Oracle Attack(Most Significant Bit Oracle Attack )#
适用情况:可以选择密文并泄露明文的最高位 (奇偶性)。
假设远程提供一个解密服务,但是只返回明文的最高字节,并且明文的形式是64字 节,高位用
将加密的内容拿去解密会得到
不断构造密文
然后再相应缩小
最終可以找到一个
参考: Pwnhub - pkcs4
RSA Byte Oracle#
假设目前存在一个 Oracle,它会对一个给定的密文进行解密,并且会给出明文的最后一个字节。那么给定一个加密 后的密文,我们只需要
原理
这个其实算作 RSA parity Oracle 的扩展,既然可以泄露出最后一个字节,那么按道理我们获取密文对应明文的次数 应该可以咸少。
假设
第一次时,我们可以给服务器发送
服务器会计算得到
这里
-
是偶数。 -
是奇数,因为它是由两个大拜数相乘得到。
由于
我们可以利用反证法来证明上述不等式。同时
当服务器返回最后一个字节
此后,我们使用数学归纳法来获取
进一步,在第
服务器会计算得到
根据第
我们这里可以假设
与此同时,由于
所以
假设服务器返回了 b,那么可以归纳为:
L = 0
R = 1
for i in range(128):
k = mab[b]
L = L + k // 256**(i+1)
R = L + (k+1)// 256**(i+1)
M = L * n
或
k = mab[b]
interval = (ub-lb)/256
lb = lb + interval * k
ub = lb + interval
如果不知道
由于有大量除法运算,为保证精度,将中间过程用 Fraction 库保存为分数。
最后一字节的数据不准确要减掉,从服务器返回精确的最后一字节数据。
其实只要求出
from Crypto.Util.number import *
from fractions import Fraction
def get_flag():
map = {}
for i in range(0, 256):
map[-n * i % 256] = i
cipher256 = server_encode(256)
backup = c
L = Fraction(0, 1)
R = Fraction(1, 1)
for i in range(128):
c = c * cipher256 % n
b = server_decode(c)
k = map[b]
L, R = L + Fraction(k, 256**(i+1)), L + Fraction(k+1, 256**(i+1))
m = int(L * n)
print(long_to_bytes(m - m % 256 + server_decode(backup)))
2018 HITCON lost key
from pwn import *
import gmpy2
from fractions import Fraction
p = process('./rsa.py')
#p = remote('18.179.251.168', 21700)
#context.log_level = 'debug'
p.recvuntil('Here is the flag!\n')
flagcipher = int(p.recvuntil('\n', drop=True), 16)
def long_to_hex(n):
s = hex(n)[2:].rstrip('L')
if len(s) % 2: s = '0' + s
return s
def send(ch, num):
p.sendlineafter('cmd: ', ch)
p.sendlineafter('input: ', long_to_hex(num))
data = p.recvuntil('\n')
return int(data, 16)
if __name__ == "__main__":
# get n
cipher2 = send('A', 2)
cipher4 = send('A', 4)
nset = []
nset.append(cipher2 * cipher2 - cipher4)
cipher3 = send('A', 3)
cipher9 = send('A', 9)
nset.append(cipher3 * cipher3 - cipher9)
cipher5 = send('A', 5)
cipher25 = send('A', 25)
nset.append(cipher5 * cipher5 - cipher25)
n = nset[0]
for item in nset:
n = gmpy2.gcd(item, n)
# get map between k and return byte
submap = {}
for i in range(0, 256):
submap[-n * i % 256] = i
# get cipher256
cipher256 = send('A', 256)
back = flagcipher
L = Fraction(0, 1)
R = Fraction(1, 1)
for i in range(128):
print i
flagcipher = flagcipher * cipher256 % n
b = send('B', flagcipher)
k = submap[b]
L, R = L + (R - L) * Fraction(k, 256), L + (R - L) * Fraction(k + 1, 256)
low = int(L * n)
print (long_to_hex(low - low % 256 + send('B', back)).decode('hex'))
RSA parity oracle variant#
原理
如果 oracle 的参数会在一定时间、运行周期后改变,或者网络不稳定导致会话断开、重置,二分 法就不再适用了,为了减少错误,应当考虑逐位恢复。要恢复明文的第 2 低位,考虑
类似的
我们就可以使用前
其中
就可以逐步恢复原文所有的位信息了。这样的时间复杂度为
exp:
from Crypto.Util.number import *
mm = bytes_to_long(b'12345678')
l = len(bin(mm)) - 2
def genkey():
while 1:
p = getPrime(128)
q = getPrime(128)
e = getPrime(32)
n = p * q
phi = (p - 1) * (q - 1)
if GCD(e, phi) > 1:
continue
d = inverse(e, phi)
return e, d, n
e, d, n = genkey()
cc = pow(mm, e, n)
f = str(pow(cc, d, n) % 2)
for i in range(1, l):
e, d, n = genkey()
cc = pow(mm, e, n)
ss = inverse(2**i, n)
cs = (cc * pow(ss, e, n)) % n
lb = pow(cs, d, n) % 2
bb = (lb - (int(f, 2) * ss % n)) % 2
f = str(bb) + f
assert(((mm >> i) % 2) == bb)
print(long_to_bytes(int(f, 2)))
参考#
- https://crypto.stackexchange.com/questions/11053/rsa-least-significant-bit-oracle-attack
- https://pastebin.com/KnEUSMxp
- https://github.com/ashutosh1206/Crypton
Common Private Exponent(共私钥指数攻击,d相同)
加密用同样的私钥并且私钥比较短,从而导致了加密系统被破解。
假定:
其中,
构造格:
其中
再利用LLL算法进行规约得到
文。
- 使用条件:
- 参考:
Lattice Based Attack on Common Private Exponent RSA
SCTF 2020 - RSA
#Sage
from gmpy2 import *
e0=
n0=
c0=
e1=
n1=
c1=
e2=
n2=
c2=
M=iroot(int(n2),int(2))[0]
a=[0]*4
a[0]=[M,e0,e1,e2]
a[1]=[0,-n0,0,0]
a[2]=[0,0,-n1,0]
a[3]=[0,0,0,-n2]
Mat = matrix(ZZ,a)
Mat_LLL=Mat.LLL()
d = abs(Mat_LLL[0][0])/M
print(bytes.fromhex(hex(pow(c1,int(d),int(n1)))[2:]))
多组低解密指数攻击#
适用情况:2-4组
- 给定2组
且有
设
上述等式组也可表示为
对部分参数进行上界估计,
因此只要满足
from sage.all import *
import gmpy2
N =
e1 =
e2 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**0.5)
M2 = int( gmpy2.mpz(N)**(1+alpha2) )
D = diagonal_matrix(ZZ, [N, M1, M2, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2],
[0, e1, -e1, -e1*N],
[0, 0, e2, -e2*N],
[0, 0, 0, e1*e2] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi = (x[0,1]/x[0,0]*e1).floor()
try:
d = inverse_mod( 65537, phi)
m = hex(power_mod(c, d, N))[2:]
if m.startswith('44415343'):
print(i)
print(bytes.fromhex(m))
break
except:
pass
- 给定3组
类似2组情况,其中
其中
from sage.all import *
import gmpy2
N =
e1 =
e2 =
e3 =
c =
for i in range(1000):
alpha2 = i/1000
M1 = int(gmpy2.mpz(N)**(3./2))
M2 = int( gmpy2.mpz(N) )
M3 = int(gmpy2.mpz(N)**(3./2 + alpha2))
M4 = int( gmpy2.mpz(N)**(0.5) )
M5 = int( gmpy2.mpz(N)**(3./2 + alpha2) )
M6 = int( gmpy2.mpz(N)**(1.+alpha2) )
M7 = int( gmpy2.mpz(N)**(1.+alpha2) )
D = diagonal_matrix(ZZ, [M1, M2, M3, M4, M5, M6, M7, 1])
B = Matrix(ZZ, [ [1, -N, 0, N**2, 0, 0, 0, -N**3],
[0, e1, -e1, -e1*N, -e1, 0, e1*N, e1*N**2],
[0, 0, e2, -e2*N, 0, e2*N, 0, e2*N**2],
[0, 0, 0, e1*e2, 0, -e1*e2, -e1*e2, -e1*e2*N],
[0, 0, 0, 0, e3, -e3*N, -e3*N, e3*N**2],
[0, 0, 0, 0, 0, e1*e3, 0, -e1*e3*N],
[0, 0, 0, 0, 0, 0, e2*e3, -e2*e3*N],
[0, 0, 0, 0, 0, 0, 0, e1*e2*e3] ]) * D
L = B.LLL()
v = Matrix(ZZ, L[0])
x = v * B**(-1)
phi_ = (e1*x[0,1]/x[0,0]).floor()
try:
d = inverse_mod( 65537, phi_)
m = hex(power_mod(c, d, N))[2:]
if m.startswith('44415343'):
print(i)
print(bytes.fromhex(m))
break
except:
pass
-
给定更多组
-
参考Paper
Common Modulus Attacks on Small Private Exponent RSA and Some Fast Variants (in Practice)
Extending Wiener’s Attack in the Presence of Many Decrypting Exponents
多项式RSA#
在整数RSA原理基础上将多项式代入分析:
在有限域上选取两个不可约多项式
选取一个整数
计算私钥
同样考虑
得到
显然RSA对于整数的体制可以适用于有限域上的多项式。
太注意:
对于素数
原因:
由欧拉函数定义本身,欧拉函数是小于
多项式的欧拉函数则类似,表示不高于
#脚本1
#Sage
#已知p,n,m^e
p=
P = PolynomialRing(Zmod(p), name = 'x')
x = P.gen()
e =
n =
c =
#分解N
q1, q2 = n.factor()
q1, q2 = q1[0], q2[0]
#求φ,注意求法,
phi = (p**q1.degree() - 1) * (p**q2.degree() - 1)
assert gcd(e, phi) == 1
d = inverse_mod(e, phi)
m = pow(c,d,n)
#取多项式系数
flag = bytes(m.coefficients())
print("Flag: ", flag.decode())
#脚本2
#Sage
#已知p=2,n,e,c
p =
P = PolynomialRing(GF(p), name = 'x')
x = P.gen()
e =
n =
R.<a> = GF(2^2049)
c = []
q1, q2 = n.factor()
q1, q2 = q1[0], q2[0]
phi = (p**q1.degree() - 1) * (p**q2.degree() - 1)
assert gcd(e, phi) == 1
d = inverse_mod(e, phi)
ans = ''
for cc in c:
cc = P(R.fetch_int(cc))
m = pow(cc,d,n)
m = R(P(m)).integer_representation()
print(m)
ans += chr(m)
print(ans)
#Sage
#x.nbits()==2^32
poly = sum(e * x^i for i,e in enumerate(Integer(n).digits(2^32)))
(p, _), (q, _) = poly.factor_list()
p, q = p(x=2^32), q(x=2^32)
参考:
Crypto CTF2020 - Decent RSA
SecurityFest CTF 2022 - small rsa
Weak prime factors ( 具线性特征)#
适用情况:
先根据
用LLL算法进行格基规约,将规约后的某个向量作为多项式系数,再对多项式进行 分解,即可完成对
- 参考
Factoring RSA moduli with weak prime factors
N1CTF2020 - easyRSA
from tqdm import tqdm
import gmpy2
class success(Exception):
pass
def attack_weak_prime(basenum, exp, n):
m = basenum^exp
k = len(n.str(base=basenum))//(2*exp) + 1
c = gmpy2.iroot(2*k^3, int(2))
# assert c[1] == True
tmp = int(c[0])
try:
for c in tqdm(range(1, tmp)):
amount = 2*k+1
M = Matrix(RationalField(), amount, amount)
for i in range(amount):
M[i, i] = 1
M[i, amount-1] = c*m^(2*k-i)
M[amount-1, amount-1] = -c*n
new_basis = M.LLL(delta=0.75)
for j in range(amount):
last_row = list(new_basis[j])
last_row[-1] = last_row[-1]//(-c)
poly = sum(e * x^(k*2-i) for i,e in enumerate(last_row))
fac = poly.factor_list()
if len(fac) == 2:
p_poly, q_poly = fac
p_coefficient = p_poly[0].list()
q_coefficient = q_poly[0].list()
ap = sum(m^i * j for i,j in enumerate(p_coefficient))
bq = sum(m^i * j for i,j in enumerate(q_coefficient))
p = gcd(ap, n)
q = gcd(bq, n)
if (p*q == n) and (p != 1) and (q != 1):
raise success
except:
print ('n =', n)
print ('p =', p)
print ('q =', q)
print ('p*q == n ?', bool(p*q == n))
if __name__ == '__main__':
print ('[+] Weak Prime Factorization Start!')
print ('-------------------------------------------------------------------------------------------------------------------------------')
basenum, exp = (3, 66)
n =
p多次幂因子#
适用情况:
- 情形1
条件: 满足 ,其中 与 为小参数。
计算 , 则
P.<x> = PolynomialRing(Zmod(n))
f = e * x - b
root = f.monic().small_roots(X=2**672,beta=0.75)[0]
g = gcd(int(e * root - b),n3)
- 情形2
条件: 小
等价于 ,其中 。
计算 ,则
P.<x> = PolynomialRing(Zmod(n))
f = e1*e2*x - e1 + e2
root = f.monic().small_roots(X=2**672,beta=0.75)[0]
g = gcd(int(e1*e2*root - e1 + e2),n)
情形3
条件:
利用
cf = continued_fraction(n1/n2)
fracs = cf.convergents()
for xx in tqdm(fracs):
q1 = xx.numerator()
q2 = xx.denominator()
if q1.nbits() in range(511, 513) and q2.nbits() in range(511, 513):
if n1 % q1 == 0:
print(q1)
assert n1 % q1 == 0
p1 = int((n1 // q1)^(1/2))
p2 = int((n2 // q2)^(1/2))
assert p1^2 * q1 == n1
break
参考:
New attacks on RSA with Moduli
D^3CTF 2022 - d3factor
RSA-CRT#
- 错误模攻击
。
利用错淏模注入技术得到错淏签名 ,即 ,
对生成的两种签名 和 使用CRT可计算出 。
针对 的编码后消息进行分析,通过计算签名对 分解 的攻击方法:
- 对所有的
,计算出对应的整数 ,这些对应的 构成 上的向量 ; - 利用LLL定理计算出垂直于向量
的正交格 的规约基 ,其中所有的向量和格的分布都是在 内。通过对存在于 中的格应用LLL定理,即对如 下矩阵使用LLLL定理:
其中
3. 前
同步㵵2,保留计算出来的向量的最后
4. 将所有长度不超过
from tqdm import tqdm
import gmpy2,sys
def orthogonal_lattice(B):
LB = B.transpose().left_kernel(basis="LLL").basis_matrix()
return LB
cs = []
s = []
l = 6
v = []
for i in range(len(cs_)):
v.append(int(crt([s_[i], cs_[i]], [n, N])))
v = vector(ZZ, v)
Lv = orthogonal_lattice(Matrix(v))
L1 = orthogonal_lattice(Lv.submatrix(0, 0, l-2, l))
x, y = L1
for a in tqdm(range(333)):
for b in tqdm(range(333)):
z = a*x+b*y
for each in (v-z):
tmp = gcd(each,n)
if tmp>1:
p = tmp
print(p)
sys.exit()
参考:
Modulus Fault Attacks Against RSA-CRT Signatures
2022巅峰极客 - Learning with Fault
其他特别情形
- 多素数因子 (Multi-prime RSA)
- next_prime0
根据素数定理,表数的平均间隔为: ,因此常见的下一个素数比当前素数大一点,一般不会超过 1500 。 - 变种1:
费马因式分解。 - 给 e,p,c
-
给
已知: 同比特位数。
令 ,有 。-
,
比较比特位数, 与 同长,可爆破 ,得 ; -
上式
,
结合 ,即 ,
联立: -
由费马小定理,存在
满足 , -
因对于任意
,当 为 因子时, , 故 ,
已知 ,由 可得到多组 的乘积,计算 可得到 ; -
由
求模逆可得 ,再用 计算出 。
-
参考:TSG CTF 2020 - Modulus Amittendus
-
时, 与 不互紘,
则 。
当 较小时,可以直接对 开根,有两种情况:-
,这种情况直接对 开 次方即可; -
,这种情况需要在有限域下对 开方,一般先计算
,分别求出 在 下的 次根 (可能 有多个),然后使用CRT遍历所有组合,分别check得出明文。
当
较大时,求 的 次根步癷需要替换为一些有限域开根的 高效算法 (如AMM算法等) 进行计算。
参考:
De1CTF2019 - Baby RSA
0ctf 2016 - RSA? -
-
e|(p-1), e|(q-1)
上面的 情况不针对 ,这里对 的特殊情况进行讨论。
解题思路即求解 和 ,再通过CRT还原 。主要 难点则是在 上求 次根。
在有限域上求r-th root有两个常见算法 (Adleman-Manders-Miller algorithm 和Cipolla-Lehmer algorithm),Namhun Koo提出一种更具一般性的开根算 法,且在 足够小的时候更高效 。
参考: NCTF 2019 - easyRSA (Adleman-Manders-Miller rth Root Extraction Method)
本题则为 和 (或 ) 的最大公约数就是 本身,也就是说 ,只有对 开 次方根才行。
可以将同余方程 化成
-
然后分别在
和 上对 开 次方根,再用CRT组合一下即可得到 在 下的解。
问题是,如何在有限域内円根?
这里 与 和 都不互嗉,不能简单地求个逆元就完事。
这种情况下,开平方根可以用Tonelli-Shanks algorithm
,Wiki说这个算法可 以扩展到开 次方根。
在这篇paper里给出了具体的算法:Adleman-Manders-Miller $x$ th Root Extraction Method
。
这个算法只能开出一个根,实际上开 次方,最多会有 个根(这题的情况下 有 个根)。
如何找到其他根?
StackOverflow - Cube root modulo P 给出了方法。
如何找到所有的 primitive th root of 1 ?
StackExchange - Finding the n-th root of unity in a finite field 给出了方 法。Exploit (以
为例)- 先用 Adleman-Manders-Miller
th Root Extraction Method 在 和 上对 开 次方根,分别得到一个解。大概不到10秒。 - 然后去找到所有的
个 primitive nth root of 1 ,乘以上面那个解,得到所有的 个解。大概 1 分钟。 - 再用CRT对
和 上的两组 个解组合成 下的解,可以得到 个 的解。最后能通过 check 0 的即为flag。 大概十几分钟。
- 先用 Adleman-Manders-Miller
#脚本1
#Sage
import random
import time
# About 3 seconds to run
def AMM(o, r, q):
start = time.time()
print('\n----------------------------------------------------------------------------------')
print('Start to run Adleman-Manders-Miller Root Extraction Method')
print('Try to find one {:#x}th root of {} modulo {}'.format(r, o, q))
g = GF(q)
o = g(o)
p = g(random.randint(1, q))
while p ^ ((q-1) // r) == 1:
p = g(random.randint(1, q))
print('[+] Find p:{}'.format(p))
t = 0
s = q - 1
while s % r == 0:
t += 1
s = s // r
print('[+] Find s:{}, t:{}'.format(s, t))
k = 1
while (k * s + 1) % r != 0:
k += 1
alp = (k * s + 1) // r
print('[+] Find alp:{}'.format(alp))
a = p ^ (r**(t-1) * s)
b = o ^ (r*alp - 1)
c = p ^ s
h = 1
for i in range(1, t):
d = b ^ (r^(t-1-i))
if d == 1:
j = 0
else:
print('[+] Calculating DLP...')
j = - discrete_log(a, d)
print('[+] Finish DLP...')
b = b * (c^r)^j
h = h * c^j
c = c ^ r
result = o^alp * h
end = time.time()
print("Finished in {} seconds.".format(end - start))
print('Find one solution: {}'.format(result))
return result
def findAllPRoot(p, e):
print("Start to find all the Primitive {:#x}th root of 1 modulo {}.".format(e, p))
start = time.time()
proot = set()
while len(proot) < e:
proot.add(pow(random.randint(2, p-1), (p-1)//e, p))
end = time.time()
print("Finished in {} seconds.".format(end - start))
return proot
def findAllSolutions(mp, proot, cp, p):
print("Start to find all the {:#x}th root of {} modulo {}.".format(e, cp, p))
start = time.time()
all_mp = set()
for root in proot:
mp2 = mp * root % p
assert(pow(mp2, e, p) == cp)
all_mp.add(mp2)
end = time.time()
print("Finished in {} seconds.".format(end - start))
return all_mp
c =
p =
q =
e = 0x1337
cp = c % p
cq = c % q
mp = AMM(cp, e, p)
mq = AMM(cq, e, q)
p_proot = findAllPRoot(p, e)
q_proot = findAllPRoot(q, e)
mps = findAllSolutions(mp, p_proot, cp, p)
mqs = findAllSolutions(mq, q_proot, cq, q)
print(mps, mqs)
def check(m):
h = m.hex()
if len(h) & 1:
return False
if bytes.fromhex(h).startswith(b'NCTF'):
print(bytes.fromhex(h))
return True
else:
return False
# About 16 mins to run 0x1337^2 == 24196561 times CRT
start = time.time()
print('Start CRT...')
for mpp in mps:
for mqq in mqs:
solution = CRT_list([int(mpp), int(mqq)], [p, q])
if check(solution):
print(solution)
print(time.time() - start)
end = time.time()
print("Finished in {} seconds.".format(end - start))
#脚本2
#Sage
c = 346925245648012783854132941104554194717281878370806475831055718275298366664505658836564073456294047402009856656647760
p = 21122913513992623721920275602985463699928507831138027
q = 16471885912035642894544190467774867069446937372970845578732298073
e = 239
P.<a>=PolynomialRing(Zmod(p),implementation='NTL')
f=a^e-c
mps=f.monic().roots()
P.<a>=PolynomialRing(Zmod(q),implementation='NTL')
g=a^e-c
mqs=g.monic().roots()
for mpp in mps:
x=mpp[0]
for mqq in mqs:
y=mqq[0]
solution = hex(CRT_list([int(x), int(y)], [p, q]))[2:]
if solution.startswith('666c'):
print(solution)
-
SMUPE 问题 (不同N,e加密线性关系明文)
a system of univariate polynomial equations problem 一元多项式方程组求解问题-
定义
是一个整数, 为满足RSA算法的模数, 是多项式的阶。 -
多项式方程组表示如下,目的是求解
: -
求解条件
Alexander May, Maike Ritzenhofent提出一种求解方法,简单地说当多项式的阶 满足以下情况时可解( 是多项式的阶): -
具体描述:
令 作为SMUPE问题的首一多项式组,
定义
则SMUPE问题可以在 复杂度解决。
-
-
反溸数 (emirp数)
已知: reverse 为进制数。
爆破思路类似RSA parity oracle。 是bit翻转关系,已知 最低的 位,则已知 最高的 位。 假设已知 位的 ,记为 ,利用不等式
逐位向低地址懪破,不断收缩不等式的范围,最終可求得
参考:
ASIS 2015 Finals: RSASR
Midnight Sun CTF 2020 Quals
RoarCTF 2020 - Reverse
#python2
#x=10
n = 6528060431134312098979986223024580864611046696815854430382374273411300418237131352745191078493977589108885811759425485490763751348287769344905469074809576433677010568815441304709680418296164156409562517530459274464091661561004894449297362571476259873657346997681362092440259333170797190642839587892066761627543
def t(a, b, k):
# sqrt(n) has 155 digits, so we need to figure out 77 digits on each side
if k == 77:
if a*b == n:
print a, b
return
for i in xrange(10):
for j in xrange(10):
# we try to guess the last not-already-guessed digits of both primes
a1 = a + i*(10**k) + j*(10**(154-k))
b1 = b + j*(10**k) + i*(10**(154-k))
if a1*b1 > n:
# a1 and b1 are too large
continue
if (a1+(10**(154-k)))*(b1+(10**(154-k))) < n:
# a1 and b1 are too small
continue
if ((a1*b1)%(10**(k+1))) != (n%(10**(k+1))):
# The last digits of a1*b1 (which won't change later) doesn't match n
continue
# this a1 and b1 seem to be a possible match, try to guess remaining digits
t(a1, b1, k+1)
# the primes have odd number of digits (155), so we try all possible middle digits (it simplifies the code)
for i in xrange(10):
t(i*(10**77), i*(10**77), 0)
method
对使用一类特定䋤数乘积的模数的分解。 会将其视为 RSA 的后门之一,称之为 RSA backdoor 。- QiCheng Prime
import sys
sys.setrecursionlimit(10^6)
def QiCheng(n):
R = Integers(n)
attempts = 20
js = [0, (-2^5)^3, (-2^5*3)^3, (-2^5*3*5)^3, (-2^5*3*5*11)^3, (-2^6*3*5*23*29)^3]
for _ in range(attempts):
for j in js:
if j == 0:
a = R.random_element()
E = EllipticCurve([0, a])
else:
a = R(j)/(R(1728)-R(j))
c = R.random_element()
E = EllipticCurve([3*a*c^2, 2*a*c^3])
x = R.random_element()
z = E.division_polynomial(n, x)
g = gcd(z, n)
if g > 1:
return g
n =
p = int(QiCheng(Integer(n)))
Masaaki Shirase & Vladimir Sedlacek Improvement
更多 Ds 值。
参考:
NCTF 2020 - RSA_revenge
CryptoHack Challenge - RSA Backdoor Viability
-
Common Prime RSA
情形:gcd(p−1,q−1)=ggcd(p−1,q−1)=g
分解的n方法有四种:
(1)修改Pollard’s rho方法分解n;
(2)知道a、b的值分解n;
(3)知道g的值分解n;
(4)分解N-1。
# Pollard’s rho
from Crypto.Util.number import *
import gmpy2
def f(x, n):
return (pow(x, n - 1, n) + 3) % n
def rho(n):
i = 1
print 'Factorizing'
while True:
x1 = getRandomRange(2, n)
x2 = f(x1, n)
j = 1
while True:
p = gmpy2.gcd(abs(x1 - x2), n)
if p == n:
break
elif p > 1 and isPrime(p):
print 'Found!'
return (p, n // p)
else:
x1 = f(x1, n)
x2 = f(f(x2, n), n)
j += 1
i += 1
- Return of Coppersmith' s attack (ROCA)
CVE-2017-15361
形如 生成表数的RSA系统, 是前 个连续表数的乘积, 是仅取决于所需密钥大小的常数。
https://github.com/jvdsn/crypto-attacks/blob/master/attacks/factorization/roca.py
https://github.com/FlorianPicca/ROCA
PEM密钥
由-----BEGIN <TAG>-----
开头,-----END <TAG>-----
结尾,中间是Base64编码的一串二进制,每64个字母(即解码后的48bytes)有一个换行。中间的Base64解码后是一串遵循ASN.1协议的DER编码,简单来说可以看成一种序列化,把一个结构体中的整数、字串等编码成一个方便传输的二进制。
生成代码:
from Crypto.PublicKey import RSA
rsa = RSA.generate(1024)
pk = rsa.publickey().exportKey()
sk = rsa.exportKey()
with open ('./pub.pem', 'wb') as f:
f.write(pk)
with open ('./priv.pem', 'wb') as f:
f.write(sk)
RSA私钥
-----BEGIN RSA PRIVATE KEY-----
...Base64 encoded key...
-----END RSA PRIVATE KEY-----
RFC3447定义:
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
Version ::= INTEGER { two-prime(0), multi(1) }
(CONSTRAINED BY
{-- version must be multi if otherPrimeInfos present --})
例:
3082025d02010002818100a0d154d5bf97c40f7797b44819d09c608fa4b5c38e70d83bc13267138c6eff4c1aacefe3ddb571e1b41d911c7ab6136cf90493189563450e1f4270cabbc4207c54c4da7b84a20311cfbbabe82b9fe60bdf48a08d57839d0cdf9464d84262bcc06bc308095a6987f60ad07d669a312b5a7e4133213788eecf25863248b91349ef02030100010281800f8270c496903bf3e3ec4912450f15edc81cb1fcf4b154615aee11fbd428e64d402b5a8d66d5f770358f3e6df935b324e8d5349c83d7c992a5982249a31734acb1db19c4c8d829267514bc1ef7bbfbe242d4350f67a002a56d33e56d1a94adc71c68f020dc39ab7d0064c111b164e26ba0698dc94a03cdfd516ffd966e877949024100ca97e49c058237f96e99118ce383f91912cba1163de9236181ff754ef3ef1a260fac8d2d9aee866d51a8b6836983b05cf850e786289b6859925bc8695fc67c47024100cb3630aafffcb29607f0833dc7f05c143ee92fadfe975da4cf6719e71226bee72562e8631328a25d7351507a8d43c1295ab6ea242b60a28b109233a983f4211902401b4a32a541a8b4d988a85dd0d8a4e25d1a470bbfef3f0461121dd3337b706dd94aab37a9390180622169d48c071e921733ebd204245c2ac6460ccf0642bc7de90241008d9f44a7c823eaaa58fa2bdd20bcc8cf6b50c463f4acb51ca956e75c7ceff7d7cbdc74aca7ab880cacd39cccec2aae320e00b0896899be6e40ac43c8fe2763f1024100c67ca6d988f53abea82159431a146512a8d942978d4a8f83f2d426f1095e3bf1b5b9b8b1ccbbad2a31c6401880447a45f5e0790269061ac13b5f68f1777d7f07
30
是Sequence的tag,82
是指接下来后两个bytes是这个Sequence的长度,即0x025d
个bytes,也就是剩下全部都是;接着的020100
就是整数0,其中02
是整数的tag,01
是这个整数占1byte,00
是value同样的方法也可以解02818100a0...
和后面其他整数,拆分:
3082025d # Begin Sequence: len=0x025d
0201 # Version: (len=0x01)
00
028181 # n: (len=0x81)
00a0d154d5bf97c40f7797b44819d09c608fa4b5c38e70d83bc13267138c6eff4c1aacefe3ddb571e1b41d911c7ab6136cf90493189563450e1f4270cabbc4207c54c4da7b84a20311cfbbabe82b9fe60bdf48a08d57839d0cdf9464d84262bcc06bc308095a6987f60ad07d669a312b5a7e4133213788eecf25863248b91349ef
0203 # e: (len=0x03)
010001
028180 # d: (len=0x80)
0f8270c496903bf3e3ec4912450f15edc81cb1fcf4b154615aee11fbd428e64d402b5a8d66d5f770358f3e6df935b324e8d5349c83d7c992a5982249a31734acb1db19c4c8d829267514bc1ef7bbfbe242d4350f67a002a56d33e56d1a94adc71c68f020dc39ab7d0064c111b164e26ba0698dc94a03cdfd516ffd966e877949
0241 # p: (len=0x41)
00ca97e49c058237f96e99118ce383f91912cba1163de9236181ff754ef3ef1a260fac8d2d9aee866d51a8b6836983b05cf850e786289b6859925bc8695fc67c47
0241 # q: (len=0x41)
00cb3630aafffcb29607f0833dc7f05c143ee92fadfe975da4cf6719e71226bee72562e8631328a25d7351507a8d43c1295ab6ea242b60a28b109233a983f42119
0240 # d mod (p-1): (len=0x40)
1b4a32a541a8b4d988a85dd0d8a4e25d1a470bbfef3f0461121dd3337b706dd94aab37a9390180622169d48c071e921733ebd204245c2ac6460ccf0642bc7de9
0241 # d mod (q-1): (len=0x41)
008d9f44a7c823eaaa58fa2bdd20bcc8cf6b50c463f4acb51ca956e75c7ceff7d7cbdc74aca7ab880cacd39cccec2aae320e00b0896899be6e40ac43c8fe2763f1
0241 # (inverse of q) mod p: (len=0x41)
00c67ca6d988f53abea82159431a146512a8d942978d4a8f83f2d426f1095e3bf1b5b9b8b1ccbbad2a31c6401880447a45f5e0790269061ac13b5f68f1777d7f07
# End Sequence
RSA公钥
-----BEGIN PUBLIC KEY-----
...Base64 encoded key...
-----END PUBLIC KEY-----
例:
30819f300d06092a864886f70d010101050003818d0030818902818100a0d154d5bf97c40f7797b44819d09c608fa4b5c38e70d83bc13267138c6eff4c1aacefe3ddb571e1b41d911c7ab6136cf90493189563450e1f4270cabbc4207c54c4da7b84a20311cfbbabe82b9fe60bdf48a08d57839d0cdf9464d84262bcc06bc308095a6987f60ad07d669a312b5a7e4133213788eecf25863248b91349ef0203010001
拆分:
30819f # Begin Main Sequence: len=0x9f
300d # Begin Sub1 Sequence: len=0x0d
0609 # algo_oid: (1.2.840.113549.1.1.1 - PKCSv1.2)
2a864886f70d010101
0500 # params: (null)
# End Sub1 Sequence
03818d # BitString: len=0x8d ([n, e])
00308189 # Begin Sub2 Sequence: len=0x89
028181 # n:
00a0d154d5bf97c40f7797b44819d09c608fa4b5c38e70d83bc13267138c6eff4c1aacefe3ddb571e1b41d911c7ab6136cf90493189563450e1f4270cabbc4207c54c4da7b84a20311cfbbabe82b9fe60bdf48a08d57839d0cdf9464d84262bcc06bc308095a6987f60ad07d669a312b5a7e4133213788eecf25863248b91349ef
0203 # e:
010001
# End Sub2 Sequence
# End Main Sequence
参考
详细原理
[Cryptanalysis of RSA and It’s Variants](http://index-of.es/Varios-2/Cryptanalysis of RSA and It's Variants.pdf)
作者:chinjinyu
出处:https://www.cnblogs.com/vconlln/p/17092254.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理