xman排位賽-Crypto第一彈-RSA
前話
閒下來把之前沒做完(出來)的排位賽的crypto做了下,這裡分享兩道xman夏令營排位賽的RSA的題目,認真學習!
0x01 RSA-Generator
這題算是三題裡面最簡單的一題了。
import gmpy2 import random from Crypto.Util.number import getPrime from Crypto.PublicKey import RSA def generate_public_key(): part1 = 754000048691689305453579906499719865997162108647179376656384000000000000001232324121 part1bits = part1.bit_length() lastbits = 512 - part1.bit_length() part1 = part1 << lastbits part2 = random.randrange(1, 2**lastbits) p = part1 + part2 while not gmpy2.is_prime(p): p = part1 + random.randrange(1, 2**lastbits) q = getPrime(512) n = p * q print p print q e = 0x10001 key = RSA.construct((long(n), long(e))) key = key.exportKey() with open('public.pem', 'w') as f: f.write(key) def encrypt(): flag = open('./flag.txt').read().strip('n') flag = flag.encode('hex') flag = int(flag, 16) with open('./public.pem') as f: key = RSA.importKey(f) enc = gmpy2.powmod(flag, key.e, key.n) with open('flag.enc', 'w') as f: f.write(hex(enc)[2:]) generate_public_key() encrypt()
提取出有用的條件
我們來看看我們有的條件
- 題目給出了
pblic.pem
,我們用python的RSA
庫提取出n和e
from Crypto.PublicKey import RSA pub = RSA.importKey(open('public.pem').read()) n = long(pub.n) e = long(pub.e) print "n:"+str(n) print "e:"+str(e)
能夠得到:
n=0x639386F4941D1511D89A9D19DC4731188D3F4D2D04623FB26F5A85BB3A54747BCBADCDBD8E4A75747DB4072A90F62DCA08F11AC276D7588042BEFA504DCD87CD3B0810F1CB28168A53F9196CDAF9FD1D12DCD4C375EB68B67A8EFCCEC605C57C736943170FEF177175F696A0F6123B993E56FFBF1B62435F728A0BAC018D0113 e=0x10001
- 程式碼邏輯大致為:隨機生成的
q
和已知p
的高位為part1
,p
的低位part2
是完全隨機的不可控的。 -
p
和q
的位數都是512位,part2的位數是279位
coppersmith高位已知攻擊
根據已知的的條件,可以聯想到coopersmith的高位攻擊。
我們來關注一下coppersmith的一些定理:
- 定理3.3 對任意的a > 0 , 給定N = PQR及PQ的高位
(1/5)(logN,2)
位元,我們可以在多項式時間logN內得到N的分解式。
這是三個因式的分解。也就是說我們現在是由理論依據的,已知高位是可以在一定時間內分解N。具體的演算法的推導這裡沒法給出。 - 那麼在已知p高位多少位才可以進行攻擊呢,保證哥在做題的時候給出了定理的提示

