密码学学习笔记 之 RSA的一些套路

admin 2024年8月24日23:36:22评论19 views字数 18153阅读60分30秒阅读模式

咕了快半年233333

这里是RSA最最基础的,也是最最常规的利用,都是由于对算法的不正确使用而造成的,coppersmith相关的attack请移步密码学学习笔记 之 Coppersmith’s Method

解密算法正确性的证明

首先从解密算法的正确性证明起手,即证明$m \equiv c^d \pmod n$

欧拉定理:若$a,n$为正整数,且$𝑎,𝑛$互素(即$gcd(a,n) = 1$),则$𝑎^{𝜑(𝑛)} \equiv 1 \pmod n$。已知 $1 \equiv e\cdot d \pmod {φ(n)},𝑚 < 𝑛,c \equiv m^e \pmod n,gcd(𝑚,𝑛) = 1$,求证$m \equiv c^d \pmod n$

等式左边为m

等式右边为$c^d \pmod{n}$∵ $𝑐 \equiv m^e\pmod n $$

∴ $c^d \pmod n \equiv (m^e \pmod n)^d \pmod n \equiv m^{e\cdot d} \pmod n $

∵ $1 \equiv e\cdot d \pmod {φ(n)} $

∴ $e\cdot d \equiv k\cdot φ(n) + 1 $

∴ $c^d \equiv m^{e \cdot d} \equiv m^{k\cdot φ(n)+1} \equiv m\cdot (m^{φ(n)})^k \pmod n $

∵ $gcd(m,n) \equiv 1$

∴ $m^{φ(n)} \equiv 1 \pmod n $

∴ $c^d \equiv m\cdot (m^{φ(n)})^k \equiv m\cdot (1)^k \equiv m \pmod n $

∵ $m<n$

∴ $m \pmod n \equiv m$

∴ $c^d \equiv m \pmod n \equiv m $

等式右边等于等式左边,证毕。

p和q相差过小

如果题目代码给到$p = nextprime(q)$这样

那么说明$p$和$q$特别接近,所以可以看作$n = p^2$

所以我们直接对$n$开方就可以得到$p、q$之间的一个值$t$,进而求一个$nextprime(t)$即可得到$p$

模n有公约数

利用欧几里得辗转相除法($gcd$)即可得到$n$的公约数

已知e,d,n分解模求p,q

原理来自这篇综述

给定$d$,计算$k=d\cdot e−1$。根据$d$和$e$的定义我们知道$k$是$φ(N)$的倍数。由于$φ(N)$是偶数,$k=2^tr$其中$r$为奇数且$t≥1$。对于每个$g\in \mathbb{Z_n}$都有$g^k=1$,因此$g^{\frac{k}{2}}$是单位模$N$的平方根。根据中国剩余定理,1在模$N$下有四个平方根。其中两个平方根是$±1$,另外两个是$±x$,其中x满足$x\equiv 1 \pmod p$和$x\equiv −1 \pmod q$。用这最后两个平方根中的任意一个,通过计算$gcd(x−1,N)$来揭示$N$的因式分解。一个直截了当的论证表明,如果从$\mathbb{Z_n}$中随机选择$g$,那么序列中的一个元素$g^{\frac{k}{2}},g^{\frac{k}{4}},…,g^{\frac{k}{2^t}} \pmod N$是统一平方根的概率至少为$\frac{1}{2}$,从而揭示了$N$的分解,序列中的所有元素可以在时间$\mathcal{O}(n^3))$内有效地计算,其中$n=log_2N$。

12345678910111213141516
from gmpy2 import gcdfrom random import *def factor_n_with_ed(n,e,d):      p = 1      q = 1      while p==1 and q==1:          k = d * e - 1          g = random.randint ( 0 , n )          while p==1 and q==1 and k % 2 == 0:              k /= 2              y = pow(g,k,n)              if y!=1 and gcd(y-1,n)>1:                  p = gcd(y-1,n)                  q = n/p      return p,q

$d_p、d_q$泄露

摘自这篇私钥重构

首先说明$d_p$和$d_q$的含义

$d_p \equiv d \pmod{φ(p)}$$d_q \equiv d \pmod{φ(q)}$

