同时,我们假设 $m<n_i, 1\leq i \leq 3$。如果这个条件不满足的话,就会使得情况变得比较复杂,这里我们暂不讨论。
既然他们互素,那么我们可以根据中国剩余定理,可得$m^3 \equiv C \bmod n_1n_2n_3$。
此外,既然 $m<n_i, 1\leq i \leq 3$,那么我们知道 $m^3 < n_1n_2n_3$ 并且 $C<m^3 < n_1n_2n_3$,那么 $m^3 = C$,我们对 C 开三次根即可得到 m 的值。
对于较大的 e 来说,我们只是需要更多的明密文对。
SCTF RSA3 LEVEL4
参考 http://ohroot.com/2016/07/11/rsa-in-ctf。
这里我们以 SCTF RSA3 中的 level4 为例进行介绍,首先编写代码提取 cap 包中的数据,如下
#!/usr/bin/env python
from scapy.all import *
import zlib
import struct
PA = 24
packets = rdpcap('./syc_security_system_traffic3.pcap')
client = '192.168.1.180'
list_n = []
list_m = []
list_id = []
data = []
for packet in packets:
# TCP Flag PA 24 means carry data
if packet[TCP].flags == PA or packet[TCP].flags == PA + 1:
src = packet[IP].src
raw_data = packet[TCP].load
head = raw_data.strip()[:7]
if head == "We have":
n, e = raw_data.strip().replace("We have got N is ",
"").split('\ne is ')
data.append(n.strip())
if head == "encrypt":
m = raw_data.replace('encrypted messages is 0x', '').strip()
data.append(str(int(m, 16)))
with open('./data.txt', 'w') as f:
for i in range(0, len(data), 2):
tmp = ','.join(s for s in data[i:i + 2])
f.write(tmp + '\n')
其次,利用得到的数据直接使用中国剩余定理求解。
from functools import reduce
import gmpy
import json, binascii
def modinv(a, m):
return int(gmpy.invert(gmpy.mpz(a), gmpy.mpz(m)))
def chinese_remainder(n, a):
sum = 0
prod = reduce(lambda a, b: a * b, n)
# 并行运算
for n_i, a_i in zip(n, a):
p = prod // n_i
sum += a_i * modinv(p, n_i) * p
return int(sum % prod)
nset = []
cset = []
with open("data.txt") as f:
now = f.read().strip('\n').split('\n')
for item in now:
item = item.split(',')
nset.append(int(item[0]))
cset.append(int(item[1]))
m = chinese_remainder(nset, cset)
m = int(gmpy.mpz(m).root(19)[0])
print binascii.unhexlify(hex(m)[2:-1])
➜ sctf-rsa3-level3 git:(master) ✗ sage exp.sage
sys:1: RuntimeWarning: not adding directory '' to sys.path since everybody can write to it.
Untrusted users could put files in this directory which might then be imported by your Python code. As a general precaution from similar exploits, you should not execute Python code from this directory
F4An8LIn_rElT3r_rELa53d_Me33Age_aTtaCk_e_I2_s7aLL
我们假设爱丽丝要给鲍勃发送消息,首先爱丽丝对要加密的消息 M 进行随机 padding,然后加密得到密文 C1,发送给鲍勃。这时,中间人皮特截获了密文。一段时间后,爱丽丝没有收到鲍勃的回复,再次对要加密的消息 M 进行随机 padding,然后加密得到密文 C2,发送给 Bob。皮特再一次截获。这时,皮特就可能可以利用如下原理解密。
def gen_key():
while True:
p = getPrime(k/2)
if gcd(e, p-1) == 1:
break
q_t = getPrime(k/2)
n_t = p * q_t
t = get_bit(n_t, k/16, 1)
y = get_bit(n_t, 5*k/8, 0)
p4 = get_bit(p, 5*k/16, 1)
u = pi_b(p4, 1)
n = bytes_to_long(long_to_bytes(t) + long_to_bytes(u) + long_to_bytes(y))
q = n / p
if q % 2 == 0:
q += 1
while True:
if isPrime(q) and gcd(e, q-1) == 1:
break
m = getPrime(k/16) + 1
q ^= m
return (p, q, e)
def get_bit(number, n_bit, dire):
'''
dire:
1: left
0: right
'''
if dire:
sn = size(number)
if sn % 8 != 0:
sn += (8 - sn % 8)
return number >> (sn-n_bit)
else:
return number & (pow(2, n_bit) - 1)
可以看出根据 dire(ction) 的不同,会得到不同的数
dire=1 时,程序首先计算 number 的二进制位数 sn,如果不是 8 的整数倍的话,就将 sn 增大为 8 的整数倍,然后返回 number 右移 sn-n_bit 的数字。其实 就是最多保留 number 的 n_bit 位。
dire=0 时,程序直接获取 number 的低 n_bit 位。
然后我们再来看程序
t = get_bit(n_t, k/16, 1)
y = get_bit(n_t, 5*k/8, 0)
p4 = get_bit(p, 5*k/16, 1)
这三个操作分别做了如下的事情
t 为 n_t 的最多高 k/16 位,即 128 位,位数不固定。
y 为 n_t 的低 5*k/8 位,即 1280 位,位数固定。
p4 为 p 的最多高 5*k/16 位,即 640 位,位数不固定。
此后,程序有如下操作
u = pi_b(p4, 1)
利用 pi_b 对 p4 进行了加密
def pi_b(x, m):
'''
m:
1: encrypt
0: decrypt
'''
enc = DES.new(key)
if m:
method = enc.encrypt
else:
method = enc.decrypt
s = long_to_bytes(x)
sp = [s[a:a+8] for a in xrange(0, len(s), 8)]
r = ""
for a in sp:
r += method(a)
return bytes_to_long(r)
n = bytes_to_long(long_to_bytes(t) + long_to_bytes(u) + long_to_bytes(y))
q = n / p
if q % 2 == 0:
q += 1
while True:
if isPrime(q) and gcd(e, q-1) == 1:
break
m = getPrime(k/16) + 1
q ^= m
return (p, q, e)
而 p 是 k/2 位的,所以说,random 的部分最多可以影响原来的 n 的最低的 $k/2+k/16=9k/16$ 比特位。
而,我们还知道 n 的最低的 5k/8=10k/16 比特为其实就是 y,所以其并没有影响到 u,即使影响到也就最多影响到一位。
所以我们首先可以利用我们得到的 n 来获取 u,如下
u=hex(n)[2:-1][-480:-320]
虽然,这样可能会获得较多位数的 u,但是这样并不影响,我们对 u 解密的时候每一分组都互不影响,所以我们只可能影响最高位数的 p4。而 p4 的的高 8 位也有可能是填充的。但这也并不影响,我们已经得到了因子 p 的的很多部分了,我们可以去尝试着解密了。如下
if __name__=="__main__":
n = 0x724d41149e1bd9d2aa9b333d467f2dfa399049a5d0b4ee770c9d4883123be11a52ff1bd382ad37d0ff8d58c8224529ca21c86e8a97799a31ddebd246aeeaf0788099b9c9c718713561329a8e529dfeae993036921f036caa4bdba94843e0a2e1254c626abe54dc3129e2f6e6e73bbbd05e7c6c6e9f44fcd0a496f38218ab9d52bf1f266004180b6f5b9bee7988c4fe5ab85b664280c3cfe6b80ae67ed8ba37825758b24feb689ff247ee699ebcc4232b4495782596cd3f29a8ca9e0c2d86ea69372944d027a0f485cea42b74dfd74ec06f93b997a111c7e18017523baf0f57ae28126c8824bd962052623eb565cee0ceee97a35fd8815d2c5c97ab9653c4553f
u = hex(n)[2:-1][-480:-320]
u = int(u,16)
p4 = pi_b(u,0)
print hex(p4)
from sage.all import *
import binascii
n = 0x724d41149e1bd9d2aa9b333d467f2dfa399049a5d0b4ee770c9d4883123be11a52ff1bd382ad37d0ff8d58c8224529ca21c86e8a97799a31ddebd246aeeaf0788099b9c9c718713561329a8e529dfeae993036921f036caa4bdba94843e0a2e1254c626abe54dc3129e2f6e6e73bbbd05e7c6c6e9f44fcd0a496f38218ab9d52bf1f266004180b6f5b9bee7988c4fe5ab85b664280c3cfe6b80ae67ed8ba37825758b24feb689ff247ee699ebcc4232b4495782596cd3f29a8ca9e0c2d86ea69372944d027a0f485cea42b74dfd74ec06f93b997a111c7e18017523baf0f57ae28126c8824bd962052623eb565cee0ceee97a35fd8815d2c5c97ab9653c4553f
p4 =0xa37302107c17fb4ef5c3443f4ef9e220ac659670077b9aa9ff7381d11073affe9183e88acae0ab61fb75a3c7815ffcb1b756b27c4d90b2e0ada753fa17cc108c1d0de82c747db81b9e6f49bde1362693
cipher = 0xf11e932fa420790ca3976468dc4df1e6b20519ebfdc427c09e06940e1ef0ca566d41714dc1545ddbdcae626eb51c7fa52608384a36a2a021960d71023b5d0f63e6b38b46ac945ddafea42f01d24cc33ce16825df7aa61395d13617ae619dca2df15b5963c77d6ededf2fe06fd36ae8c5ce0e3c21d72f2d7f20cd9a8696fbb628df29299a6b836c418cbfe91e2b5be74bdfdb4efdd1b33f57ebb72c5246d5dce635529f1f69634d565a631e950d4a34a02281cbed177b5a624932c2bc02f0c8fd9afd332ccf93af5048f02b8bd72213d6a52930b0faa0926973883136d8530b8acf732aede8bb71cb187691ebd93a0ea8aeec7f82d0b8b74bcf010c8a38a1fa8
e2 = 0xf93b
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)
if roots:
p = p4+int(roots[0])
print "p: ", hex(int(p))
assert n % p == 0
q = n/int(p)
print "q: ", hex(int(q))
print gcd(p,q)
phin = (p-1)*(q-1)
print gcd(e2,phin)
d = inverse_mod(e2,phin)
flag = pow(cipher,d,n)
flag = hex(int(flag))[2:-1]
print binascii.unhexlify(flag)
结果如下
➜ 2016-HCTF-RSA2 git:(master) ✗ sage payload.sage
sys:1: RuntimeWarning: not adding directory '' to sys.path since everybody can write to it.
Untrusted users could put files in this directory which might then be imported by your Python code. As a general precaution from similar exploits, you should not execute Python code from this directory
640
p: 0xa37302107c17fb4ef5c3443f4ef9e220ac659670077b9aa9ff7381d11073affe9183e88acae0ab61fb75a3c7815ffcb1b756b27c4d90b2e0ada753fa17cc108c1d0de82c747db81b9e6f49bde13626933aa6762057e1df53d27356ee6a09b17ef4f4986d862e3bb24f99446a0ab2385228295f4b776c1f391ab2a0d8c0dec1e5L
q: 0xb306030a7c6ace771db8adb45fae597f3c1be739d79fd39dfa6fd7f8c177e99eb29f0462c3f023e0530b545df6e656dadb984953c265b26f860b68aa6d304fa403b0b0e37183008592ec2a333c431e2906c9859d7cbc4386ef4c4407ead946d855ecd6a8b2067ad8a99b21111b26905fcf0d53a1b893547b46c3142b06061853L
1
1
hctf{d8e8fca2dc0f896fd7cb4cb0031ba249}
题目
2016 湖湘杯 简单的 RSA
2017 WHCTF Untitled
Boneh and Durfee attack
攻击条件
当 d 较小时,满足 $d < N^{0.292}$ 时,我们可以利用该攻击,比 Wiener's Attack 要强一些。
首先题目给了一堆 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!
delta = .262 # this means that d < N^delta
#
# Lattice (tweak those values)
#
# you should tweak this (after a first run), (e.g. increment it until a solution is found)
m = 8 # size of the lattice (bigger the better/slower)
# you need to be a lattice master to tweak these
t = int((1-2*delta) * m) # optimization from Herrmann and May
X = floor(3*e/N*N^delta) #4*floor(N^delta) # this _might_ be too much
Y = floor(2*N^(1/2)) # correct if p, q are ~ same size
最后可以得到结果
[DEBUG] Received 0x1f bytes:
'Succcess!\n'
'OOO{Br3akingL!mits?}\n'
OOO{Br3akingL!mits?}
不得不说这个题目,真的是需要多核服务器。。
参考资料
Survey: Lattice Reduction Attacks on RSA
An Introduction to Coppersmith’s method and Applications in Cryptology