文章

RSA 加密及一些攻击方式

原理

随机生成两个素数,p , q

令n = p*q

由欧拉公式计算出φ(n) = (p-1)(q-1)

规定e,使得e满足1<e<φ(n),且gcd(e,φ(n)) = 1,一般e=65537或0x10001

此时就有了公钥=(e,n)

计算私钥

计算d,使得d满足ed≡1mod φ(n),即称d是e在模φ(n)下的逆元

得到私钥=(d,n)

有关欧拉公式

  • n=pe_1_1pe_2_2dotspe_k_kn=p^{e\_1}\_1p^{e\_2}\_2\\dots p^{e\_k}\_kn=pe_1_1pe_2_2dotspe_k_k
  • varphi(n)=n(1frac1p_1)(1frac1p_2)dots(1frac1p_k)\\varphi(n)=n(1-\\frac{1}{p\_1})(1-\\frac{1}{p\_2})\\dots (1-\\frac{1}{p\_k})varphi(n)=n(1frac1p_1)(1frac1p_2)dots(1frac1p_k)
  • varphi(n)=pe_11_1pe_21_2dotspe_k1_k(p_11)(p_21)dots(p_k1)\\varphi(n)=p^{e\_1-1}\_1p^{e\_2-1}\_2\\dots p^{e\_k-1}\_k(p\_1-1)(p\_2-1)\\dots (p\_k-1)varphi(n)=pe_11_1pe_21_2dotspe_k1_k(p_11)(p_21)dots(p_k1)
  • gcd(n,x)=1gcd(n,x)=1gcd(n,x)=1时,满足xvarphi(n)equiv1modnx^{\\varphi(n)}\\equiv 1\\ mod\\ nxvarphi(n)equiv1modn

RSA加密和解密

规定m表示明文,c表示密文

加密:c≡me mod n

解密:m≡cd mod n

解密方式

正常解密

  • 此方法适用于已知p,q,e,cp,q,e,cp,q,e,c的情况下

可以使用RSA Tool 2工具解密或者编写如下脚本

from Crypto.Util.number import *
p = ?
q = ?
e = ?
c = ?
n = p*q
d = inverse(e,(p-1) * (q-1))
m = pow(c,d,n)
print(long_to_bytes(m))

dp,dq泄露

  • 此方法使用于已知p,q,d_p,d_q,cp,q,d\_{p},d\_{q},cp,q,d_p,d_q,c的情况下
  • 然而使用此方法前可以先尝试常用e值,在发现无法确定e值的情况下再使用

实例BUUCTF-Crypto-21.RSA1

理论推导

  • dp,dq泄露
  • 理论推导 红色字 为重要结论

becausemequivcd(modn)\\because m \\equiv c^d(mod\\ n)becausemequivcd(modn)

thereforem=cd+kn=cd+kcdotpq\\therefore m = c^d+kn = c^d+k\\cdot pqthereforem=cd+kn=cd+kcdotpq

m_1equivcd(modp),m_2equivcd(modq)m\_{1} \\equiv c^d(mod\\ p)\\ ①\\ ,\\ m\_{2} \\equiv c^d(mod\\ q)\\ ②m_1equivcd(modp),m_2equivcd(modq)

Rightarrowcd=m_1+tp①\\Rightarrow c^d=m\_{1}+tpRightarrowcd=m_1+tp

代入②Rightarrowm_2equivm_1+tp(modq)代入②\\Rightarrow m\_{2}\\equiv m\_{1}+tp(mod\\ q)代入Rightarrowm_2equivm_1+tp(modq)

thereforem_2=m_1+tcdotp+rcdotqRightarrowm_2m_1=tcdotp+rcdotq\\therefore m\_{2}=m\_{1}+t\\cdot p+r\\cdot q\\ \\Rightarrow m\_{2}-m\_{1}=t\\cdot p+r\\cdot qthereforem_2=m_1+tcdotp+rcdotqRightarrowm_2m_1=tcdotp+rcdotq

thereforem_2m_1=tcdotp(modq)\\therefore m\_{2}-m\_{1}=t\\cdot p(mod\\ q)thereforem_2m_1=tcdotp(modq)

therefore(m_2m_1)cdotp1equivt(modq)\\therefore (m\_{2}-m\_{1})\\cdot p^{-1}\\equiv t(mod\\ q)therefore(m_2m_1)cdotp1equivt(modq)

Rightarrowt=(m_2m_1)cdotp1(modq)\\Rightarrow t=(m\_{2}-m\_{1})\\cdot p^{-1}(mod\\ q)Rightarrowt=(m_2m_1)cdotp1(modq)

\\therefore c^d=\[(m\_{2}-m\_{1})\\cdot p^{-1}(mod\\ q)\]p+m\_{1}

becausemequivcd(modn)\\because m\\equiv c^d(mod\\ n)becausemequivcd(modn)

\\therefore {\\color{Red} m\\equiv \[\[(m\_{2}-m\_{1})\\cdot p^{-1}(mod\\ q)\]p+m\_{1}\]mod\\ n}

dequivd_pmod(p1),dequivd_qmod(q1)d\\equiv d\_{p}mod(p-1)\\ , \\ d\\equiv d\_{q}mod(q-1)dequivd_pmod(p1),dequivd_qmod(q1)

m_1equivcdmodp,m_2equivcdmodqm\_{1} \\equiv c^dmod\\ p\\ ,\\ m\_{2} \\equiv c^dmod\\ qm_1equivcdmodp,m_2equivcdmodq

\\Rightarrow m\_{1} \\equiv c^\\left .d\_{p}\\ +k\_{1}\\cdot (p-1)\\right.\\ mod\\ p\\ ,\\ m\_{2} \\equiv c^\\left .d\_{q}\\ +k\_{2}\\cdot (q-1)\\right.\\ mod\\ p

\\because c^\\left.p-1\\right.\\equiv 1\\ mod\\ p

\\Rightarrow {\\color{Red} m\_{1}\\equiv c^\\left .d\_{p}\\right . mod\\ p\\ ,\\ m\_{2}\\equiv c^\\left .d\_{q}\\right . mod \\ q}

使用脚本解密

from Crypto.Util.number import *

p = ?
q = ?
dp = ?
dq = ?
c = ?

mp = pow(c,dp,p)
mq = pow(c,dq,q)

pi = inverse(p,q)