利用条件:已知$p,q,d_p,d_q,c$,无公钥$e$、私钥$d$

证:

由 $e\cdot d \equiv 1 \pmod {(p-1)\cdot (q-1)}$

​ $d_p \equiv d \pmod{φ(p)}$

得 $e\cdot d_p \equiv 1 \pmod {φ(p)} $

设$m_1 \equiv m^{e\cdot d_p} \equiv c^{d_p} \pmod p$

则$m_1 \equiv m^{k\cdot φ(p)+1} \pmod p $

由费马小定理,$m^{φ(p)} \equiv 1 \pmod p$

所以$ m_1 \equiv m \pmod p$

同理 $m_2 \equiv m \equiv c^{d_q} \pmod q$

然后我们就有同余式方程组:$m \equiv m_1 \pmod p$

​ $m \equiv m_2 \pmod q$

然后走一波CRT恢复$m$就好了($m < p\cdot q$)

exp:

12345
mp = pow(c,dp,p)mq = pow(c,dq,q)a = invert(p,q)b = invert(q,p)m = (mp*p*a+mq*q*b) % (p*q)

$d_p$泄露

已知:公钥($n,e)$以及$d_p$

求解:$d$

已知条件:

$c\equiv m^e \pmod n$$m\equiv c^d \pmod n$$φ(n)=(p−1)\cdot (q−1)$$d\cdot e\equiv 1 \pmod {φ(n)}$

$d_p\equiv d \pmod {p−1}$

由上式可以得到 $d_p\cdot e\equiv d\cdot e \pmod {p−1}$

即 $d\cdot e = k_1\cdot (p−1)+d_p\cdot e$ ①

又 $d\cdot e\equiv 1 \pmod {(p−1)\cdot (q−1)}$ ②

①带入②得到: $k_1\cdot (p−1)+d_p\cdot e \equiv 1\pmod { (p−1)\cdot (q−1)}$

即 $k_2\cdot (p−1)\cdot (q−1)+1=k_1\cdot (p−1)+d_p\cdot e$

整理一下 $(p−1)\cdot [k_2\cdot (q−1)−k_1]+1=d_p\cdot e$

因为 $ d_p<p-1$

所以 $e>k_2\cdot (q−1)−k_1$

我们假设 $x = k_2\cdot (q−1)−k_1$

即$ x$的范围为$(0,e)$

那么我们可以遍历$e$种可能就可以求出$p-1 = \frac{d_p\cdot e -1} {x}$,

123456
def rsa_nedp(n,e,dp):    for i in range(1,e):        if (dp*e-1)%i == 0:            if n%(((dp*e-1)/i)+1)==0:                p=((dp*e-1)/i)+1                q=n/(((dp*e-1)/i)+1)

低加密指数攻击

这个很简单,无非就是加密指数$e$的选取太小了,导致$m^e<n$,这个时候根本没有触发模运算,所以直接对$c$开$e$次根就好了。一个明显的特征就是c的bit位数远小于n的bit位数。

e与φ(n)不互素

情况分几种叭,

$m^{gcd(e,phi)} < n$

这也很好理解,有点算是低加密指数攻击的变型

原来我们的加密公式是$c \equiv m^e \pmod n $

现设$t = gcd(e,phi)$

那么加密公式变为$c \equiv m^{ \hat e * t} \pmod n $,其中$\hat e * t = e$

显然此时$gcd(\hat e ,phi) =1$

我们可以先利用$\hat e ,phi$算出的私钥$\hat d$进行解密,求出$m^t$

然后由于$m^t<n$,我们直接对$m^t$开$t$次根就好了。

e=2

好家伙,e=2也就意味着上式中的$\hat e$就等于1了,没啥意义了。

e=2这种特殊情况对应着的是rabin密码系统,具体原理可移步密码学学习笔记 之 数论四大定理及应用

e != 2 && e | phi

这里要求的条件比较苛刻,你需要知道$n$的分解$p,q$然后再在模$p,q$下对$c$开$e$次根,也即域下开根,现存有一些算法可以做到,比较有效的是AMM,我这篇文章给的是另一种算法,稍微麻烦些,要分类讨论。最后如果$m>p, m>q$的话还要CRT一波,解集大小是$gcd(e,p-1)*gcd(e,q-1)$。对应题目可参考2019NCTF-EASYRSA

