本文总结了一个基于格的攻击,该攻击针对的是多个共用同一个私钥的RSA实例。当私钥d \,d\,d满足一定条件时,即可根据这些RSA实例的公钥求出d \,d\,d,从而将这些RSA实例全部破解。
理解本文所需的前置背景:
- 格论,可以先看看格基规约算法:数学基础
- 格基规约算法的用途,见格基规约算法:算法详解
攻击描述
符号约定和假设
首先我们给出攻击中的假设条件和符号约定。设同一个RSA加密系统中有r \,r\,r个RSA实例共用同一个私钥d \,d\,d,这些实例的公钥分别为( e 1 , N 1 ) , … , ( e r , N r ) \, \left(e_1,\ N_1\right),\ldots,\left(e_r,\ N_r\right)\,(e1, N1),…,(er, Nr) 。不妨设N 1 , … , N r \,N_1,\ldots,\ N_r\,N1,…, Nr依次增大,并且由于是同一个系统中的实例,故认为N 1 , … , N r \, N_1,\ldots,\ N_r\,N1,…, Nr具有相同的比特长度,于是有N 1 < N 2 < ⋯ < N r < 2 N 1 \,N_1<N_2<\cdots<N_r<2N_1\,N1<N2<⋯<Nr<2N1。
由RSA加密的原理可以得到如下r \,r\,r个方程:
e 1 d = 1 + k 1 φ ( N 1 ) e 2 d = 1 + k 2 φ ( N 2 ) ⋮ e r d = 1 + k r φ ( N r ) \begin{array}{c} e_{1} d=1+k_{1} \varphi\left(N_{1}\right) \\ e_{2} d=1+k_{2} \varphi\left(N_{2}\right) \\ \vdots \\ e_{r} d=1+k_{r} \varphi\left(N_{r}\right) \end{array}e1d=1+k1φ(N1)e2d=1+k2φ(N2)⋮erd=1+krφ(Nr)
对任意的i \,i\,i,设e i < φ ( N i ) , φ ( N r ) = N i − s i \, e_i<\varphi\left(N_i\right)\ ,\ \ \varphi\left(N_r\right)=N_i-s_i\,ei<φ(Ni) , φ(Nr)=Ni−si.我们假定生成N 1 , … , N r \, N_1,\ldots,\ N_r\,N1,…, Nr 的素数具有相同的比特长度,则s i < 3 N 1 1 / 2 {\, s}_{i\ }<3N_1^{1/2}\,si <3N11/2 。并且最关键的,假设以下命题成立:若v \, v\,v是格L \, \mathcal{L}\,L中满足Minkowski’s bound的一个向量,则± v \, \pm v\,±v为格中所有的非零最短向量,除此以外没有其他向量范数为λ 1 ( L ) \,\lambda_{1}\!\left(\mathcal{L}\right)\,λ1(L) 。
攻击原理和方法
设M = ⌊ N r 1 / 2 ⌋ \, M=\left\lfloor N_r^{1/2}\right\rfloor\,M=⌊Nr1/2⌋ ,由之前假设中的等式可得方程组
d M = d M e 1 d − N 1 k 1 = 1 − k 1 s 1 e 2 d − N 2 k 2 = 1 − k 2 s 2 ⋮ e r − N r k r = 1 − k r s r \begin{array}{c} d M=d M \\ e_{1} d-N_{1} k_{1}=1-k_{1} s_{1} \\ e_{2} d-N_{2} k_{2}=1-k_{2} s_{2} \\ \vdots \\ e_{r}-N_{r} k_{r}=1-k_{r} s_{r} \end{array}dM=dMe1d−N1k1=1−k1s1e2d−N2k2=1−k2s2⋮er−Nrkr=1−krsr
下面将方程组写为矩阵形式。记
x r = ( d , k 1 , k 2 , ⋯ , k r ) , v r = ( d M , 1 − k 1 s 1 , ⋯ , 1 − k r s r ) \, x_r=\left(d,k_1,k_2,\cdots,k_r\right)\ ,v_r=\left(dM,1-k_1s_1,\cdots,1-k_rs_r\right)xr=(d,k1,k2,⋯,kr) ,vr=(dM,1−k1s1,⋯,1−krsr)
B r = [ M e 1 e 2 ⋯ e r 0 − N 1 0 ⋯ 0 0 0 − N 2 ⋯ 0 ⋮ ⋮ ⋮ ⋱ ⋮ 0 0 0 ⋯ − N r ] \mathcal{B}_{r}=\left[\begin{array}{ccccc} M & e_{1} & e_{2} & \cdots & e_{r} \\ 0 & -N_{1} & 0 & \cdots & 0 \\ 0 & 0 & -N_{2} & \cdots & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0 & 0 & 0 & \cdots & -N_{r} \end{array}\right]Br=⎣⎢⎢⎢⎢⎢⎡M00⋮0e1−N10⋮0e20−N2⋮0⋯⋯⋯⋱⋯er00⋮−Nr⎦⎥⎥⎥⎥⎥⎤
则有x r B r = v r \, x_r\mathcal{B}_r=v_r\,xrBr=vr。注意到B r \, \mathcal{B}_r\,Br的行向量组生成了一个r + 1 \, r+1\,r+1维的格L \, \mathcal{L}\,L,而v r ∈ L \, v_r\in\mathcal{L}\,vr∈L。那么由假设可知,当不等式∥ v r ∥ < r + 1 det ( L ) 1 / ( r + 1 ) \left\|v_{r}\right\| < \sqrt{r+1} \det(\mathcal{L})^{1 /(r+1)}∥vr∥<r+1det(L)1/(r+1) 成立时(即满足Minkowski’s bound时),攻击者只要解出格L \, \mathcal{L}\,L上的SVP问题即可解出v r \, v_r\,vr,从而得到v r \, v_r\,vr第一个分量d M \, dM\,dM。M \,M\,M已知,因此攻击者可求出d \, d\,d,从而攻破这些共解密指数实例。
攻击条件
经过一些简单的不等式推导可得,当d < N r δ r \, d<N_r^{\delta_r}\,d<Nrδr且δ r < 1 2 − 1 2 ( r + 2 ) − log N r 6 \, \delta_r<\frac{1}{2}-\frac{1}{2(r+2)}-\log_{N_r}6\,δr<21−2(r+2)1−logNr6时,不等式∥ v r ∥ < r + 1 det ( L ) 1 / ( r + 1 ) \,\left\|v_{r}\right\| < \sqrt{r+1} \det(\mathcal{L})^{1 /(r+1)}\,∥vr∥<r+1det(L)1/(r+1) 一定能成立。因此d < N r δ r \, d<N_r^{\delta_r}\,d<Nrδr且δ r < 1 2 − 1 2 ( r + 2 ) − log N r 6 \, \delta_r<\frac{1}{2}-\frac{1}{2(r+2)}-\log_{N_r}6\,δr<21−2(r+2)1−logNr6就是在先前提出的假设成立时,攻击一定能成功的条件(具体推导请查阅参考资料,查找关键词common private)。
攻击的提出者Hinek在对RSA-2048/4096进行实验时发现要想保证攻击一定成功,实际的δ r \,\delta_r\,δr要比满足前面这个不等式的δ r \,\delta_r\,δr最大值小一点(大概小0.005左右)。
共用私钥的RSA实例个数r \,r\,r越大,攻击效果越好。Hinek进行了全面的攻击实验,当设定r = 35 \,r=35\,r=35,当d < N 0.485 \, d<N^{0.485}\,d<N0.485,数万次攻击基本全部成功。更详细的实验结果请查阅文末的参考资料。
攻击代码
以下代码在sagemath 9.1上运行。
攻击算法
当攻击成功时会计算并打印出正确的私钥d \,d\,d,当攻击失败时计算出的d \,d\,d是错误的。
"""
instance: 共私钥的RSA实例,为一个列表。该列表的每一项都是字典,instance[i]['e']为第i个RSA实例的e,instance[i]['n']为第i个RSA实例的n。
"""
def common_private_attack(instance, debug=False, algo="LLL"):
r = len(instance)
instance.sort(key=lambda x: x['n'])
M = isqrt(instance[r-1]['n'])
# Build up Lattice basis B
B = zero_matrix(r+1)
B[0,0] = isqrt(instance[r-1]['n'])
for i in range(1, r+1):
B[0,i] = instance[i-1]['e']
B[i,i] = - instance[i-1]['n']
if debug:
print("The basis of the lattice we build is:"); print(B)
if algo == "LLL":
print("Performing LLL reduction..."); B = B.LLL(); print("Done.")
elif algo == "BKZ":
print("Performing BKZ reduction..."); B = B.BKZ(block_size=len(instance)); print("Done.")
dM = B[0,0]; d = dM // M;
# 有时会算出负数
d = abs(d)
print(" dM = {} \n d = {}".format(dM, d))
if dM % d != 0:
print("Fail to attack these instances.")
测试代码
调用以下方法即可生成满足攻击条件的RSA实例。
from Crypto.Util.number import getStrongPrime, getPrime, getRandomNBitInteger
from gmpy2 import gcd, invert
from math import floor, log
# Generate r instances of RSA with the same d.
def gen_instance(r=2, bit_len=2048, delta_r=None,delta=0, debug=False):
if delta_r is None:
delta_r = 0.5 - 0.5 / (r+1) - log(6, 2^bit_len) + delta
# d = getRandomNBitInteger(int(2 * bit_len * delta_r))
d = getPrime(floor(bit_len * delta_r))
if d & 1 == 0:
d += 1
print("The d we choose is: {}".format(d))
print("d satisfy the condition: d < Nr^{}".format(delta_r))
print("\n")
print("Generating instances ...")
instance = []
for i in range(r):
while True:
p = getStrongPrime(bit_len//2)
q = getStrongPrime(bit_len//2)
# p = getPrime(bit_len)
# q = getPrime(bit_len)
n = p * q
phi = (p-1)*(q-1)
if gcd(d, phi) == 1:
e = invert(d, phi)
this = {'n':n, 'e':e, 'd':d}
instance.append(this)
break
print("Done.")
if debug:
print("The RSA Instances we choose is below:")
for i in range(r):
print("instance {}: {}".format(i, instance[i]))
return instance
参考资料
- M. Jason Hinek. On the Security of Some Variants of RSA. 2007.
- M. Jason Hinek. Cryptanalysis of RSA and Its Variants. 2009