m = ((((mq-mp)pi)%q)p+mp)%(p*q)

print(long_to_bytes(m))

dp泄露

  • 此方法使用于已知e,n,d_p,ce,n,d\_{p},ce,n,d_p,c的情况下

实例BUUCTF-Crypto-27.RSA2

理论推导

  • dp泄露
  • 理论推导 红色字 为重要结论

d_pequivdmod(p1)d\_{p} \\equiv d mod (p-1)d_pequivdmod(p1)

Rightarrowdcdote=k_1(p1)+d_pcdote\\Rightarrow d\\cdot e = k\_{1}(p-1)+d\_{p}\\cdot eRightarrowdcdote=k_1(p1)+d_pcdote

becausedcdote=1mod(p1)(q1)Rightarrowdcdote=k_2(p1)(q1)+1\\because d\\cdot e = 1mod(p-1)(q-1) \\Rightarrow d\\cdot e=k\_{2}(p-1)(q-1)+1becausedcdote=1mod(p1)(q1)Rightarrowdcdote=k_2(p1)(q1)+1

thereforecolorRed(p1)(k_2(q1)k_1)+1=d_pe\\therefore {\\color{Red} (p-1)(k\_{2}(q-1)-k\_{1})+1 = d\_{p}e}thereforecolorRed(p1)(k_2(q1)k_1)+1=d_pe

becaused_p<d\\because d\_{p}<dbecaused_p<d

therefore(k_2(q1)k_1)=xin(1,e)\\therefore (k\_{2}(q-1)-k\_{1})=x\\in (1,e)therefore(k_2(q1)k_1)=xin(1,e)

from Crypto.Util.number import *
e = 65537
n = ?
dp = ?
c = ?
a = dp*e-1
for x in range(2,e):
    if a%x == 0:
        p = a//x+1
        if n%p == 0:
            q = n//p
            break
d = inverse(e,(p-1)*(q-1))
m = pow(c,d,n)
print(long_to_bytes(m))

共模攻击

  • 此方法使用于已知n,e_1,e_2,c_1,c_2n,e\_{1},e\_{2},c\_{1},c\_{2}n,e_1,e_2,c_1,c_2的情况下
  • gcd(e_1,e_2)=1gcd(e\_{1},e\_{2})=1gcd(e_1,e_2)=1

实例BUUCTF-Crypto-28.RSA3

理论推导

  • 共模攻击
  • 理论推导 红色字 为重要结论
  • 存在两种密钥对同一明文进行加密
  • 下述为针对此题特殊情况下的特殊推导,需满足gcd(e_1,e_2)=1gcd(e\_{1},e\_{2})=1gcd(e_1,e_2)=1

c\_{1}=m^\\left. e\_{1}\\right. mod\\ n\\ \\&\\ m=c\_{1}^\\left.d\_{1}\\right.mod\\ n

c\_{2}=m^\\left. e\_{2}\\right. mod\\ n\\ \\&\\ m=c\_{2}^\\left.d\_{2}\\right.mod\\ n

构造一对(s_1,s_2)满足e_1s_1+e_2s_2=1其中s_1,s_2inZ,s_1>0,s_2<0构造一对(s\_{1},s\_{2})满足e\_{1}s\_{1}+e\_{2}s\_{2}=1其中s\_{1},s\_{2}\\in Z,s\_{1}>0,s\_{2}<0构造一对(s_1,s_2)满足e_1s_1+e_2s_2=1其中s_1,s_2inZ,s_1>0,s_2<0

c\_{1}=m^\\left. e\_{1}\\right. mod\\ n \\Rightarrow c\_{1}^\\left. s\_{1}\\right. =m^\\left. e\_{1}s\_{1}\\right. mod\\ n ①

c\_{2}=m^\\left. e\_{2}\\right. mod\\ n \\Rightarrow c\_{2}^\\left. s\_{2}\\right. =m^\\left. e\_{2}s\_{2}\\right. mod\\ n ②

①\\times ②\\Rightarrow c\_{1}^\\left. s\_{1}\\right. c\_{2}^\\left. s\_{2}\\right. mod\\ n = m^\\left. e\_{1}s\_{1}+e\_{2}s\_{2}\\right. mod\\ n

\\Rightarrow c\_{1}^\\left. s\_{1}\\right. c\_{2}^\\left. s\_{2}\\right. mod\\ n=m\\ mod\\ n

becausem=cdmodnthereforem<n\\because m=c^d mod\\ n \\therefore m<nbecausem=cdmodnthereforem<n

\\therefore {\\color{red}m=c\_{1}^\\left. s\_{1}\\right. c\_{2}^\\left. s\_{2}\\right. mod\\ n}

需要注意的是推论到上面就结束了,但是实际计算中上述式子计算过于复杂,所以应当使用下述式子需要注意的是推论到上面就结束了,但是实际计算中上述式子计算过于复杂,所以应当使用下述式子需要注意的是推论到上面就结束了,但是实际计算中上述式子计算过于复杂,所以应当使用下述式子

{\\color{red}m=(c\_{1}^\\left. s\_{1}\\right. mod\\ n\\cdot c\_{2}^\\left. s\_{2}\\right. mod\\ n)mod\\ n}

以及在计算s_1,s_2过程中可以采用逆元的方式以及在计算s\_{1},s\_{2}过程中可以采用逆元的方式以及在计算s_1,s_2过程中可以采用逆元的方式

(e_1e2)s_1+e2(s_1+s_2)=1(e\_{1}-e{2})s\_{1}+e{2}(s\_{1}+s\_{2})=1(e_1e2)s_1+e2(s_1+s_2)=1

(e_1e2)s_1equiv1mode_2(e\_{1}-e{2})s\_{1}\\equiv 1\\ mod\\ e\_{2}(e_1e2)s_1equiv1mode_2

s\_{1}=(e\_{1}-e{2})^\\left. -1\\right.

  • 由此计算出一对(s_1,s_2)(s\_{1},s\_{2})(s_1,s_2),进而算出明文m
  • 编写脚本
from Crypto.Util.number import *
n = ?
c1 = ?
c2 = ?
e1 = ?
e2 = ?
e1_e2 = e1-e2
s1 = inverse(e1_e2,e2)
s2 = (1-e1*s1)//e2
m = pow(c1,s1,n)*pow(c2,s2,n)%n
print(long_to_bytes(m))

