RSA的已知高位攻击

时间:2022-04-27
本文章向大家介绍RSA的已知高位攻击,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

当在比赛遇到知道p或q的高位因子时,我们就可以用这种方法分解从而得到p,q ,这里我们以WHCTF的Untitled题为例子讲解一下这种攻击方法

首先是看加密的源码

def gen_args():
    p=getPrime(1024)
    q=getPrime(1024)
    n=p*q
    e=0x10001
    d=primefac.modinv(e,(p-1)*(q-1))%((p-1)*(q-1))
    return (p,q,e,n,d)

这里相当于随机生成了1024位的p和q,然后给了e,跟RSA的算法一样生成私钥d。继续分析

def proof():
    salt=urandom(4)
    print salt.encode("base64"),
    proof=raw_input("show me your work: ")
    if hashlib.md5(salt+proof.decode("base64")).hexdigest().startswith("0000"):
        print "checked success"
        return 1
    return 0

第一段验证的程度,需要我们给一段base64编码的work,然后salt+work的md5的开头要是"0000",直接用代码爆破得到合适的work

salt=p.recvline()
msg = base64.b64decode(salt)
work=""
for i in count():
    hashid = md5(msg+str(i)).hexdigest()
    if hashid.startswith('0000'):
        #print i,hashid
        work=base64.b64encode(str(i))
        break

继续看

def run():
    if not proof():
        return
    m=int(open("/home/bibi/PycharmProjects/work/whctf/flag","r").read().encode("hex"),16)#flag{*}
    (p,q,e,n,d)=gen_args()
    c=pow(m,e,n)
    print "n:",hex(n)
    print "e:",hex(e)
    print "c:",hex(c)
    t=int(hex(m)[2:][0:8],16) #取hex(m)的前八位      t=1718378855
    u=pow(t,e,n)
    print "u:",hex(u)
    print "===="
    x=int(hex(m)[2:][0:8]+raw_input("x: "),16)
    print "===="
    y=int(raw_input("y: "),16)
    if (pow(x,e,n)==y and pow(y,d,n)==t):
        print "s:",hex(int(bin(p)[2:][0:568],2))
run()

这里大概意思就是题目会返回给我们n,e,c,u的值,并且我们需要输入x,y使得pow(x,e,n)==y and pow(y,d,n)==t才会返回给我们s,那么仔细来看看,首先n,e,c的意思大家应该明白。看看t=int(hex(m)[2:][0:8],16)这句代码,我在后面写了备注,就是取hex(m)的前八位十六进制即4个字符,而题目给了tips,m是flag{*}形式的。所以t是固定的。然后继续看,需要我们输入一个x0,y0然后x=t+x0,y=y0使得pow(x,e,n)==y and pow(y,d,n)==t

到这里我们可以分析一下数学逻辑,根据RSA的逻辑

c=pow(m,e,n)<===>m=pow(c,d,n)
 u=pow(t,e,n)<===>t=pow(u,d,n)

而题目要求

t==pow(y,d,n) ==>y=u
 y==pow(x,e,n) <==> u==pow(x,e,n) ==>x=t

所以我们可以知道y=u,x=t结合x=t+x0,y=y0可以得出x0=空字符 y0=u, 按要求输入x0,y0就能得到s了,这里放最终代码

#coding=utf-8
from pwn import *
from itertools import count
from hashlib import md5
import base64
 
p=remote('118.31.18.75',20013)
 
salt=p.recvline()
msg = base64.b64decode(salt)
work=""
for i in count():
    hashid = md5(msg+str(i)).hexdigest()
    if hashid.startswith('0000'):
        #print i,hashid
        work=base64.b64encode(str(i))
        break
#print work
t=1718378855
p.recvuntil('work: ')
p.sendline(work)
print p.recvline()
print p.recvline()
print p.recvline()
print p.recvline()
u = int(p.recvline()[5:-2],16)   #u
print "u:",hex(u)
print p.recvline()
p.recvuntil('x: ')
#print str(hex(t))
p.sendline("")  #x
print p.recvline()
p.recvuntil('y: ')  #y
p.sendline(str(hex(u))[2:])
# p_568=int(p.recvline()[5:-2],16)
# print "p_568:",hex(p_568)
print p.recvline()

继续分析得到s,是p的前568位二进制数组成的数,这里有个小坑,直接拿去高位攻击是不行的,因为必须要已知576位才能高位攻击,所以我们要爆破568到576中的八位二进制数即两位十六进制然后再进行已知高位攻击,直接放sage代码

from sage.all import *
import binascii
n =   0x9d3a1a28ecb1bd245dd86b18dc4c5b729f23778710005118836129f08e31d6516de8ab47db1b3b7f660f50d283b1e9f2c06e7836136e4c0159f5d2b05771861d3ce6aa8715932eadc1cc0f380909a1961018340f7393142f9c177b1187151f97ac8cdc4ad17fa59a0f39d192af555f27de9cc800846eb2ca6ce78f87c0c0fbf47828328392b81771af624389fd779d130d80739bb7a608961125ba3f1800c766440fa70bfd3f834294d47d7ed9cfffd6d14ae18310f6c1d6d8f88b6c5d72a0b45608b4e21bbb8e314220ed7a2d6a8c95454e571c71b50f1d6a823778ca47131f5b889a1ed1957248bee8c4ac66872a5fd58a121560a27bad4958f1c763f2ffddL
 
 
cipher = 0x1f2deea59244b14e53c72465febc2064172a35245842fa83ebff313344bed35ee8af8c3f8f61e6f498fa1fd35e63998a573d7717905f72ec01de0b0529eaab10eb0b0c2ca06e9d6e4245e748fd74f4f756a86e379559793389a3ae6c421d51bb78331a487fc3c3e68971e3e26991ab34ce2a2c07ffd5a5a1e215e766b51fb2d6aab63c2dafa3c87d0a5eb79b634740e1fca7a727de997958839bda684e19acad93cae4abfd1c8cc3684419f83696fe4840f3253e7c038adb13a1382667cf7e17ef55c1e950ea474594102e660e36a23bfd3fd830d1c18a434d0b34bed98308399a894dcab909d68bcab7c7ac990974a4f6ed7d612abb7044f6734eaaebcdc0b5L
 
e2 = 0x10001
pbits = 1024
for i in range(0,127):
    p4=0xda5df16f286dbc825cd0c8ee48aa26ac27338a75172c5b92351f14d083216f7e91b9355e27cf930646fbbda6058dec3c4ddf751f36df5556359fbe671f9b947b4c79cadfdbb27b00
    p4=p4+int(hex(i),16)
    print hex(p4)
    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) #进行爆破
    #rint roots
  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)