公钥n由多个素数因子组成

因子越多,同等长度$n$的情况下越容易分解。(所以如果$n$长度比较短,因子又被显示地说明有很多,可以试试yafu)

与正常解密RSA无异。根据定义求$φ(n)$就好。

低加密指数广播攻击

满足条件:$m.bit_length*e < \prod c.bit_length $

m,e相同,不同的是c和n,有:$c_1 \equiv m^e \pmod {n_1}$$c_2 \equiv m^e \pmod {n_2}$$c_3 \equiv m^e \pmod {n_3}$…$c_n \equiv m^e \pmod {n_n}$

CRT可以得到

$c \equiv m^e \pmod {n_1n_2n_3\cdot\cdot\cdot n_n}$

当$c < n_1n_2n_3\cdot\cdot\cdot n_n$时,上面就可以改成等式了

然后直接开一手根,结束。

1234567891011121314
from gmpy2 import *from Crypto.Util.number import *n =  []c =  []def CRT(mi, ai):    assert (reduce(gmpy2.gcd,mi)==1)    assert (isinstance(mi, list) and isinstance(ai, list))    M = reduce(lambda x, y: x * y, mi)    ai_ti_Mi = [a * (M / m) * gmpy2.invert(M / m, m) for (m, a) in zip(mi, ai)]    return reduce(lambda x, y: x + y, ai_ti_Mi) % Me=0x7m=iroot(CRT(n, c), e)[0]print(long_to_bytes(m))

共模攻击

已知:若干次加密中使用了相同的模n和不同的公钥e对同一明文m进行了多次加密并且给出了对应密文,那么我们就可以在不得到私钥的前提下,解出明文m。

首先,需要两个加密指数互质:$gcd(e_1,e_2)=1$

即存在$s_1$和$s_2$使得:$s_1\cdot e_1+s_2\cdot e_2=1$

又因为:$c_1\equiv m^{e_1} \pmod n$$c_2\equiv m^{e_2} \pmod n$

两条式子分别求一个$s_1,s_2$次方然后相乘,化简可得:$c_1^{s_1} \cdot c_2^{s_2} \equiv m^{e\cdot s_1} \cdot m^{e\cdot s_2} \equiv m^{e\cdot s_1+e\cdot s_2} \equiv m \pmod n$

即可求出明文

exp:

1234567891011121314151617181920212223242526272829303132333435
import sysimport binasciisys.setrecursionlimit(1000000)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)def modinv(a, m):    g, x, y = egcd(a, m)    if g != 1:      raise Exception('\pmodular inverse does not exist')    else:      return x % mc1=n=e1=0xc21000af014a98b2455dec479c2=e2=0x9935842d63b75899ddd81b467s = egcd(e1, e2)s1 = s[1]s2 = s[2]if s1<0:   s1 = - s1   c1 = modinv(c1, n)elif s2<0:   s2 = - s2   c2 = modinv(c2, n)m=(pow(c1,s1,n)*pow(c2,s2,n)) % nprint(m)

低加密指数攻击

连分数知识相关

相关理论知识百度百科就有,这里直接上过程,挺好懂得。

密码学学习笔记 之 RSA的一些套路

连分数就是用分数去表示一个小数叭。如果是个无限不循环小数,那么连分数就会无限得去逼近它。如果不是无限不循环小数,那么连分数得最后一项就是该小数的分数表示。

可能不懂哪来的项,什么是最后一项。

直接那上述的例子,你可以认为,连分数的

第一项就是3,和3.245差很大叭

第二项是$3\frac1 4=3.25$,只差0.005了

第三项是$3 \frac 1 {4+\frac 1 {12}}=3\frac {12} {49}=3.24489$,差更少了

最后一项就是准确表示了。

wiener‘s Attack

摘自二十年以来对 RSA 密码系统攻击综述

令$N=pq$且$q<p<2q$,$d<\frac13N^{\frac14}$,给定$⟨N,e⟩$且满足$ed=1 \pmod {φ(N)}$,攻击者可以有效计算出$d$。

