Skip to content

背包密码 ​

背包问题 ​

假设有序列 W→={W0,W1,…,Wn},需要找到一个子集 w→⊆W→,使得 ∑wi∈w→wi=S。

这个问题是 NP hard 的。

例如:W→={3,34,4,12,5,2},S=9,则 w→={4,5} 是一个解。

超增背包问题 ​

假设有一个超增序列(SIK) W→={W0,W1,…,Wn},满足 Wi>∑j=0i−1Wj,例如:W→={2,3,7,14,30,57,120,251},找到一个子集 w→⊆W→,使得 ∑wi∈w→wi=S 是简单的。

假设 S=186,则 w→={2,7,14,30,57,120} 是一个解。

  • 251 > 186,不选 251, 余 186
  • 120 ≤ 186,选 120, 余 66
  • 57 ≤ 66,选 57, 余 9
  • 30 > 9,不选 30, 余 9
  • 14 > 9,不选 14, 余 9
  • 7 ≤ 9,选 7, 余 2

背包密码 ​

1. Keygen ​

  • 选择一个超增序列 W→={W0,W1,…,Wn}。
  • 选择一个模数 n,满足 n>∑i=0nWi。
  • 选择 m,满足 gcd(m,n)=1。
  • 计算序列 B→={B0,B1,…,Bn},其中 Bi=m⋅Wimodn。

得到:

  • 公钥:pk←B→。
  • 私钥:sk←W→,n,m。

e.g:

sk←W→=(2,3,7,14,30,57,120,251)n=491m=41m−1modn=12pk←B→=(82,123,287,83,248,373,10,471)

2. Encrypt ​

  • 假设明文 P=[10010110]
  • 密文 C=∑i=0nPi⋅Bi=82+83+373+10=548

3. 解密 ​

  • 计算 C′=C⋅m−1modn=548⋅12mod491=193。
  • 然后使用超增序列,容易解得 193=120+57+14+2,对应明文 P=[10010110]。

使用格破解背包密码 ​

从攻击者视角,只知道公钥超递增背包被“扰乱”后的序列:

  • 已知:B→=(B0,…,Bn−1),以及密文 C
  • 不知:超增序列 W→、模数 n、乘数 m

但对任意明文比特向量 P→=(P0,…,Pn−1)∈{0,1}n,总有:

C≡∑i=0n−1PiBi(modn)

以及

0≤∑i=0n−1PiBi<n⋅maxBi

把解密问题改写成求解短向量的形式 ​

解密的本质是:从所有 2n 个 0/1 向量里,找到一个满足

∑i=0n−1PiBi=C

的向量 P→。
如果我们能把“所有可能的线性组合”组织进一个格里,那么“正确的那一个组合”就会对应于格中的某个特殊向量(通常是比较短的向量)。
这就把“解背包”转化成了一个格中的最短向量问题。

构造格 ​

M=[In×nBn×10m×n−Cn×m]

在这里, n 是背包的维度, B 是公钥列张成的向量, C 是密文列张成的向量,m 指的是密文条数,一般为1。故而也可以写成

[In×nBn×101×n−Cm×1]

任意整数向量 (x0,…,xn−1,xn)∈Zn+1 乘上这个基,就得到格中的一个向量:

M⋅[Xn×111×1]=[Xn×101×1]

格 L 中的任意向量可表示为:

v→=M⋅x→=[x0x1⋮xn−1∑i=0n−1xiBi−xnC]T,其中 x→=(x0,x1,…,xn)⊤∈Zn+1.

对于明文 P→=(P0,…,Pn−1),并令 xi=Pi(i=0,…,n−1),xn=1。则对应的格向量为:

v→true=[P0P1⋮Pn−1∑i=0n−1PiBi−C]T=[P0P1⋮Pn−10]T

因为 ∑PiBi=C。因此,v→true 是格 L 中的一个向量:

前 n 个分量属于 {0,1}, 最后一个分量为 0, 欧几里得范数满足 ||P→||≤n。

由于 n 通常不大,这个向量在高维空间中是非常短的。

利用 格基规约 算法恢复明文 ​

可以通过 LLL 或者 BKZ 等格基规约算法,找到格 L 中的短向量,从而恢复明文 P→。

攻击步骤如下:

  1. 根据公钥 B→ 和密文 C 构造格基矩阵 M;
  2. 对 M 应用 LLL 算法,得到一组约简基 {b→0,b→1,…,b→n};
  3. 检查每个基向量: 若某向量 b→ 的最后一个分量为 0, 且前 n 个分量均为 0 或 1, 则该向量极可能就是 v→true;
  4. 提取前 n 位作为明文 P→,并验证 ∑PiBi=C。

Code ​

Sage
python
from sage.all import * 
from Crypto.Util.number import *

Length = 16

# 生成私钥: 超增序列
def gen_superincreasing_sequence(length):
    sequence = []
    current_sum = 0
    for _ in range(length):
        next_element = current_sum + randint(1, 10)
        sequence.append(next_element)
        current_sum += next_element
    return sequence, current_sum    

sequence, total_sum = gen_superincreasing_sequence(Length)
print("私钥:",sequence)
bash
私钥: [9, 17, 32, 65, 129, 259, 517, 1033, 2067, 4136, 8270, 16537, 33078, 66151, 132305, 264611]
python
# 生成模数和乘数
bits = log(total_sum, 2)
p = getPrime(int(bits) + 1)
while True:
    m = randint(2, p - 1)
    if gcd(m, p) == 1:
        break

print("p: ", p)
print("m: ", m)
bash
p:  711751
m:  421763
python
# 生成公钥
pub_key = []
for i in sequence:
    pub_key.append((i * m) % p)
    