低加密指数广播攻击

对于同一个明文mmm,使用同一个低加密指数eee和一组不同的nnn进行加密,得到一组c_ic\_ic_i,则有

c_i=memodn_ic\_i = m^e \\mod n\_ic_i=memodn_i

可以注意到对(c_i,n_i)(c\_i,n\_i)(c_i,n_i)使用 CRT 合并那么就能得到mem^eme

于是就能通过开eee次方根的方式得到mmm

维纳攻击

  • 此方法使用于eee过大或过小的情况下
  • 本质:满足d<frac13Nfrac14d < \\frac{1}{3} N^{\\frac{1}{4} }d<frac13Nfrac14则一定可以分解NNN
  • 大致解密过程是用RSAwienerHacker库来攻击得到d
  • 板子

实例BUUCTF-Crypto-40.rsa2

from RSAwienerHacker import *
n = ?
e = ?
d = hack_RSA(e,n)

此攻击有两种方法可以实现,一种是用连分数实现,还有一种方法是用格基规约的方式实现的(据一大佬说,所有的连分数实现的攻击方法都能转化为格基规约的方式,我没有研究过这个问题)

连分数是一种表示实数的方式,可以将一个实数表示为整数部分和分数部分的递归形式。连分数的形式为

x=a_0+frac1a_1+frac1a_2+frac1a_3+cdotsx = a\_0 + \\frac{1}{a\_1 + \\frac{1}{a\_2 + \\frac{1}{a\_3 + \\cdots}}}x=a_0+frac1a_1+frac1a_2+frac1a_3+cdots

一般可以将这组a_ia\_ia_i简写成\[a\_0;a\_1,a\_2,a\_3,\\cdots\]

在 RSA 的计算中,有edequiv1modvarphi(n)ed \\equiv 1 \\mod \\varphi(n)edequiv1modvarphi(n)

于是能将其变形为leftfracevarphi(n)frackdright=frac1dvarphi(n)\\left|\\frac{e}{\\varphi(n)}-\\frac{k}{d}\\right|=\\frac{1}{d\\varphi(n)}leftfracevarphi(n)frackdright=frac1dvarphi(n)

又由于varphi(n)approxn\\varphi(n)\\approx nvarphi(n)approxn

所以有leftfracenfrackdright=leftfracednkndright=leftfrac1k(nvarphi(n))ndright<leftfrack(p+q1)ndright\\left|\\frac{e}{n}-\\frac{k}{d}\\right|=\\left|\\frac{ed-nk}{nd}\\right|=\\left|\\frac{1-k(n-\\varphi(n))}{nd}\\right|<\\left|\\frac{k(p+q-1)}{nd}\\right|leftfracenfrackdright=leftfracednkndright=leftfrac1k(nvarphi(n))ndright<leftfrack(p+q1)ndright

p<q<2pp < q < 2pp<q<2p,则有p+q1<3p<3sqrtnp+q-1<3p<3\\sqrt{n}p+q1<3p<3sqrtn

同时有kvarphi(n)<edk\\varphi(n) < edkvarphi(n)<ed,于是k<d<frac13nfrac14k < d < \\frac{1}{3}n^{\\frac{1}{4}}k<d<frac13nfrac14

带入后得到\\left|\\frac{e}{n}-\\frac{k}{d}\\right|<\\frac{\\frac{1}{3}n^{\\frac{1}{4}}\\cdot3\\sqrt{n}}{nd}=\\frac{1}{n^\\frac{1}{4}d}<\\frac{1}{2d^2}

Legendre’s theorem[1] 指出对于任意一个实数 xxx,若有最简分数fracab\\frac{a}{b}fracab其中gcd(a,b)=1gcd(a,b)=1gcd(a,b)=1,且满足

leftxfracabright<frac1b2\\left| x – \\frac{a}{b} \\right| < \\frac{1}{b^2}leftxfracabright<frac1b2

那么该最简分数fracab\\frac{a}{b}fracab一定是xxx的某项渐进分数

于是对于leftfracenfrackdright<frac12d2\\left|\\frac{e}{n}-\\frac{k}{d}\\right|<\\frac{1}{2d^2}leftfracenfrackdright<frac12d2只需要计算fracen\\frac{e}{n}fracen的每一项的渐进分数,并且判断是否满足判别式,从而得到私钥ddd

参考文献

[1] Dujella A. Continued fractions and RSA with small secret exponent[J]. arXiv preprint cs/0402052, 2004.

易得 ed+kvarphi(n)=1ed+k\\varphi(n)=1ed+kvarphi(n)=1,所以 edkn=k(p+q1)+1ed-kn=k(p+q-1)+1edkn=k(p+q1)+1,显然有 leftkright<varphi(n)<n\\left|k\\right| < \\varphi(n) < nleftkright<varphi(n)<n

同时 leftkright=fraced1varphi(n)approxfracedn\\left|k\\right|=\\frac{ed-1}{\\varphi(n)}\\approx \\frac{ed}{n}leftkright=fraced1varphi(n)approxfracedn,可以知道 k 和 d 的规模相近

构造这样的格,用 K 来配平格基的行列式

(d,k) \\left(\\begin{matrix} e & K \\\\ n & 0 \\\\ \\end{matrix}\\right) =(ed+kn,dK)

检查是否符合_Hermite_定理,由经验可得dKleed+kndK\\le ed+kndKleed+kn

leftleftvecvrightright<frac12leftleftmathcalLrightrightfrac12\\left|\\left|\\vec{v}\\right|\\right|<\\frac{1}{2}{\\left|\\left|\\mathcal{L}\\right|\\right|}^{\\frac{1}{2}}leftleftvecvrightright<frac12leftleftmathcalLrightrightfrac12

得到 sqrt(ed+kn)2+dK2approxed+kn<frac12sqrtnK\\sqrt{(ed+kn)^2+dK^2}\\approx ed+kn<\\frac{1}{2}\\sqrt{nK}sqrt(ed+kn)2+dK2approxed+kn<frac12sqrtnK

由于ed+kned+kned+kn2dp2dp2dp规模相近,所以令nnn的规模约为22x2^{2x}22xddd的规模约为2y2^y2yKKK的规模约为22z2^{2z}22z,于是就有