proof

证明基于使用连分数的逼近,由于$ed=1 \pmod {φ(N)}$,那么存在一个$k$满足$ed−kφ(N)=1$。所以,

$\vert \frac{e}{φ(N)}-\frac kd \vert= \frac 1 {dφ(N)}$,因此,$\frac kd $是$\frac e {φ(N)}$的逼近,尽管我们不知道$φ(N)$,但是我们可以用$N$来近似。

因为$φ(N)=(p−1)(q−1)=pq−p−q+1=N−p−q+1$,又$p+q−1<3\sqrt N $所以我们有$|N−φ(N)|<3\sqrt N$

使用$N$替换$φ(N)$,我们得到:

$\vert \frac e N - \frac k d\vert = \vert \frac{ed - kN}{Nd}\vert = \vert \frac{ed - kφ(N)-kN+kφ(N)}{Nd}\vert=\vert \frac {1-k(N-φ(N))}{Nd}\vert ≤ \vert \frac{3k\sqrt{N}}{Nd}\vert =\frac{3k}{d\sqrt N}$

现在,$kφ(N)=ed−1<ed$,因为$e<φ(N)$,我们知道$k<d<\frac13N^{\frac14}$(译者注:可以得到$3k<3d<N^{\frac 14}和dN^{\frac 14}>3d^2$)。因此我们得到:

$\vert \frac e N - \frac k d\vert ≤ \frac{3k}{d\sqrt N}<\frac {N^{\frac1 4}}{d\sqrt N}≤\frac 1 {dN^{\frac 14}}< \frac 1{3d^2}$

这是一个经典的逼近关系,分数$\frac k d$且$d<φ(N)$在$log_2N$约束内非常逼近$\frac e N$。实际上,所有类似$\frac k d$这样的分数都是$\frac e N$的连分数展开的收敛。因此我们首要做的便是计算$\frac e N$的连分数的$logN$收敛,其中一个连分数就等于$\frac k d$。因为$ed−kφ(N)=1$,我们有$gcd(k,d)=1$,因此$\frac k d$是一个最简分数。这是可以算出密钥d的线性时间算法。

(至于为啥d的上界是那个,别问我,找wiener问去。)

下面是python的代码实现

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
import gmpy2def transform(x,y):       #使用辗转相处将分数 x/y 转为连分数的形式    res=[]    while y:        res.append(x//y)        x,y=y,x%y    return resdef 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#以上是获得e/n的连分数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,x2def 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 (\pmod φ(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 (\pmod  φ(n))的结果,也就是e关于 φ(n)的乘法逆元d            return d    print("该方法不适用")e = 14058695417015334071588010346586749790539913287499707802938898719199384604316115908373997739604466972535533733290829894940306314501336291780396644520926473n = 33608051123287760315508423639768587307044110783252538766412788814888567164438282747809126528707329215122915093543085008547092423658991866313471837522758159d = wienerAttack(e,n)print("d=",d)

Wiener‘s Attack失效时

失效就是意味着界限不满足,而现存的有许多基于Wiener‘s Attack的变型算法就是用来尽量扩大这个界限的。这里是Boneh Durfee Attack的脚本,需要用到多元coppersmith的原理(俺不会,略略略)