print("公钥: ",pub_key)
bash
公钥:  [237112, 52461, 684898, 368057, 314351, 338714, 255665, 89567, 600897, 621818, 400110, 246682, 45163, 116764, 75315, 572393]
加密
python
message = [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0]
# 加密
ciphertext = 0
for i in range(Length):
    ciphertext += message[i] * pub_key[i] % p
    
print("密文: ",ciphertext)
bash
密文:  2613511
解密
python
# 解密
m_inv = inverse_mod(m, p)
adjusted_ciphertext = (ciphertext * m_inv) % p

def decrypt_superincreasing_sequence(sequence, adjusted_ciphertext):
    decrypted_message = []
    for element in reversed(sequence):
        if adjusted_ciphertext >= element:
            decrypted_message.append(1)
            adjusted_ciphertext -= element
        else:
            decrypted_message.append(0)
    decrypted_message.reverse()
    return decrypted_message

dec = decrypt_superincreasing_sequence(sequence, adjusted_ciphertext)
print("解密:", dec)
assert dec == message
bash
解密: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
  • 构造格
[In×nAn×101×n−Bm×1]

在这里只有一条密文

[I16×16T16×101×16−C1×1]
构造格基矩阵
python
# 生成单位阵 I
I = identity_matrix(Length)
weight = 1
# 零向量
zero_row = Matrix(ZZ, 1, Length, [0]*Length)
# 公钥向量
T_vev = Matrix(ZZ, Length, 1, pub_key) * weight
# 密文向量
C_vev = Matrix(ZZ, 1, 1, [-ciphertext]) * weight
# 拼接分块矩阵
L = block_matrix([[I, T_vev], [zero_row, C_vev]])
print(L)
bash
[       1        0        0        0        0        0        0        0        0        0        0        0        0        0        0        0|  237112]
[       0        1        0        0        0        0        0        0        0        0        0        0        0        0        0        0|   52461]
[       0        0        1        0        0        0        0        0        0        0        0        0        0        0        0        0|  684898]
[       0        0        0        1        0        0        0        0        0        0        0        0        0        0        0        0|  368057]
[       0        0        0        0        1        0        0        0        0        0        0        0        0        0        0        0|  314351]
[       0        0        0        0        0        1        0        0        0        0        0        0        0        0        0        0|  338714]
[       0        0        0        0        0        0        1        0        0        0        0        0        0        0        0        0|  255665]
[       0        0        0        0        0        0        0        1        0        0        0        0        0        0        0        0|   89567]
[       0        0        0        0        0        0        0        0        1        0        0        0        0        0        0        0|  600897]
[       0        0        0        0        0        0        0        0        0        1        0        0        0        0        0        0|  621818]
[       0        0        0        0        0        0        0        0        0        0        1        0        0        0        0        0|  400110]
[       0        0        0        0        0        0        0        0        0        0        0        1        0        0        0        0|  246682]
[       0        0        0        0        0        0        0        0        0        0        0        0        1        0        0        0|   45163]
[       0        0        0        0        0        0        0        0        0        0        0        0        0        1        0        0|  116764]
[       0        0        0        0        0        0        0        0        0        0        0        0        0        0        1        0|   75315]
[       0        0        0        0        0        0        0        0        0        0        0        0        0        0        0        1|  572393]
[-----------------------------------------------------------------------------------------------------------------------------------------------+--------]
[       0        0        0        0        0        0        0        0        0        0        0        0        0        0        0        0|-2613511]
格基规约
python
# LLL
L_LLL = L.BKZ()
print(L_LLL)
bash
[ 0  0  0  0  0  0 -2 -1  1  0  0  0  0  0  0  0  0]
[ 1  0  1  0  1  0  1  0  1  0  1  0  1  0  1  0  0]
[ 0  0  0  0  0  0  0  2 -1  0  0  0  0  0 -2  1  0]
[ 1 -1 -1  0 -1  0 -1  0 -1  0 -1  0 -1  0  1 -1  0]
[ 0  0  0  2 -1  0 -2  1  0  0  0  0  0  0  0  0  0]
[ 0  0  0  0  0 -2  1 -2  1  0  0  0  0  0  0  0  0]
[ 0 -2 -1  1  0  0  0 -2  1  0  0  0  0  0  0  0  0]
[ 0 -1 -1  0  0 -1  1  0  0  1  1 -1  1  0  0  0  1]
[ 1 -1 -1  0 -1  0  1 -1 -1 -2  0  0 -1  0 -1  0  0]
[ 0  0  0  0  0  0  0 -2  1  0  0 -2 -1  1  0  0  0]
[ 1  1  0  1  1  0 -2  1  0 -1  0  0 -1  1  0  0  1]
[ 1  0 -1 -1  0  0  1  0  0  0  1  1 -1 -1  1  0  2]
[ 0  0  2 -1  0  0  0  0  0  0 -2 -1  1  0  0  0  0]
[ 0  0  0  0  2  1 -1  0  0  0 -2 -1 -1 -1 -1  1  0]
[ 2  1 -1  0  0  0  0  0  0  0  0  0  0  2 -1  0  0]
[ 0  1  0  1  1  0  0 -2 -1  0  0  0  1  0  0  0  1]
[ 1  0  0  0 -1  0 -1  1  0 -1 -1  0  1  0  1  2 -1]
提取明文
python
# 找到最短向量
def fine(L_LLL):
    all_positive = True
    for i in range(Length):
        for row in L_LLL[i][:-1]:
            if row < 0:
                all_positive = False
                break
        if all_positive:
            return L_LLL[i][:-1]
        all_positive = True
res = fine(L_LLL)
print("最短向量: ", res)
assert list(res) == message
bash
最短向量:  (1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0)