密码学学习笔记 之 knapsack

  1. Subset-sum problems and knapsack cryptosystems
    1. preparation
    2. encryption
    3. decryption
    4. table
    5. attention
    6. further more
    7. variant

首发于安全客

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’$和序列里面每一项的关系就能解出明文了。【记得从序列大的一端开始判断,如果满足关系别忘了减掉那一项再判断下一项】

伪代码走一波

1
2
3
4
5
6
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 \newline
0&2&0&\dots&0&0&m_2 \newline
\vdots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots \newline
0&0&0&\dots&2&0&m_{n-1}\newline
0&0&0&\dots&0&2&m_{n}\newline
1&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 的变体,这里用的不是子集之和,而是子集之积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from Cryptodome.Util.number import bytes_to_long, getPrime
import random
import hashlib

sr = random.SystemRandom()
p = getPrime(120)
num = [sr.randint(1,p-1) for i in range(90)]
secret = sr.randint(0, 2**90)
r = 1
for i in range(90):
if (secret >> i) & 1:
r *= num[i]
r %= p

flag = 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,这样这个子集积的问题就能重新变回子集和的问题了。

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
from sage.all import *
import ast, sys, subprocess
import hashlib
from random import shuffle
from time import time

#不断nc以获取一个具有p-1 smooth性质的模数p
while 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() #获取原根g
es = []
for num in nums:
e = F(num).log(g) #以原根为底数开log,因为循环群中所有的数都能以原根表示
print(num, "->", e)
es.append(e)

r = F(r).log(g)
nums = es
mod = p - 1

#利用LLL解决0-1背包问题
N = 90

nums0 = nums[::]

BS = 30

itr = 0
while 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,也可以邮件至 643713081@qq.com

文章标题:密码学学习笔记 之 knapsack

文章字数:2.1k

本文作者:Van1sh

发布时间:2020-06-08, 10:30:43

最后更新:2020-08-21, 14:48:24

原始链接:http://jayxv.github.io/2020/06/08/密码学学习笔记之knapsack/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