利用条件:已知给出n,e,c (e很大) (boneh_durfee.sage)

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
from __future__ import print_functionimport time############################################# Config##########################################"""Setting debug to true will display more informationsabout the lattice, the bounds, the vectors..."""debug = True"""Setting strict to true will stop the algorithm (andreturn (-1, -1)) if we don't have a correctupperbound on the determinant. Note that thisdoesn't necesseraly mean that no solutionswill be found since the theoretical upperbound isusualy far away from actual results. That is whyyou should probably use `strict = False`"""strict = False"""This is experimental, but has provided remarkable resultsso far. It tries to reduce the lattice as much as it canwhile keeping its efficiency. I see no reason not to usethis option, but if things don't work, you should trydisabling it"""helpful_only = Truedimension_min = 7 # stop removing if lattice reaches that dimension############################################# Functions########################################### display stats on helpful vectorsdef helpful_vectors(BB, modulus):    nothelpful = 0    for ii in range(BB.dimensions()[0]):        if BB[ii,ii] >= modulus:            nothelpful += 1    print(nothelpful, "/", BB.dimensions()[0], " vectors are not helpful")# display matrix picture with 0 and Xdef matrix_overview(BB, bound):    for ii in range(BB.dimensions()[0]):        a = ('%02d ' % ii)        for jj in range(BB.dimensions()[1]):            a += '0' if BB[ii,jj] == 0 else 'X'            if BB.dimensions()[0] < 60:                a += ' '        if BB[ii, ii] >= bound:            a += '~'        print(a)# tries to remove unhelpful vectors# we start at current = n-1 (last vector)def remove_unhelpful(BB, monomials, bound, current):    # end of our recursive function    if current == -1 or BB.dimensions()[0] <= dimension_min:        return BB    # we start by checking from the end    for ii in range(current, -1, -1):        # if it is unhelpful:        if BB[ii, ii] >= bound:            affected_vectors = 0            affected_vector_index = 0            # let's check if it affects other vectors            for jj in range(ii + 1, BB.dimensions()[0]):                # if another vector is affected:                # we increase the count                if BB[jj, ii] != 0:                    affected_vectors += 1                    affected_vector_index = jj            # level:0            # if no other vectors end up affected            # we remove it            if affected_vectors == 0:                print("* removing unhelpful vector", ii)                BB = BB.delete_columns([ii])                BB = BB.delete_rows([ii])                monomials.pop(ii)                BB = remove_unhelpful(BB, monomials, bound, ii-1)                return BB            # level:1            # if just one was affected we check            # if it is affecting someone else            elif affected_vectors == 1:                affected_deeper = True                for kk in range(affected_vector_index + 1, BB.dimensions()[0]):                    # if it is affecting even one vector                    # we give up on this one                    if BB[kk, affected_vector_index] != 0:                        affected_deeper = False                # remove both it if no other vector was affected and                # this helpful vector is not helpful enough                # compared to our unhelpful one                if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]):                    print("* removing unhelpful vectors", ii, "and", affected_vector_index)                    BB = BB.delete_columns([affected_vector_index, ii])                    BB = BB.delete_rows([affected_vector_index, ii])                    monomials.pop(affected_vector_index)                    monomials.pop(ii)                    BB = remove_unhelpful(BB, monomials, bound, ii-1)                    return BB    # nothing happened    return BB""" Returns:* 0,0   if it fails* -1,-1 if `strict=true`, and determinant doesn't bound* x0,y0 the solutions of `pol`"""def boneh_durfee(pol, modulus, mm, tt, XX, YY):    """    Boneh and Durfee revisited by Herrmann and May    finds a solution if:    * d < N^delta    * |x| < e^delta    * |y| < e^0.5    whenever delta < 1 - sqrt(2)/2 ~ 0.292    """    # substitution (Herrman and May)    PR.<u, x, y> = PolynomialRing(ZZ)    Q = PR.quotient(x*y + 1 - u) # u = xy + 1    polZ = Q(pol).lift()    UU = XX*YY + 1    # x-shifts    gg = []    for kk in range(mm + 1):        for ii in range(mm - kk + 1):            xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk            gg.append(xshift)    gg.sort()    # x-shifts list of monomials    monomials = []    for polynomial in gg:        for monomial in polynomial.monomials():            if monomial not in monomials:                monomials.append(monomial)    monomials.sort()    # y-shifts (selected by Herrman and May)    for jj in range(1, tt + 1):        for kk in range(floor(mm/tt) * jj, mm + 1):            yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk)            yshift = Q(yshift).lift()            gg.append(yshift) # substitution    # y-shifts list of monomials    for jj in range(1, tt + 1):        for kk in range(floor(mm/tt) * jj, mm + 1):            monomials.append(u^kk * y^jj)    # construct lattice B    nn = len(monomials)    BB = Matrix(ZZ, nn)    for ii in range(nn):        BB[ii, 0] = gg[ii](0, 0, 0)        for jj in range(1, ii + 1):            if monomials[jj] in gg[ii].monomials():                BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY)    # Prototype to reduce the lattice    if helpful_only:        # automatically remove        BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1)        # reset dimension        nn = BB.dimensions()[0]        if nn == 0:            print("failure")            return 0,0    # check if vectors are helpful    if debug:        helpful_vectors(BB, modulus^mm)    # check if determinant is correctly bounded    det = BB.det()    bound = modulus^(mm*nn)    if det >= bound:        print("We do not have det < bound. Solutions might not be found.")        print("Try with highers m and t.")        if debug:            diff = (log(det) - log(bound)) / log(2)            print("size det(L) - size e^(m*n) = ", floor(diff))        if strict:            return -1, -1    else:        print("det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)")    # display the lattice basis    if debug:        matrix_overview(BB, modulus^mm)    # LLL    if debug:        print("optimizing basis of the lattice via LLL, this can take a long time")    BB = BB.LLL()    if debug:        print("LLL is done!")    # transform vector i & j -> polynomials 1 & 2    if debug:        print("looking for independent vectors in the lattice")    found_polynomials = False    for pol1_idx in range(nn - 1):        for pol2_idx in range(pol1_idx + 1, nn):            # for i and j, create the two polynomials            PR.<w,z> = PolynomialRing(ZZ)            pol1 = pol2 = 0            for jj in range(nn):                pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY)                pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY)            # resultant            PR.<q> = PolynomialRing(ZZ)            rr = pol1.resultant(pol2)            # are these good polynomials?            if rr.is_zero() or rr.monomials() == [1]:                continue            else:                print("found them, using vectors", pol1_idx, "and", pol2_idx)                found_polynomials = True                break        if found_polynomials:            break    if not found_polynomials:        print("no independant vectors could be found. This should very rarely happen...")        return 0, 0    rr = rr(q, q)    # solutions    soly = rr.roots()    if len(soly) == 0:        print("Your prediction (delta) is too small")        return 0, 0    soly = soly[0][0]    ss = pol1(q, soly)    solx = ss.roots()[0][0]    #    return solx, solydef example():    ############################################    # How To Use This Script    ##########################################    #    # The problem to solve (edit the following values)    #    # the modulus    N = 0xc2fd2913bae61f845ac94e4ee1bb10d8531dda830d31bb221dac5f179a8f883f15046d7aa179aff848db2734b8f88cc73d09f35c445c74ee35b01a96eb7b0a6ad9cb9ccd6c02c3f8c55ecabb55501bb2c318a38cac2db69d510e152756054aaed064ac2a454e46d9b3b755b67b46906fbff8dd9aeca6755909333f5f81bf74db    # the public exponent    e = 0x19441f679c9609f2484eb9b2658d7138252b847b2ed8ad182be7976ed57a3e441af14897ce041f3e07916445b88181c22f510150584eee4b0f776a5a487a4472a99f2ddc95efdd2b380ab4480533808b8c92e63ace57fb42bac8315fa487d03bec86d854314bc2ec4f99b192bb98710be151599d60f224114f6b33f47e357517    # the hypothesis on the private exponent (the theoretical maximum is 0.292)    delta = .18 # 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 = 4 # 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 = 2*floor(N^delta)  # this _might_ be too much    Y = floor(N^(1/2))    # correct if p, q are ~ same size    #    # Don't touch anything below    #    # Problem put in equation    P.<x,y> = PolynomialRing(ZZ)    A = int((N+1)/2)    pol = 1 + x * (A + y)    #    # Find the solutions!    #    # Checking bounds    if debug:        print("=== checking values ===")        print("* delta:", delta)        print("* delta < 0.292", delta < 0.292)        print("* size of e:", int(log(e)/log(2)))        print("* size of N:", int(log(N)/log(2)))        print("* m:", m, ", t:", t)    # boneh_durfee    if debug:        print("=== running algorithm ===")        start_time = time.time()    solx, soly = boneh_durfee(pol, e, m, t, X, Y)    # found a solution?    if solx > 0:        print("=== solution found ===")        if False:            print("x:", solx)            print("y:", soly)        d = int(pol(solx, soly) / e)        print("private key found:", d)    else:        print("=== no solution was found ===")    if debug:        print(("=== %s seconds ===" % (time.time() - start_time)))if __name__ == "__main__":    example()

