( 原文地址:https://0xffff.one/d/1064-yong-ge-jie-lwe-bi-ji )
接上一篇 继续讨论格密码的攻击,上一篇的几个问题几乎都是在解SVP(Shortest Vector Problem) 问题,这次来讨论一个解CVP(Closest Vector Problem) 问题的,即LWE(Learning With Errors) 。
LWE·
虽然我个人认为,会接下去看的人应该都是知道LWE是啥的了(LWE不懂能看懂LWE的破解?),但还是简略讲一下LWE是个啥。
主要关注这样一个式子:
A x + e = b ( m o d q ) Ax+e=b\ (mod\ q)
A x + e = b ( m o d q )
其中A是个m*n的矩阵(公开)(好像m会比n大?如果n比m大的话就是一个长的x映射到一个短的b,就能有多个x映射到b导致不能解密了) ;x是n*1的向量(明文);e是误差,可以看成是每个元素都很小的m*1的向量(密钥);b是m*1向量(密文)。运算都是在mod q下进行的,q是素数。详细点可以写成这样:
A m ∗ n x n ∗ 1 + e m ∗ 1 = b m ∗ 1 ( m o d q ) A_{m*n}x_{n*1}+e_{m*1}=b_{m*1}\ (mod\ q)
A m ∗ n x n ∗ 1 + e m ∗ 1 = b m ∗ 1 ( m o d q )
在知道e的情况下,A x = b − e ( m o d q ) Ax=b-e\ (mod\ q) A x = b − e ( m o d q ) 通过简单的高斯消元就可以解出x,即有私钥是容易解的。在没e的情况下,高斯消元会把误差e放大(好像是这么说的),所以只知道b会解不出x。
CVP·
CVP问题输入一个格L和一个通常不在格上的向量t,要求出在格上的离t“最近”的向量w。IMC上的定义:
通常来说,在维度小的情况下用LLL+Babai的方法可以解CVP问题,LLL主要用来找L的一组“很垂直”的基,Babai算法大概如下:
上面说的是L在R n R^{n} R n 的情况,即L要是方阵 ,L不是方阵的话可以用Babai的"nearest plane algorithm"来解:
在Sage里,LLL可以直接用M.LLL()解,或者用free_module_integer 里的IntegerLattice;CVP的话听说可以用free_module_integer的(且听说不是Babai的算法,试了一下,非常慢,放弃了,详细原因见下图),但还是手撸Babai的代码好一点。
解法参考了一下一篇 Aero CTF 2020 - Magic II 的wp ,里面其实有挺详细的说明了(包括方程里矩阵的展开)。
首先现在问题是:
A x + e = b ( m o d q ) Ax+e=b\ (mod\ q)
A x + e = b ( m o d q )
知道A、b和q,e很小,求x,因为加了e,所以b大概率不会在格A中,但考虑到e很小,如果找到b在格A中最近的向量b‘,则有(其实b’是等于b-e ?):
A x = b ′ ( m o d q ) Ax=b'\ (mod\ q)
A x = b ′ ( m o d q )
通过简单的高斯消元即可求出x。由于Babai的算法是不能在mod q的情况下解的,所以第一步是要先把mod q拆出来:
A x + e = b ( m o d q ) A x + q I m k = b − e Ax+e=b\ (mod\ q) \\
Ax+qI_mk=b-e
A x + e = b ( m o d q ) A x + q I m k = b − e
其中,I m I_m I m 是维度m的单位矩阵,k是拆mod q是的未知倍数(向量,m*1),即是把每一条a i x i + k i q = b i − e i a_ix_i+k_iq=b_i-e_i a i x i + k i q = b i − e i 写成矩阵和向量的形式而已。由于多了个q I m k qI_mk q I m k ,所以要构造新的格,这也不难,根据上面式子马上有:
[ A p I m ] [ x k ] = b − e \left[ \begin{array}{c|c} A & pI_m \end{array}\right]
\begin{bmatrix} x \\ k \\ \end{bmatrix}
= b-e
[ A p I m ] [ x k ] = b − e
由于sage中的向量是横过来的(??),所以左右做个转置:
[ x k ] [ A T p I m ] = ( b − e ) T \left[ \begin{array}{c|c} x & k \end{array}\right]
\begin{bmatrix} A^T \\ pI_m \\ \end{bmatrix}
= (b-e)^{T}
[ x k ] [ A T p I m ] = ( b − e ) T
当然也有另一种组合(实际尝试中会有上面那种不行,但下面的行的情况):
[ p I m A ] [ k x ] = b − e [ k x ] [ p I m A T ] = ( b − e ) T \left[ \begin{array}{c|c} pI_m & A \end{array}\right]
\begin{bmatrix} k \\ x \\ \end{bmatrix}
= b-e
\\
\left[ \begin{array}{c|c} k & x \end{array}\right]
\begin{bmatrix} pI_m \\ A^T \\ \end{bmatrix}
= (b-e)^{T}
[ p I m A ] [ k x ] = b − e [ k x ] [ p I m A T ] = ( b − e ) T
以2020祥云杯的easy matrix为例,题目可以在这里 找到。题目就是个LWE的加密,matrix是A、secret是x、offset 是e、result是b、prime是q。
按照上面的思路可以写出Sage脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 from sage.modules.free_module_integer import IntegerLatticeimport numpy as npdef Babai (B, t ): B = IntegerLattice(B, lll_reduce=True ).reduced_basis G = B.gram_schmidt()[0 ] b = t for i in reversed (range (B.ncols())): b -= B[i] * ((b * G[i]) / (G[i] * G[i])).round () return t - b mx = list (np.load('./matrix.npy' )) rt = list (np.load('./result.npy' )) A = matrix(ZZ, mx) b = vector(ZZ, rt) p = 2129 r = A.nrows() c = A.ncols() pIr = p*identity_matrix(r) M = block_matrix([[pIr], [A.transpose()]]) br = Babai(M, b) print ('e = %s' % (b-br))R = IntegerModRing(p) Ar = matrix(R, mx) secret = Ar.solve_right(br) m = '' .join(chr (x) for x in secret) print ('m = %s' % m)
在Babai里,本来以为可以直接用B.LLL()生成的基做的,但好像实际要用的是方阵的基,LLL()生成的基规模和B是一样的,无奈只能调用IntegerLattice的reduced_basis。
PS:写的时候参考了一下Lazzaro 的代码,比较奇怪的是网上找easy matrix的wp发现代码几乎都是一样的,据说都是参考Nu1l的?震惊
CVP,LLL+Babai
IMC
Tel Aviv University, lattices_fall_2004, Lecture 3 CVP Algorithm
格密码 by Lazzaro
一篇 Aero CTF 2020 - Magic II 的wp
Lattice-Based Cryptanalysis