首发于安全客
ACM中的背包问题是一种组合优化的NP完全问题,而在密码学中,也有对背包问题的应用。
Subset-sum problems and knapsack cryptosystems
密码学中的利用knapsack problem的加密。大意就是,你公开一个序列,然后你把你的消息转为二进制,如果你消息的这一位是1,就从你序列中取出对应位置的值,最后密文就是你所有取出的值的和。
但是这样就产生一个问题,加密是加过去了。那该怎么解密呢?
蛮力攻击的时间复杂度是$O(2^n)$,就算中途相遇也只能降低到$O(2^{\frac{n}{2}})$。太难了。。。
那Alice怎么解决这个问题呢?公钥密码系统如何体现呢?
这就看Alice对序列的选择了。她用的是超递增序列。什么是超递增序列呢?就是后一项≥前一项的两倍,也就意味着,后一项大于前面的所有项之和。
有超递增序列后,给你一个密文,你只需要一项一项对比过来,你就能知道加密者选取的元素是哪几个了。
这不难理解。我们将序列中的每一项放到二进制下就很明显。如果第一项的位长是2,那第二项的位长就至少是3,如果第二项位长是3,那么第三项的位长至少是4。如果你的密文最后是个2位长的,那加密者必定是没有用到第三项,必定是用到了第二项,至于有没有用到第一项,那就看密文减掉第二项之后还有没有剩下来了。【当然,这在每一项只能用一次的大前提下】
preparation
Alice准备加密了,首先她先整了一个超递增序列,$r = (r_1,r_2,,,r_n)$,然后她整了两个大数A和B,其中要求$B>2r_n$ 也就是B大于这个超递增序列之和啦。然后要需要$gcd(A,B)=1$,因为之后要求一个A在B下的逆。
encryption
Alice要加密了,她先用她的A和她的超递增序列生成一个新的序列M,其中$M_i \equiv Ar_i \pmod B$
这个新的序列就是Alice的公钥了,她把这个序列甩给Bob,然后Bob按照之前描述的方式,用这个序列和他的明文生成密文$S = x*M = \sum \limits_{i=1}^{n}x_iM_i$,然后把这传给Alice
decryption
Alice要解密了,她拿到了这个密文S,然后计算
$S’ \equiv A^{-1}S \equiv A^{-1}\sum_{i=1}^{n}x_iM_i\equiv A^{-1}\sum_{i=1}^{n}x_iAr_i\equiv \sum_{i=1}^{n}x_ir_i \pmod B$
所以这个$S’$就是密文在超递增序列下的值了,之后再像之前那样判断一下这个$S’$和序列里面每一项的关系就能解出明文了。【记得从序列大的一端开始判断,如果满足关系别忘了减掉那一项再判断下一项】
伪代码走一波
123456 |
for i in reverse(r):if s >= i: m = '1' + m s -= i else: m = '0' + m |
table
来个总体流程
attention
同RSA一样,这里对参数的选择也要慎重,不然会出现许多出人意料的问题。
具体实例可参考BJD3rd-knapsack
这里由于对参数A的不当选择,导致序列中较小的项乘以A后并没有被B模掉,也就导致了参数A的泄露。
并且由于这里的超递增序列的生成规则过于简单,只是不断地整除2,所以攻击者能够利用加密后的序列轻易的计算出B来。
further more
之前提到Alice最开始整的一个无序的序列,由于这玩意儿没有陷门(trapdoor),因此这无法成为一个密码系统。但自从有关LLL的paper发表后,基于背包的密码系统出现了一个大weakness,
这里简短的介绍下Eve怎么去解决这个无序序列的背包问题。不管是序列本身无序,还是超递增序列加密后显得无序的序列。
Eve先构造了一个矩阵$M =\begin{bmatrix}2&0&0&\dots&0&0&m_1 \newline0&2&0&\dots&0&0&m_2 \newline\vdots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots \newline0&0&0&\dots&2&0&m_{n-1}\newline0&0&0&\dots&0&2&m_{n}\newline1&1&1&\dots&1&1&S\newline\end{bmatrix}$
其中这个$m_1,m_2,m_3…$就是那个无序序列,S就是密文
然后Eve从这个矩阵中,将每一条行向量划分出来,分别为$V_1,V_2,…,V_n,V_{n+1}$
我们现在假设向量$x = (x_1,x_2,x_3,…,x_n)$ 是明文,($x_i = 0$ or $1$)
那么这个格中就会有这么一条向量
$t = \sum_ \limits {i=1}^{n}x_iV_i-V_{n+1}=(2x_1-1,2x_2-1,…,2x_n-1,0)$
这个t,因为$2x_i-1=\pm 1$,所以t的模长是$\sqrt{n}$,
根据Minkowskl’s First Theorem,
即对于任意n维满秩格基,都有 $SVP(L)≤\sqrt n(det(L))^{\frac 1 n}$
显然t是格L中的短向量。
所以,如果Eve知道如何找到lattice中的短向量,那么他就可以完成破解了。
关于找到lattice中短向量的算法我们称之为reduction algorithm,最著名的就是LLL algorithm了,然后它还有变体LLL-BKZ。
variant
再刚过去不久的2020RCTF中出现了一道有关knapsack problem 的变体,这里用的不是子集之和,而是子集之积
123456789101112131415161718192021 |
from Cryptodome.Util.number import bytes_to_long, getPrimeimport randomimport hashlibsr = random.SystemRandom()p = getPrime(120)num = [sr.randint(1,p-1) for i in range(90)]secret = sr.randint(0, 2**90)r = 1for i in range(90): if (secret >> i) & 1: r *= num[i] r %= pflag = open("flag.txt","rb").read()h = hashlib.sha256(str(secret).encode('utf-8')).digest()print(p)print(num)print(r)print(bytes_to_long(h)^bytes_to_long(flag)) |
蛮力攻击的时间复杂度是2的90次方,即使是中途相遇攻击的时间复杂度也仍然有2的45次方,于此同时还得考虑2的45次方的空间复杂度。
赛后看了国外大佬hellman的脚本才明白了本题的解法。
这一道题的切入点就在于模数p,通过不断地nc,直到获得的模数p具有p-1 smooth的性质(为了方便后面解离散对数),
这个时候再找到一个p的原根,然后对所有的数据利用pohlig算法解一个离散对数,就是开一个log,这样这个子集积的问题就能重新变回子集和的问题了。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
from sage.all import *import ast, sys, subprocessimport hashlibfrom random import shufflefrom time import time#不断nc以获取一个具有p-1 smooth性质的模数pwhile True: data = subprocess.check_output("nc 124.156.133.6 22298 </dev/null", shell=True).splitlines() p = int(data[0]) nums = ast.literal_eval(data[1].decode()) r = int(data[2]) encflag = int(data[3]) fp1 = factor(p-1) print("p-1 =", fp1) if all(d < 2**50 for d, e in fp1):#判断模数是否具有p-1 smooth的性质 break print("too high factor of p-1")# 解离散对数问题 -> 将子集积问题转化问子集和F = GF(p)g = F.primitive_element() #获取原根ges = []for num in nums: e = F(num).log(g)#以原根为底数开log,因为循环群中所有的数都能以原根表示 print(num, "->", e) es.append(e)r = F(r).log(g)nums = esmod = p - 1#利用LLL解决0-1背包问题N = 90nums0 = nums[::]BS = 30itr = 0while True: itr += 1 print(itr, "BS", BS) t0 = time() nums = nums0[::] shuffle(nums) h = QQ(1)/2 # n1 1 0 0 0 0 # n2 0 1 0 0 0 # n3 0 0 1 0 0 # n4 0 0 0 1 0 # r h h h h h # mod 0 0 0 0 0 m = matrix(QQ, N+2, N+2) m.set_column(0, nums + [r, mod]) m.set_row(N, [r] + [h] * (N+1)) m[:N,1:N+1] = identity_matrix(N) m.set_column(0, 50*m.column(0)) ml = (m*2).change_ring(ZZ).BKZ(block_size=BS) print("time %.3fs" % (time() - t0)) for irow, row in enumerate(ml): if not (-1 <= min(row[:-1]) < max(row[:-1]) <= 1): continue if row[-1] < 0: row = -row print("GOOD", irow) secret = 0 for i in range(N): if row[1+i] < 0: secret |= 1 << nums0.index(nums[i]) print("secret", secret, "=", hex(secret)) h = hashlib.sha256(str(secret).encode('utf-8')).digest() h = int.from_bytes(h, "big") flag = encflag ^ h print(int(flag).to_bytes(100, "big").strip(b"\x00")) quit()# RCTF{M4th_0f_MuLLLtiplication_2333} |
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论