RSA LSB Oracle Attack

适用情况:可以选择密文发送给服务器,并且服务器返回解密密文后的明文的最低位(LSB)

在一次RSA加密中,明文为m,模数为n,加密指数为e,密文为c。我们可以构造出$c’\equiv ((2^e)\cdot c) \equiv ((2^e)\cdot (m^e))\equiv ((2\cdot m)^e) \pmod n$, 因为m的两倍可能大于n,所以经过解密得到的明文是 $m’\equiv (2\cdot m)\pmod n$ 。我们还能够知道 m’ 的最低位lsb 是1还是0。 因为n是奇数,而$2\cdot m$是偶数,所以如果lsb 是0,说明$(2\cdot m)%n$ 是偶数,没有超过n,即$m<\frac{n}{2}$,反之则$m>\frac{n}{2}$。

举个例子就能明白2 % 3=2 是偶数,而4 % 3=1 是奇数。以此类推,构造密文$c’’\equiv 4^e\cdot c \pmod n$ 使其解密后为$m’’\equiv (4\cdot m)\pmod n$ ,判断$m’’$ 的奇偶性可以知道m 和 $\frac{n}{4}$ 的大小关系。所以我们就有了一个二分算法,可以在对数时间内将m的范围逼近到一个足够狭窄的空间。

