假定一个背包可以承重 W,现在有 n 个物品,其重量分别为 a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an, 问装哪些物品可以恰好使得背包装满,并且每个物品只能被装一次。背包问题
其实就是在解这样的一个问题:
x 1 a 1 + x 2 a 2 + x 3 a 3 + . . . + x n a n = W x_1a_1 + x_2a_2 + x_3a_3 + ... + x_na_n = W x1a1+x2a2+x3a3+...+xnan=W
其中所有的 x i x_i xi 只能为 0 和 1。显然我们必须枚举所有的 n 个物品的组合才能解决这个问题,而复杂度也就是 2n,这也就是背包加密的妙处所在。
在加密时,如果我们想要加密的明文为 x x x,那么我们可以将其表示为 n 位二进制数,然后分别乘上 a i a_i ai 即可得到加密结果。
但是解密的时候,该怎么办呢?我们确实让其他人难以解密密文,但是我们自己也确实没有办法解密密文。
但是当是超递增的话,我们就有办法解了,所谓超递增是指序列满足如下条件:
a i > ∑ k = 1 i − 1 a k a_i > \sum_{k=1}^{i-1}{a_k} ai>k=1∑i−1ak
即第 i i i 个数大于前面所有数的和。
超递增背包问题的解很容易找到,我们可以计算其总量并与背包序列中最大的数比较,如果总重量小于这个数,则它不在背包中;如果总重量大于这个数,则它在背包中。背包重量减去这个数,进而考察序列中下一个最大的数,重复直到结束。
但是,这样又出现了一个问题,由于 a i a_i ai 是公开的,如果攻击者截获了密文,那么它也就很容易去破解这样的密码。为了弥补这样的问题,就出现了 Merkle–Hellman
这样的加密算法。
私钥就是我们的初始的背包集,这里我们使用超递增序列,怎么生成呢?我们可以假设 a 1 = 1 a_1 = 1 a1=1,那么 a 2 a_2 a2 大于 1 即可,类似的可以依次生成后面的值。
在生成公钥的过程中主要使用了模乘的运算。
首先,我们生成模乘的模数 m m m,这里要确保
m > ∑ i = 1 i = n a i m > \sum_{i=1}^{i=n}{a_i} m>i=1∑i=nai
其次,我们选择模乘的乘数 w w w,作为私钥并且确保
g c d ( w , m ) = 1 gcd(w,m) = 1 gcd(w,m)=1
之后,我们便可以通过如下公式生成公钥
b i = w a i m o d m b_i = wa_i \mod m bi=waimodm
并将这个新的背包集 b i b_i bi 和 m m m 作为公钥。
假设我们要加密的明文为 v,其每一个比特位为 v i v_i vi ,那么我们加密的结果为
m > ∑ i = 1 i = n b i v i m o d m m > \sum_{i=1}^{i=n}{b_iv_i\mod m} m>i=1∑i=nbivimodm
对于解密方,首先可以求的 w w w 关于 m m m 的逆元 w − 1 w^{-1} w−1。
然后我们可以将得到的密文乘以 w − 1 w^{-1} w−1 即可得到明文,这是因为
∑ i = 1 i = n w − 1 b i v i m o d m = ∑ i = 1 i = n a i v i m o d m \sum_{i=1}^{i=n}{w^{-1}b_iv_i\mod m} = \sum_{i=1}^{i=n}{a_iv_i\mod m} i=1∑i=nw−1bivimodm=i=1∑i=naivimodm
这里有
b i ≡ w a i m o d m b_i \equiv wa_i \mod m bi≡waimodm
对于每一块的加密的消息都是小于 m 的,所以求得结果自然也就是明文了。
该加密体制在提出后两年后该体制即被破译,破译的基本思想是我们不一定要找出正确的乘数 w(即陷门信息),只需找出任意模数 m ′ m^′ m′ 和乘数 w ′ w^′ w′,只要使用 w ′ w^′ w′ 去乘公开的背包向量 B 时,能够产生超递增的背包向量即可。
题目来源:Dest0g3 Crypto Bag
题目代码:
import gmpy2
from Crypto.Util.number import *
from secret import flag
message = bytes_to_long(flag[8:-1])
Baglenth=286
Bag=[]
Bag=Bag[::-1]
m=372992427307339981616536686110115630075342113098010788080347982669869622759400031649792
w=274062421102700155372289583695782343443
assert gmpy2.gcd(m,w)==1
h=0
j=0
if m.bit_length()%2==0:
h=m.bit_length()
j=int(h//2)
else:
h=m.bit_length()
j=int(h//2+1)
def pad(m,lenth):
while len(m)<lenth:
m='0'+m
return m
def keygen():
pk=[]
sk=[]
sk.append(m)
sk.append(int(gmpy2.invert(w,m)))
D=[]
binD=[]
for i in range(Baglenth):
di=(w*Bag[i])%m
D.append(di)
bindi=bin(di)[2:]
bindi=pad(bindi,h)
binD.append(bindi)
U=[]
V=[]
for i in range(Baglenth):
tempu=int(str(binD[i][:j]),2)
U.append(tempu)
tempv=int(str(binD[i][j:]),2)
V.append(tempv)
e=gmpy2.next_prime(sum(V))+2
f=gmpy2.next_prime(sum(U))
assert gmpy2.gcd(e,f)==1
sk.append(int(e))
sk.append(int(f))
for i in range(Baglenth):
ai=e*U[i]+f*V[i]
pk.append(int(ai))
return pk,sk
Pk,Sk=keygen()
print(Pk)
print(Sk)
def Encrypt(plain,pk):
mbin=bin(plain)[2:]
c=0
mbin=pad(mbin,Baglenth)
for i in range(Baglenth):
c=c+int(mbin[i])*pk[i]
return c
c=Encrypt(message,Pk)
print(c)
使用LLL(Lenstra-Lenstra-Lovász)格基约化
算法求解:
pubKey = []
nbit = len(pubKey)
encoded = 1475864207352419823225329328555476398971654057144688193866218781853021651529290611526242518
A = Matrix(ZZ, nbit + 1, nbit + 1)
for i in range(nbit):
A[i, i] = 1
for i in range(nbit):
A[i, nbit] = pubKey[i]
A[nbit, nbit] = -int(encoded)
res = A.LLL()
for i in range(0, nbit + 1):
M = res.row(i).list()
flag = True
for m in M:
if m != 0 and m != 1:
flag = False
break
if flag:
print(i, M)
M = ''.join(str(j) for j in M)
M = M[:-1]
M = hex(int(M, 2))[2:]
print(bytes.fromhex(M))
背包加密 - CTF Wiki
密码学——公钥密码体系之背包算法1 - 哔哩哔哩
Dest0g3 520迎新赛 | Lazzaro