Skip to content

背包密码

背包问题

假设有序列 W={W0,W1,,Wn},需要找到一个子集 wW,使得 wiwwi=S

这个问题是 NP hard 的。

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

超增背包问题

假设有一个超增序列(SIKW={W0,W1,,Wn},满足 Wi>j=0i1Wj,例如:W={2,3,7,14,30,57,120,251},找到一个子集 wW,使得 wiwwi=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=mWimodn

得到:

  • 公钥:pkB
  • 私钥:skW,n,m

e.g:

skW=(2,3,7,14,30,57,120,251)n=491m=41m1modn=12pkB=(82,123,287,83,248,373,10,471)

2. Encrypt

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

3. 解密

  • 计算 C=Cm1modn=54812mod491=193
  • 然后使用超增序列,容易解得 193=120+57+14+2,对应明文 P=[10010110]

使用格破解背包密码

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

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

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

Ci=0n1PiBi(modn)

以及

0i=0n1PiBi<nmaxBi

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

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

i=0n1PiBi=C

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

构造格

M=[In×nBn×10m×nCn×m]

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

[In×nBn×101×nCm×1]

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

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

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

v=Mx=[x0x1xn1i=0n1xiBixnC]T,其中 x=(x0,x1,,xn)Zn+1.

对于明文 P=(P0,,Pn1),并令 xi=Pii=0,,n1),xn=1。则对应的格向量为:

vtrue=[P0P1Pn1i=0n1PiBiC]T=[P0P1Pn10]T

因为 PiBi=C。因此,vtrue 是格 L 中的一个向量:

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

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

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

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

攻击步骤如下:

  1. 根据公钥 B 和密文 C 构造格基矩阵 M
  2. M 应用 LLL 算法,得到一组约简基 {b0,b1,,bn}
  3. 检查每个基向量: 若某向量 b 的最后一个分量为 0, 且前 n 个分量均为 0 或 1, 则该向量极可能就是 vtrue
  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×nBm×1]

在这里只有一条密文

[I16×16T16×101×16C1×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)