exp:

123456789101112131415161718
def get_lsb(c):    sh.recvuntil(">")    sh.sendline(str(c))    rev = sh.recvuntil("\n")[:-1]    return int(rev)def oracle(c,n,t):    L=0    H=n    for i in range(1024):        c = (t*c) % n        if get_lsb(c) == 0:            H = (L+H) // 2        else:            L = (L+H) // 2    return long_to_bytes(L)oracle(c, n, pow(m,2,n))

上面的脚本在最后会出现一些误差,大约一到两个字符,具体原因尚不明确,但是下面这个脚本是没有误差的。

1234567891011121314151617181920
def lsb_oracle_attack(encrypted_flag, n, e):    flag_count = n_count = 1    flag_lower_bound = 0    flag_upper_bound = n    ciphertext = encrypted_flag    mult = 1    while flag_upper_bound > flag_lower_bound + 1:        ciphertext = (ciphertext * pow(2, e, n)) % n        flag_count *= 2        n_count = n_count * 2 - 1        print("bit = %d" % mult)        mult += 1        data=get_lsb(str(ciphertext))        if(data==1):            flag_upper_bound = n * n_count / flag_count        else:            flag_lower_bound = n * n_count / flag_count            n_count += 1    return flag_upper_bound

选择密文攻击

适用情况:可以构造任意密文,服务器会解密密文并返回对应明文。【LSB Oracle Attack的简易版本】

利用RSA的乘法同态性质就好。

这个好理解,在一个RSA加密过程中,明文为m,密文为c,模数为n,加密指数为e,

选取$x$以满足$gcd(x,n) =1$ 从而使$x$模$n$的逆存在,

构造密文$c’ \equiv c\cdot (x^e) \pmod n$使解密后明文为$m’ \equiv (m\cdot x) \pmod n$,

则$m\equiv m’\cdot x^{-1} \pmod n $。

可参看模意义下的运算法则部分。【$x^{-1} = inverse(x,n)$】

RSA经典非预期

这里最后来一个RSA偶尔出现的经典非预期

如果出题人没有设置好明文填充,而是单单把flag作为明文的话,通常情况下这个明文都会比较短,短到$m<p$,($p$是模数$n$的一个分解)那么我们就可以直接利用$p$作为模数然后正常求解RSA就可以求解得到明文了。2020祥云杯的more_calc这一题就可以直接这样子一把梭。(预期解的原理推起来还挺麻烦的,回头可以记录一下)

至于为啥?$c \equiv m^e \pmod n \rightarrow c \equiv m^e \pmod p$,好推的,自己试试~

Coppersmith相关

移步密码学学习笔记 之 Coppersmith’s Method

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年8月24日23:36:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   密码学学习笔记 之 RSA的一些套路https://cn-sec.com/archives/3093521.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息