這個定理是在《Mathematics_of_Public_Key_Cryptography》這本數裡面提到的,我們將我們上面得到的N的值帶入上圖的式子中。
計算(1/根號2)*N
根據上式子我們得出:
if p.bit_length == 1024 ,p的高位需已知約576位 if p.bit_length == 1024 ,p的高位需已知約288位
-
sage
裡面的small_roots
能實現上述的給出已知的p高位進行分解N的函式方法,利用了LLL演算法求解非線性低維度多項式方程小根的方法。
Coppersmith證明了在已知p和q部分位元的情況下,若q和p的未知部分的上界X
和Y
滿足XY <= N ^ (0.5)
則N的多項式可以被分解。
這裡的0.5
可以替換成其他的數,具體原因不詳。 - 我們已知的part1的位元位為279位,距離我們的條件還差9位左右,所以這裡不能直接讓上述的條件成立,所以我們還需要爆破
9
位左右的資料。
由於9
位左右的bit需要至少3
位十六進位制(3*4 > 9)
攻擊指令碼
sage裡 ofollow,noindex" target="_blank">small_roots具體用法
#!/usr/bin/env sage -python # -*- coding: utf-8 -*- from sage.all import * import binascii n = 0x639386F4941D1511D89A9D19DC4731188D3F4D2D04623FB26F5A85BB3A54747BCBADCDBD8E4A75747DB4072A90F62DCA08F11AC276D7588042BEFA504DCD87CD3B0810F1CB28168A53F9196CDAF9FD1D12DCD4C375EB68B67A8EFCCEC605C57C736943170FEF177175F696A0F6123B993E56FFBF1B62435F728A0BAC018D0113 cipher = 0x56c5afbc956157241f2d4ea90fd24ad58d788ca1fa2fddb9084197cfc526386d223f88be38ec2e1820c419cb3dad133c158d4b004ae0943b790f0719b40e58007ba730346943884ddc36467e876ca7a3afb0e5a10127d18e3080edc18f9fbe590457352dca398b61eff93eec745c0e49de20bba1dd77df6de86052ffff41247d e2 = 0x10001 pbits = 512 for i in range(0,4095): p4 = 0x635c3782d43a73d70465979599f65622c7b4242a2d623459337100000000004973c619000 p4 = p4 + int(hex(i),16) kbits = pbits - p4.nbits()#未知需要爆破的位元位數 p4 = p4 << kbits PR.<x> = PolynomialRing(Zmod(n)) f = x + p4 roots = f.small_roots(X=2^kbits, beta=0.4) #進行爆破 #print roots if roots:#爆破成功,求根 p = p4+int(roots[0]) assert n % p == 0 q = n/int(p) phin = (p-1)*(q-1) d = inverse_mod(e2,phin) flag = pow(cipher,d,n) flag = hex(int(flag))[2:-1] print binascii.unhexlify(flag)
最後的flag:
flag: xman{RSA-is-fun???!!!!}
分享一個線上sage的網站,tql, http://sagecell.sagemath.org/
0x02
題目原始碼
from Crypto.PublicKey import RSA from Crypto.Util.number import bytes_to_long, long_to_bytes import socketserver class PUB(): def __init__(self): self.rsa = RSA.generate(2048) def get_n(self): return self.rsa.n def get_e(self): return self.rsa.e def encrypt(self, plaintext): return self.rsa.encrypt(plaintext, None)[0] def decrypt(self, ciphertext): return (self.rsa.decrypt(ciphertext) % 2 == 0) class process(socketserver.BaseRequestHandler): def handle(self): #self.justWaite() pub = PUB() e, n = pub.get_e(), pub.get_n() self.request.send(bytes(hex(e), 'utf-8')) self.request.send(b'nn') self.request.send(bytes(hex(n), 'utf-8')) while True: self.request.send(b"n'f'lag or 'e'ncrypt or 'd'ecrypt_detectn") c = self.request.recv(2)[:-1] if c == b'f': flag = b'xman{*********************}' flag = bytes_to_long(flag) self.request.send(long_to_bytes(pub.encrypt(flag))) elif c == b'd': c = self.request.recv(2048)[:-1] c = bytes_to_long(c) self.request.send(bytes(str(pub.decrypt(c)), 'utf-8')) self.request.close() class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer): pass if __name__ == "__main__": HOST, PORT = '0.0.0.0', 10093 server = ThreadedServer((HOST, PORT), process) server.allow_reuse_address = True server.serve_forever()
提取有用資訊
-
self.request.send(long_to_bytes(pub.encrypt(flag)))
==> 我們如果輸入f
,能得到flag的byte值,實際上就相當於給了我們flag的加密值。 -
def decrypt(self, ciphertext): return (self.rsa.decrypt(ciphertext) % 2 == 0)
==> 如果我們輸入
d
,就能得到返回值的ciphertext
是否位偶數,是則返回True
否則返回false
能得到的訊息是已知n和e,c以及返回值的奇偶性,這些條件,馬上能聯想到 LSB Oracle
攻擊。
LSB oracle
LSB oracle實際上是原理是一種二分逼近的方法。
我們來假設正常的加密為:
ct = pt^e mod n
那麼我們假設存在 c'
:
ct' = ct * 2^e mod n
則有:
ct' = pt^e * 2^e mod n
則有:
ct' = (2*pt)^e mod n
那麼:
ct'^d = (2pt^e)^d mod n
則有:
ct'^d = 2pt^ed mod n
又因為在rsa的體系裡面
ed = 1 mod n
則有
ct' ^ d = 2pt mod n
那麼就有下面幾個情況:
- 如果返回值是
True
即LSB是0
即解密出來明文是偶數),那麼數字小於模數, 因此2*pt < n
意味著pt < n/2
- 如果返回值是
False
即LSB是1
即解密出來明文大於模數,因此2*pt > n
意味著pt > n/2
- 如果我們再詢問LSB,4*pt mod n我們可以再次得到兩個可能的結果之一:
- 如果LSB 0那麼4 pt < n意味著Pt < n/4是否pt < n/2為真,或者在前一步中pt < 3 n/4是否pt > n/2為真
- 如果LSB 1那麼4 pt > n意味著pt > n/4是否pt < n/2為真,或者在前一步中pt > 3 n/4是否pt > n/2為真
….以此類推
攻擊指令碼
故而最後可以用上下限逼近的方法進行解密,最後的程式碼如下:
from Crypto.Util.number import bytes_to_long, long_to_bytes from hashlib import sha1 import itertools import socket import time import math import binascii e=0x10001 def encrypt(m, N): return pow(m, 2, N) s = socket.create_connection(('202.112.51.184', 10093)) r1 = s.recv(4096) r2 = s.recv(4096) n = int(r2[r2.find('0x'):r2.find(''f'')-1],16) s.send('fn') r3 = s.recv(4096) r3 = bytes(r3) c =bytes_to_long(r3) upper = n lower = 0 iter_count = math.log(n, 2) r = s.recv(4096) print long(math.ceil(long(iter_count))) for i in xrange(0, long(math.ceil(long(iter_count)))): s.send('dn') print 'Round', i power = pow(2, i+1, n) ct = (pow(power, e, n) * c) % n s.send(str(hex(ct))[2:-1]+'n') r = s.recv(4096) # even if upper-lower <= 2: break if 'True' in r: upper = (upper + lower)/2 # odd elif 'False' in r: lower = (upper + lower)/2 print 'nFlag:' print str(long_to_bytes(upper))
tips: 伺服器好像出了點問題,flag跑不出來,大家有興趣的可以本地搭一下試試。
最後的flag:`xman{adsfklhuyuy709877*.ho}
LSB Oracle還是挺簡單的,如果上述有問題,請指出大家多xiao習xiao習。