比赛后才知道是paper题,而且这真的是个在md5被攻破以后提出又被废弃的hash方案LPS,查hash就能查到(鬼知道啊,这一看还以为只是考线代复变的,以为题目乱取的,居然还真有人拿这当hash方案)首先是dbt在discord找到了出题人说参考的《Full Cryptanalysis of LPS and Morgenstern Hash Functions》[1]但这篇很多都没讲清楚,在参考文献中又找到了一篇《Collisions for the LPS expander graph hash function》[2]两篇结合加上一点自己的想法就可以做出这道题了。第二篇是注重碰撞,而碰撞只需要分解单位矩阵I即可,而第一个注重找原像,也就是题目中的分解。
如果我们知道有解,设F = gk0gk1...gkn,然后我们爆破六个矩阵基,分别计算Fgi的值,当 gi = gkn-1时,F = 5gk0gk1...gkn-1,此时F中的四个数均能被5整除,即使存在i使得gi != gkn-1时,F也能被5整除,那此时F的行列式仍然在下降,不断下降一定会下降到1.
代码很简单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
deffactor_Zi(M): flist = [] testM = [i for i in M] while testM.count(0) < 3: for i inrange(6): temp = mul(g[i], testM) for j inrange(4): if temp[j] % 5 != 0: break else: flist.append(5 - i) testM = [i // 5for i in temp] break else: return -1 return flist
这里的testM最后应当是[a , 0, 0, 0]的形式。
PSL到Z[i]的转化
首先上面的分解提供了我们需要转化的最终结果,所以这一步的需求是,先将随机矩阵转化为四元向量 (A, B, C ,D),然后求解方程
classContinuedFraction(): def__init__(self , numerator, denumerator): self.numberlist = [] #number in continued fraction self.fractionlist = [] #the near fraction list self.GenerateNumberList(numerator , denumerator) self.GenerateFractionList() defGenerateNumberList(self,numerator,denumerator): while numerator != 1: quotient = numerator//denumerator remainder = numerator % denumerator self.numberlist.append(quotient) numerator = denumerator denumerator = remainder defGenerateFractionList(self): self.fractionlist.append([self.numberlist[0] ,1 ]) for i inrange(1,len(self.numberlist)): numerator = self.numberlist[i] denumerator = 1 for j inrange(i): temp = numerator numerator = denumerator + numerator * self.numberlist[i-j-1] denumerator = temp self.fractionlist.append([numerator,denumerator]) defcompute_square_modp(a , p): assert is_prime(p) ifpow(a , (p-1)//2 , p) != 1: returnNone R = GF(p) returnint(R(a).nth_root(2)) defsolve_yz(n): ifnot is_prime(n) or n % 4 != 1: return -1 else: r = compute_square_modp(-1 , n) print(1) cf = ContinuedFraction(r , n) square = iroot(n , 2)[0]
for i inrange(len(cf.fractionlist)): if cf.fractionlist[i][1] <= square and cf.fractionlist[i+1][1] >= square: p , q = cf.fractionlist[i] a = q b = q*r - p*n assert a **2 + b ** 2 == n return a , abs(b) defconvert_to_Zi(A): assert A.count(0) == 2 nonzero = [] zero = [] temp = [] for i inrange(4): if A[i] == 0: zero.append(i) else: nonzero.append(i) temp.append(A[i]) a ,b = temp k = 224 ifpow(a**2 + b**2 , (p-1)//2 , p) != 1: return -1 lamda = compute_square_modp((l**k) * inverse(a**2+b**2 , p) , p) m = (l**k - (a**2+b**2)*lamda**2) // p while1: w = randint(1 , p-1) if (a*lamda + w) % 2 == 0: continue x = (m * inverse(2*lamda*b , p) - a * w * inverse(b , p)) % p n = (l ** k - (a*lamda + w*p)**2 - (b*lamda + x*p)**2)// p**2 if n%4 != 0or n < 0: continue n //= 4 temp = solve_yz(n) if temp != -1: y , z = temp ab = [a*lamda+w*p , b*lamda + x*p] res = [0] * 4 for i inrange(4): if i in zero: res[i] = temp[zero.index(i)]*2*p else: res[i] = ab[nonzero.index(i)] return res
这里同时考虑了0的位置不同的问题,开头和结尾是用于B = D = 0或C = D = 0的情况的。但由于这里有很多随机处(二次剩余与n的素数),因此这个函数可能跑的很慢也有可能很快。
有了这个以后,接下来如果我们能够用对角矩阵将任意矩阵变成对角矩阵,就能够使用上面的方法将这些矩阵一一分解。文献[1]使用的是这种方法,使用了7个矩阵来表示任意矩阵(4个对角矩阵与三个基矩阵)但我尝试以后,总是找不到能够同时满足所有的对角矩阵A2+B2为二次剩余的情况,不知道是方法问题还是写出bug了。但我尝试了另一条路。上面的方法并不是一定需要对角矩阵的,只要两个位置是0即可,而对角线相等,反对角线相反的矩阵刚好是B = D = 0的情况,而这种矩阵是很容易构造的。
当\(\alpha,w\)满足 \[
\alpha w = \frac{M_4}{M_1}\\
-\frac{w}{\alpha}=\frac{M_2}{M_3}\\
=>-w^2=\frac{M_4M_2}{M_1M_3}
\] 如果右边的是一个二次剩余,则我们能够算出\(\alpha\)与w,从而用三个矩阵来表示出来。如果它不是二次剩余,我们可以通过左乘一个基矩阵,来打散M,由于有加法,因此结果是否为二次剩余是不确定的,粗略看大致为1/2的概率同时,w和a也必须是二次剩余,这里的随机性也得通过左乘来提供。(在尝试中,有些矩阵使用这种方式似乎无效,而其他的矩阵大约在3-4次左右能够成功。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
defdiagonalize(M): left = 0 while1: print(left) m1 , m2 = M[0] m3 , m4 = M[1] temp = -m4 * m2 * inverse(m1 * m3 , p) % p ifpow(temp , (p-1)//2 , p) == 1: w = compute_square_modp(temp , p) ws = (w,-w) assertpow(w,2,p) == temp for w in ws: a = -w * m3 * inverse(m2 ,p ) % p f1 = m1 f2 = m3 * inverse(a , p) % p ifpow(a , (p-1)//2,p) ==1andpow(w , (p-1)//2,p) ==1andpow(f1**2+f2**2 , (p-1)//2,p) ==1: return left , [[1 , 0] , [0 , a]] , [[f1 , -f2] , [f2 , f1]] , [[1 , 0] , [0 , w]] M = Zi_to_PSLp(mul(g[1] , PSLp_to_Zi(M))) left += 1
至此,分解完毕。
1 2 3 4 5 6 7 8 9 10 11
defpreimages(M): left , a, b,c = diagonalize(M) print(left) flist = [4]*left for x in (a,b,c): temp = PSLp_to_Zi(x) tempm = convert_to_Zi(temp) print(tempm) flist += factor_Zi(tempm) print(flist) return flist
[1] Petit C., Lauter K., Quisquater JJ. (2008) Full Cryptanalysis of LPS and Morgenstern Hash Functions. In: Ostrovsky R., De Prisco R., Visconti I. (eds) Security and Cryptography for Networks. SCN 2008. Lecture Notes in Computer Science, vol 5229. Springer, Berlin, Heidelberg.
[2] G. Zémor and J.-P. Tillich. Collisions for the LPS expander graph hash function. To appear in the proceedings of Advances in Cryptology - EUROCRYPT 2008, 2008.
[3] G. Davidoff, P. Sarnak and A. Valette. Elementary Number Theory, Group Theory, and Ramanujan Graphs. Cambridge University Press, 2003.