\\left\\{\\begin{matrix} x+y+1 < x+z\\\\ x+y+1 > y+2z \\end{matrix}\\right.

所以得到能攻击成功的约束条件约为 KapproxsqrtnK\\approx \\sqrt{n}Kapproxsqrtnd\\approx \\sqrt\[4\]{n},然后规约,对得到的向量进行筛选,最终得到ddd

# Sage
from Crypto.Util.number import *

n = ?
e = ?
c = ?

Ge = Matrix(ZZ, 2, 2, [e, isqrt(n), n, 0])
L = Ge.LLL()
for row in L:
    if row[1]%isqrt(n) == 0:
        d = row[1]//isqrt(n)
        m = pow(c, d, n)
        print(long_to_bytes(m))
        break

扩展维纳攻击

参考 CTF wiki

两个低解密指数

from Crypto.Util.number import *

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 = inverse(e,phi)

三个低解密指数

e1 = ?
e2 = ?
e3 = ?
n  = ?
a = 2/5
D = diagonal_matrix(ZZ,[int(n^1.5), n, int(n^(a+1.5)), int(n^0.5), int(n^(a+1.5)), int(n^(a+1)), int(n^(a+1)), 1])
L = Matrix(ZZ,[[1, -n,   0,   n^2,   0,      0,      0,     -n^3],
               [0, e1, -e1, -n*e1, -e1,      0,   n*e1,   n^2*e1],
               [0,  0,  e2, -n*e2,   0,   n*e2,      0,   n^2*e2],
               [0,  0,   0, e1*e2,   0, -e1*e2, -e1*e2, -n*e1*e2],
               [0,  0,   0,     0,  e3,  -n*e3,  -n*e3,   n^2*e3],
               [0,  0,   0,     0,   0,  e1*e3,      0, -n*e1*e3],
               [0,  0,   0,     0,   0,      0,  e2*e3, -n*e2*e3],
               [0,  0,   0,     0,   0,      0,      0, e1*e2*e3]]) * D
Ge = L.LLL()[0]
x = vector(ZZ, Ge) / L
phi = int(x[1]/x[0]*e1)
d = inverse(e,phi)

通用情况

from Crypto.Util.number import *
isdigit = lambda x: ord('0') <= ord(x) <= ord('9')

def my_permutations(g, n):
    sub = []
    res = []
    def dfs(s, prev):
        if len(s) == n:
            res.append(s[::])
        for i in g:
            if i in s or i < prev:
                continue
            s.append(i)
            dfs(s, max(prev, i))
            s.remove(i)
    dfs(sub, 0)
    return res

class X3NNY(object):
    def __init__(self, exp1, exp2):
        self.exp1 = exp1
        self.exp2 = exp2
    
    def __mul__(self, b):
        return X3NNY(self.exp1 * b.exp1, self.exp2 * b.exp2)

    def __repr__(self):
        return '%s = %s' % (self.exp1.expand().collect_common_factors(), self.exp2)

class X_Complex(object):
    def __init__(self, exp):
        i = 0
        s = '%s' % exp
        while i < len(s):
            if isdigit(s[i]):
                num = 0
                while i < len(s) and isdigit(s[i]):
                    num = num*10 + int(s[i])
                    i += 1
                if i >= len(s):
                    self.b = num
                elif s[i] == '*':
                    self.a = num
                    i += 2
                elif s[i] == '/':
                    i += 1
                    r = 0
                    while i < len(s) and isdigit(s[i]):
                        r = r*10 + int(s[i])
                        i += 1
                    self.b = num/r
            else:
                i += 1
        if not hasattr(self, 'a'):
            self.a = 1
        if not hasattr(self, 'b'):
            self.b = 0

def WW(e, d, k, g, N, s):
    return X3NNY(e*d*g-k*N, g+k*s)
def GG(e1, e2, d1, d2, k1, k2):
    return X3NNY(e1*d1*k2- e2*d2*k1, k2 - k1)

def W(i):
    e = eval("e%d" % i)
    d = eval("d%d" % i)
    k = eval("k%d" % i)
    return WW(e, d, k, g, N, s)

def G(i, j):
    e1 = eval("e%d" % i)
    d1 = eval("d%d" % i)
    k1 = eval("k%d" % i)
    
    e2 = eval("e%d" % j)
    d2 = eval("d%d" % j)
    k2 = eval("k%d" % j)
    
    return GG(e1, e2, d1, d2, k1, k2)

def R(e, sn): # min u max v
    ret = X3NNY(1, 1)
    n = max(e)
    nn = len(e)
    l = set(i for i in range(1, n+1))
    debug = ''
    u, v = 0, 0
    for i in e:
        if i == 1:
            ret *= W(1)
            debug += 'W(%d)' % i
            nn -= 1
            l.remove(1)
            u += 1
        elif i > min(l) and len(l) >= 2*nn:
            ret *= G(min(l), i)
            nn -= 1
            debug += 'G(%d, %d)' % (min(l), i)
            l.remove(min(l))
            l.remove(i)
            v += 1
        else:
            ret *= W(i)
            l.remove(i)
            debug += 'W(%d)' % i
            nn -= 1
            u += 1
    # print(debug, end = ' ')
    return ret, u/2 + (sn - v) * a

def H(n):
    if n == 0:
        return [0]
    if n == 2:
        return [(), (1,), (2,), (1, 2)]
    ret = []
    for i in range(3, n+1):
        ret.append((i,))
        for j in range(1, i):
            for k in my_permutations(range(1, i), j):
                ret.append(tuple(k + [i]))
    return H(2) + ret
    
def CC(exp, n):
    cols = [0 for i in range(1<<n)]
    
    # split exp
    texps = ('%s' % exp.exp1.expand()).strip().split(' - ')
    ops = []
    exps = []
    for i in range(len(texps)):
        if texps[i].find(' + ') != -1:
            tmp = texps[i].split(' + ')
            ops.append(0)
            exps.append(tmp[0])
            for i in range(1, len(tmp)):
                ops.append(1)
                exps.append(tmp[i])
        else:
            ops.append(0)
            exps.append(texps[i])
    if exps[0][0] == '-':
        for i in range(len(exps)):
            ops[i] = 1-ops[i]
        exps[0] = exps[0][1:]
    else:
        ops[0] = 1
    # find e and N
    l = []
    for i in range(len(exps)):
        tmp = 1 if ops[i] else -1
        en = []
        j = 0
        while j < len(exps[i]):
            if exps[i][j] == 'e':
                num = 0
                j += 1
                while isdigit(exps[i][j]):
                    num = num*10 + int(exps[i][j])
                    j += 1
                tmp *= eval('e%d' % num)
                en.append(num)
            elif exps[i][j] == 'N':
                j += 1
                num = 0
                if exps[i][j] == '^':
                    j += 1
                    while isdigit(exps[i][j]):
                        num = num*10 + int(exps[i][j])
                        j += 1
                if num == 0:
                    num = 1
                tmp *= eval('N**%d' % num)
            else:
                j += 1
        if tmp == 1 or tmp == -1:
            l.append((0, ()))
        else:
            l.append((tmp, tuple(sorted(en))))
    
    # construct h
    mp = H(n)
    for val, en in l:
        cols[mp.index(en)] = val
    # print(cols)
    return cols

def EWA(n, elist, NN, alpha):
    mp = H(n)
    var('a')
    S = [X_Complex(n*a)]
    cols = [[1 if i == 0 else 0 for i in range(2^n)]]
    for i in mp[1:]:
        eL, s = R(i, n)
        cols.append(CC(eL, n))
        S.append(X_Complex(s))
    
    alphaA,alphaB = 0, 0
    for i in S:
        alphaA = max(i.a, alphaA)
        alphaB = max(i.b, alphaB)
    # print(alphaA, alphaB)
    D = []
    for i in range(len(S)):
        # print((alphaA-S[i].a), (alphaB - S[i].b))
        D.append(
            int(NN^((alphaA-S[i].a)*alpha + (alphaB - S[i].b)))
        )
    kw = {'N': NN}
    for i in range(len(elist)):
        kw['e%d' % (i+1)] = elist[i]

    B = Matrix(ZZ, Matrix(cols).T(**kw)) * diagonal_matrix(ZZ, D)
    L = B.LLL(0.5)
    v = Matrix(ZZ, L[0])
    x = v * B**(-1)
    phi = int(x[0,1]/x[0,0]*elist[0])
    return phi

def attack(NN, elist, alpha):
    phi = EWA(len(elist), elist, NN, alpha)
    print(phi)
    return phi

n = ?   # n是d的比特位数
NN = ?
elist = [e1,e2,e3, ...,en]

alpha = n / int(NN).bit_length()
for i in range(1, len(elist)+1):
    var("e%d" % i)
    var("d%d" % i)
    var("k%d" % i)
g, N, s = var('g'), var('N'), var('s')

for i in range(len(elist)):
    elist[i] = Integer(elist[i])
phi = attack(NN, elist, alpha)
d = inverse(e, phi)

Boneh Durfee攻击

  • 这是维纳攻击的一种延伸,拓展了可以攻击的d的范围
  • d<n0.292d<n^{0.292}d<n0.292

GitHub仓库,在此基础上修改

import time
from Crypto.Util.number import *

"""
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
"""
debug = True

"""
Setting strict to true will stop the algorithm (and
return (-1, -1)) if we don't have a correct 
upperbound on the determinant. Note that this 
doesn't necesseraly mean that no solutions 
will be found since the theoretical upperbound is
usualy far away from actual results. That is why
you should probably use `strict = False`
"""
strict = False

"""
This is experimental, but has provided remarkable results
so far. It tries to reduce the lattice as much as it can
while keeping its efficiency. I see no reason not to use
this option, but if things don't work, you should try
disabling it
"""
helpful_only = True
dimension_min = 7 # stop removing if lattice reaches that dimension

############################################
# Functions
##########################################

# display stats on helpful vectors
def 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 X
def 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, soly

def example(N,e,delta,m=None):
    ############################################
    # How To Use This Script
    ##########################################

    #
    # Lattice (tweak those values)
    #

    # you should tweak this (after a first run), (e.g. increment it until a solution is found)
    m = 4 if m == None else m # 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))
    return d

if __name__ == "__main__":
    n = ?
    e = ?
    c = ?
    # the hypothesis on the private exponent (the theoretical maximum is 0.292)
    delta = 0.28 # this means that d < N^delta
    d = example(n,e,delta)
    print(long_to_bytes(int(pow(c,d,n))))

e和φ不互质

1.二次剩余+CRT

  • 此方法适用于eee非质,即gcd(e,varphi)=2gcd(e,\\varphi )=2gcd(e,varphi)=2
  • 求解任意模数二次剩余先要求解奇素数模数二次剩余具体理论查看此链
  • 据此推导过程编写脚本

实例[0xGame 2024] Number-Theory-CRT

from Crypto.Util.number import *
import sympy
from sympy.ntheory.modular import crt
c = ?
e = ?
n = ?
p = ?
q = ?
phi = (p-1) * (q-1)
print(GCD(e,phi)) # 发现公约数2,分析得到二次剩余
 
def find_quadratic_residues(a, p):
    # 首先检查 a 是否是模 p 下的二次剩余
    if not sympy.is_quad_residue(a, p):
        return None  # 如果 a 不是二次剩余,返回 None
    
    # 使用 sympy 的 nthroot_mod 找到一个解
    x = sympy.nthroot_mod(a, 2, p, all_roots=False)
    
    # 计算另一个解
    second_solution = p - x
    
    return (x, second_solution)
 
x1 = find_quadratic_residues(c,p)       # 求解模p下的二次剩余
x2 = find_quadratic_residues(c,q)       # 求解模q下的二次剩余
 
for i in x1:
    for j in x2:
        remainders = [i,j]
        mods = [p,q]
        m_ = crt(mods, remainders)[0]   # CRT合并得到模n的二次剩余解
        c_ = m_%n
 
        e_ = e//2
        d = inverse(e_,phi)
        m = pow(c_,d,n)
        print(long_to_bytes(m))

2.用Sage求解有限域上开方

  • 理论来说应该可以解决任意公约数的不互质问题,但是运行时间较长
  • 自认为Method 1适用于gcd小、n大的情况,Method 2适用于gcd大、n小的情况
from Crypto.Util.number import *
from sympy.ntheory.modular import crt

p = ?
q = ?
n = ?
c = ?
e = ?

phi = (p-1)*(q-1)
gcd = GCD(e,phi)
d = inverse(e//gcd,phi)

# Method 1 求解同余方程
R.<x> = PolynomialRing(Zmod(p))
f = x^gcd - c
res1 = f.roots(multiplicities=False)

R.<x> = PolynomialRing(Zmod(q))
f = x^gcd - c
res2 = f.roots(multiplicities=False)

# Method 2 开根号
res1 = Zmod(p)(c).nth_root(gcd, all=True)
res2 = Zmod(q)(c).nth_root(gcd, all=True)

for i in res1:
    for j in res2:
        m = crt([p,q],[int(i),int(j)])
        if m is not None:
            try:
                print(long_to_bytes(int(pow(m[0],d,n))).decode())
            except Exception as e:
                continue

3.AMM算法

eeevarphi(n)\\varphi(n)varphi(n)不互质时,便无法通过逆元计算出ddd,此时可以使用 AMM 算法计算得到明文

例如 c=memodnc=m^e \\mod nc=memodn,如果 gcd(e,varphi(n))=ggcd(e,\\varphi(n))=ggcd(e,varphi(n))=g,那么就有 c=megtmodnc=m^{e’g^t} \\mod nc=megtmodn,只要计算模nnncccgtg^tgt 次方根即可得到能够正常解密的密文了

AMM算法就是计算有限域下的开方运算,然后用CRT合并即可

二次剩余

二次剩余是指对于一个模数 nnn,如果存在整数 aaa 使得 a2equivxmodna^2 \\equiv x \\mod na2equivxmodn,则称 xxx 是模 nnn 的二次剩余。否则称为二次非剩余。

Euler判别法:对于任意一个数xxx和奇素数ppp,若 xfracp12equiv1modpx^{\\frac{p-1}{2}} \\equiv 1 \\mod pxfracp12equiv1modp,则xxx为二次剩余。若 xfracp12equiv1modpx^{\\frac{p-1}{2}} \\equiv -1 \\mod pxfracp12equiv1modp,则xxx为二次非剩余。

已知xxx,求aaa的时候可以这样看

能注意到对于奇素数ppp一定能有这样的表示p1=2tcdotp-1=2^t\\cdot p1=2tcdot

t=1t=1t=1

xsequiv1modpx^s\\equiv 1 \\mod pxsequiv1modp

Rightarrowxfracs+12equivxfrac12modp\\Rightarrow x^{\\frac{s+1}{2}}\\equiv x^{\\frac{1}{2}} \\mod pRightarrowxfracs+12equivxfrac12modp

becausepmaequivxfrac12modp\\because \\pm a\\equiv x^{\\frac{1}{2}} \\mod pbecausepmaequivxfrac12modp

thereforeaequivpmxfracs+12modp\\therefore a\\equiv \\pm x^{\\frac{s+1}{2}} \\mod pthereforeaequivpmxfracs+12modp

t>1t>1t>1

x2t1cdotsequiv1modpx^{2^{t-1}\\cdot s}\\equiv 1 \\mod px2t1cdotsequiv1modp

尝试对左右两边同时开根号

x2t2cdotsequivpm1modpx^{2^{t-2}\\cdot s}\\equiv \\pm 1 \\mod px2t2cdotsequivpm1modp

为了能不断对左边开根号,于是有选择的配一个非二次剩余yyy用来保证右边始终为1

x2t2cdotsy2t1cdotscdotk_1equiv1modpx^{2^{t-2}\\cdot s}y^{2^{t-1}\\cdot s\\cdot k\_1}\\equiv 1 \\mod px2t2cdotsy2t1cdotscdotk_1equiv1modp

不断开根号

x2t3cdotsy2t2cdotscdotk_1+2t1cdotscdotk_2equiv1modpx^{2^{t-3}\\cdot s}y^{2^{t-2}\\cdot s\\cdot k\_1+2^{t-1}\\cdot s\\cdot k\_2}\\equiv 1 \\mod px2t3cdotsy2t2cdotscdotk_1+2t1cdotscdotk_2equiv1modp

xsyscdot(21cdotk_1+22cdotk_2+cdots+2t1cdotk_t1)equiv1modpx^sy^{s\\cdot(2^1\\cdot k\_1+2^2\\cdot k\_2+\\cdots+2^{t-1}\\cdot k\_{t-1})}\\equiv 1 \\mod pxsyscdot(21cdotk_1+22cdotk_2+cdots+2t1cdotk_t1)equiv1modp

于是和t=1t=1t=1时,一样的处理方法,就有

xfracs+12yscdot(k_1+21cdotk_2+cdots+2t2cdotk_t1)equivxfrac12equivpmamodpx^{\\frac{s+1}{2}}y^{s\\cdot(k\_1+2^1\\cdot k\_2+\\cdots+2^{t-2}\\cdot k\_{t-1})}\\equiv x^{\\frac{1}{2}} \\equiv \\pm a \\mod pxfracs+12yscdot(k_1+21cdotk_2+cdots+2t2cdotk_t1)equivxfrac12equivpmamodp

def AMM2(x, p, e):
    s = p-1
    t = 0
    while s % 2 == 0:
        t += 1
        s //= 2
    while True:
        if pow(y:=random.randint(1,p-1), (p-1)//2, p) != 1:
            break
    tmp1 = p-1
    tmp2 = 0
    for _ in range(t):
        tmp1, tmp2 = map(lambda x: x//2,[tmp1, tmp2])
        if pow(x, tmp1, p)*pow(y, tmp2, p) % p != 1:
            tmp2 += (p-1)//2
    return [ans:=pow(x, (tmp1+1)//2, p)*pow(y, tmp2//2, p) % p, p-ans]
高次方

对于高次方的情况,可以用类似的方法来考虑

对于 aeequivxmodpa^e \\equiv x \\mod paeequivxmodpeeep1p-1p1互质的时候直接RSA求解即可

同样的可以将Euler判别法拓展到高次方

xfracp1eequiv1modpx^{\\frac{p-1}{e}} \\equiv 1 \\mod pxfracp1eequiv1modp

对于任意的奇素数ppp一定能写成这样的形式 p1=etcdotp-1=e^t\\cdot p1=etcdot

于是依据Euler判别法可以得到

xet1cdotsequiv1modpx^{e^{t-1}\\cdot s}\\equiv 1 \\mod pxet1cdotsequiv1modp

同样也要对ttt的取值不同分析

t=1t=1t=1

这里要配一个kkk,为了方便和上式一样可以对指数除以eee

xfracks+1eequivxfrac1emodpx^{\\frac{ks+1}{e}}\\equiv x^{\\frac{1}{e}} \\mod pxfracks+1eequivxfrac1emodp

然后就能得到一个特解

a_0equivxfracks+1emodpa\_0\\equiv x^{\\frac{ks+1}{e}} \\mod pa_0equivxfracks+1emodp

t>1t>1t>1

选取一个非剩余yyyyfracp1enotequiv1modpy^{\\frac{p-1}{e}}\\not\\equiv 1 \\mod pyfracp1enotequiv1modp,很显然能得到关于模ppp下开eee次方的所有单位根构成的集合KKK

K=left1,yfracp1gcd(e,p1),y2cdotfracp1gcd(e,p1),cdots,y(gcd(e,p1)1)cdotfracp1gcd(e,p1)rightK=\\left\\{1, y^{\\frac{p-1}{gcd(e,p-1)}}, y^{2\\cdot \\frac{p-1}{gcd(e,p-1)}}, \\cdots, y^{(gcd(e,p-1)-1)\\cdot \\frac{p-1}{gcd(e,p-1)}}\\right\\}K=left1,yfracp1gcd(e,p1),y2cdotfracp1gcd(e,p1),cdots,y(gcd(e,p1)1)cdotfracp1gcd(e,p1)right

相似的持续进行开eee次方根操作,有

xkscdotet2equivK_imodpx^{ks\\cdot e^{t-2}}\\equiv K\_i \\mod pxkscdotet2equivK_imodp

很容易找到配平的数

xkscdotet2cdoty(gcd(e,p1)i)cdotfracp1gcd(e,p1)equiv1modpx^{ks\\cdot e^{t-2}}\\cdot y^{(gcd(e,p-1)-i)\\cdot\\frac{p-1}{gcd(e,p-1)}}\\equiv 1 \\mod pxkscdotet2cdoty(gcd(e,p1)i)cdotfracp1gcd(e,p1)equiv1modp

以此类推,然后能得到

xkscdotycdotsequiv1modpx^{ks}\\cdot y^{\\cdots}\\equiv 1 \\mod pxkscdotycdotsequiv1modp

然后和t=1t=1t=1时一样的处理方法,得到

xfracks+1ecdotycdotsequivxfrac1emodpx^{\\frac{ks+1}{e}}\\cdot y^{\\cdots}\\equiv x^{\\frac{1}{e}} \\mod pxfracks+1ecdotycdotsequivxfrac1emodp

然后已知将特解乘上所有单位根即得到所有的解

def AMM(x, p, e=2):
    gcds = GCD(e, p-1)
    s = p-1
    t = 0
    while s % e == 0:
        t += 1
        s //= e
    while True:
        if pow(y:=random.randint(1,p-1), (p-1)//e, p) != 1:
            break
    root_set = {pow(y,(p-1)//gcds*i,p):i for i in range(gcds)}
    k = -1*pow(s,-1, e) % e
    tmp1 = k*(p-1)
    tmp2 = 0
    for _ in range(t):
        tmp1, tmp2 = map(lambda x: x//e,[tmp1, tmp2])
        i = root_set[pow(x, tmp1, p)*pow(y, tmp2, p)%p]
        tmp2 = (tmp2 + (p-1)//gcds*(gcds-i))%(p-1)
    ans = pow(x, (tmp1+1)//e, p)*pow(y, tmp2//e, p)
    return [ans*i%p for i in root_set.keys()]

要注意的是,这里为了配平用到了kkk,这里计算了sss在模eee下的逆元,所以一般要求e是质数。非互质的情况下可以通过分解等多种方法来计算。

Rabin加密

  • 可以看作是RSA中e=2e=2e=2的特殊情况,且pequivqequiv3mod4p\\equiv q\\equiv 3\\ mod\\ 4pequivqequiv3mod4
  • 解法原理同上e和φ不互质

给定一些关于p,qp,qp,q计算后的值

  • 已知n,x_1p+y_1q,x_2p+y_2qn,x\_1p+y\_1q,x\_2p+y\_2qn,x_1p+y_1q,x_2p+y_2q
  • 已知n,(xp+yq)npqmodnn,(xp+yq)^{n-p-q}mod\\ nn,(xp+yq)npqmodn

[长城杯 2024]Crypto-rasnd

  • 已知p2+q2p^2+q^2p2+q2

[国城杯 2024]EZ_sign

# SageMath
N = ?
f = ZZ[I](N)
divisors_f = divisors(f)
for d in divisors_f:
    a,b = d.real(), d.imag()
    if a**2 + b**2 == N:
        p = abs(int(a))
        q = abs(int(b))
        if is_prime(p) and is_prime(q):
            print("p =",p)
            print("q =",q)
            break
  • 已知pqmodnp^q\\ mod\\ npqmodn
  • 易得pqmodn=pp^q\\ mod\\ n = ppqmodn=p

[HGAME 2024]ezRSA

部分泄露

已知p高位

Coppersmith 攻击

对于512位素数,需要未知至多227位

对于1024位素数,需要未知至多454\455位,可能性大致对半开

如果无法满足上述条件,可以爆破几位数

from Crypto.Util.number import *

n = ?
c = ?
e = 65537
p_high = ?

R.<x> = PolynomialRing(Zmod(n))
f = p_high + x
res = f.small_roots(X = 2^256,beta = 0.4)
if res != []:
    p = p_high + int(res[0])
    q = n // p
    d = inverse(e,(p-1)*(q-1))
    m = pow(c,d,n)
    print(long_to_bytes(int(m)))

已知m高位

[HGAME 2024]MidRSA

似乎此题的e通常为3,实测0x10001运行几分钟都没结果

from Crypto.Util.number import long_to_bytes

n = ?
c = ?
e = 3
m1 = ?
bits = ?

R.<x> = PolynomialRing(Zmod(n))
f = (m1 + x)^e - c
res = f.small_roots(X = 2^bits,beta = 1)

已知d高位

d高位低位泄露的主要推导基于此论文-An Attack on RSA Given a Small Fraction of the Private Key Bits

d高位泄露

满足上述条件时可以计算出tildek\\tilde{k}tildek然后通过恒定附加误差爆破即可得到正确kkk

再依据上述结论计算出x=pmodex= p\\ mod\\ ex=pmode

剪枝

p^(q>>nbits)

p = getPrime(lenth)
q = getPrime(lenth)
n = p*q
gift = p ^ (q >> bits)

主要思路是计算p和q,基于以下两个已知事实,去遍历确定每个比特位

  • 将 p、q 未确定的比特位全填 0,乘积应小于 n
  • 将 p、q 未确定的比特位全填 1,乘积应大于 n
from Crypto.Util.number import *
n = ?
c = ?
gift = ?
bits = 30
length = 512

def dfs(p, q, index):
    if index == 0:
        tmp = int(p,2)
        if n%tmp == 0:
            return tmp
        else:
            return 0
    tmp = bin(gift)[-index]
    for i in range(2):
        a = str(i)
        b = str(i ^ int(tmp))
        tmp1 = int(p + a, 2)
        tmp2 = int(q + b, 2)
        if (tmp1<<(index-1))*(tmp2<<(index+bits-1)) <= n and \
            (((tmp1+1)<<(index-1))-1)*(((tmp2+1)<<(index+bits-1))-1) >= n:
            tmp = dfs(p + a, q + b, index - 1)
            if tmp != 0:
                return tmp
    return 0

p = bin(gift)[2:2+bits]
q = ""

p = dfs(p, q, length-bits)
q = n // p
d = pow(0x10001, -1, (p-1)*(q-1))
flag = long_to_bytes(pow(c, d, n))
print(flag)

RSA Leak Oracle

RSA Byte Oracle

如果有一个预言机可以提供RSA解密操作,但是只会返回最后一个字节。可以在log_256nlog\_{256}nlog_256n的时间内计算出明文mmm

已知c=memodnc=m^e\\ mod\\ nc=memodnn,en,en,e

显然由于RSA是乘法同态的加密算法,于是可以构造一个已知的明文,加密后和密文相乘,使得解密后的明文乘上构造的明文,即

a=(256i)emodna = (256^i)^e\\ mod\\ na=(256i)emodn

acdequiv256immodn=256imknac^d \\equiv 256^im\\ mod\\ n=256^im-knacdequiv256immodn=256imkn

由于预言机只能给出最后一个字节所以我们实际上得到的是output=knmod256output = -kn\\ mod\\ 256output=knmod256

易得kequivn1outputmod256k\\equiv -n^{-1}output\\ mod\\ 256kequivn1outputmod256

很容易注意到这里kkk是对256取模的,也就是说当i>1i>1i>1时显然k<256ik<256^ik<256i并不能准确计算出kkk

但是能发现当i=1i=1i=1的时候很容易计算得到此时的k_1=n1output_1k\_1=-n^{-1}output\_1k_1=n1output_1k_1nle256mle(k_1+1)nk\_1n\\le 256m\\le (k\_1+1)nk_1nle256mle(k_1+1)n

那么当i=2i=2i=2时,有k_2nle2562mle(k_2+1)nk\_2n\\le 256^2m\\le (k\_2+1)nk_2nle2562mle(k_2+1)n,这两个范围肯定有并集因此可以有

\\left\\{\\begin{matrix} 256k\_1n\\le 256^2m\\le 256(k+1)n\\\\ k\_2n\\le 256^2m\\le (k\_2+1)n \\end{matrix}\\right. \\Rightarrow \\left\\{\\begin{matrix} 256k\_1<(k\_2+1)\\\\ k\_2<256(k\_1+1) \\end{matrix}\\right.

Rightarrowk_2=256k_1+t\\Rightarrow k\_2=256k\_1+tRightarrowk_2=256k_1+t

很amazing啊,推广后能发现k除以256的商正好就是上一轮的k而且余数也是能通过上述推导得来的,因此就能计算出k从而确定明文了。

MatrixRSA

此处要引入这么一个概念——一般线性群

概念

维基百科

一般线性群用来描述ntimesnn\\times nntimesnFFF域上的可逆矩阵构成的群,记作GL(n,F)GL(n,F)GL(n,F)GL_n(F)GL\_n(F)GL_n(F),常被称为线性群或矩阵群。

在有限域上

  • 有限域上矩阵A可逆Longleftrightarrow(det(A),N)=1有限域上矩阵A可逆\\Longleftrightarrow (det(A),N)=1有限域上矩阵A可逆Longleftrightarrow(det(A),N)=1

对于GL(n,Zmod(N))GL(n,Zmod(N))GL(n,Zmod(N))

当N是质数是可以得到阶的计算公式g=prod_i=0n1NnNig = \\prod\_{i=0}^{n-1} N^n-N^ig=prod_i=0n1NnNi

当N是合数时,则有N=p_1p_2p_3cdotsp_mN=p\_1p\_2p\_3\\cdots p\_mN=p_1p_2p_3cdotsp_m

同理能得到g=prod_j=1nprod_i=0n1p_jnp_jig = \\prod\_{j=1}^{n} \\prod\_{i=0}^{n-1} {p\_j}^n-{p\_j}^ig=prod_j=1nprod_i=0n1p_jnp_ji

从而有对于该群内的矩阵A就有Ag=IA^g=IAg=I

于是就可以重新仿照整数上对于欧拉函数的定义运用在矩阵上然后即可进行RSA计算

Common Prime RSA

p1p-1p1q1q-1q1拥有大素数因子的情况称为Common Prime RSA。

也就是说令pppqqq按如下方式生成

\\left\\{\\begin{matrix} p=2ga+1\\\\ q=2gb+1 \\end{matrix}\\right.

其中ggg是大素数,aaabbb互质。同时保证h=2gab+a+bh=2gab+a+bh=2gab+a+b为质数。

gapproxNfrac12g\\approx N^{\\frac{1}{2}}gapproxNfrac12

当这种情况下可以用Pollard rho算法进行整数分解,选取xN1+3modNx^{N-1}+3\\mod NxN1+3modN这个函数进行迭代

[1] McKee J F, Pinch R G E. Further attacks on server-aided RSA cryptosystems[J]. Unpublished manuscript, 1998, 104.

该算法在离散对数中讲过,此处不赘述。

根据这个解法的限制条件,不难发现当满足gapproxNfrac12g\\approx N^{\\frac{1}{2}}gapproxNfrac12时,aaabbb是较小数

*论文题

关键字:RSA、相同私钥、格基规约、格、同一明文多次加密、LLL

论文链接

实例

0 条评论