今天我们将分析背包加密算法(Knapsack Cryptosystem),这是一种基于陷门问题的非对称加密算法。背包加密算法的安全性依赖于背包问题的困难性,这使得它成为一个有趣的研究对象。

不过,我们要知道,所有基于超递增序列和模变换的背包,其陷门结构存在致命漏洞:模乘法无法完全隐藏超递增序列的结构,格基规约(LLL) 可高效还原私钥。所以绝大多数背包加密都已破解,不再使用了,但它的设计思想和分析方法对于理解现代密码学的原理仍然非常有帮助。

这里我们只介绍最经典的 Merkle-Hellman 背包加密算法,其他的背包加密算法基本上都是在这个基础上进行改进的,只有部分是基于格的(但我们不在这里讲解)。

Merkle-Hellman 背包加密数学原理 😇

Merkle-Hellman 背包加密的安全性基于背包问题的困难性。

背包问题是指给定一组物品的重量和一个背包的容量,判断是否存在一种组合使得物品的总重量恰好等于背包的容量。

举个例子:有集合 $A = {3, 5, 7}$ 和一个背包容量 $C = 10$,我们需要判断是否存在一个子集 $S \subseteq A$ 使得 $\sum_{x \in S} x = C$。在这个例子中,子集 ${3, 7}$ 的总重量为 $10$,因此答案是存在的。

这里介绍一下超递增序列。一个序列 $S = {s_1, s_2, …, s_n}$ 如果,都有

那么这个序列是超递增的。例如,序列 ${1, 3, 7, 15}$ 是超递增的,因为每个元素都大于前面所有元素的和。

我们尝试将一个超递增序列 $A = {3, 4, 9, 17, 35}$,乘一个数 $t = 19$,再取模 $k = 73$,其中 $k$ 大于 $t$ 和 $s_n$ 的乘积(即 $19 \times 35 = 665 < 73$),得到一个新的序列 $B = {57, 3, 25, 31, 8}$。

在不知道 $t$ 和 $k$ 的情况下,拿到序列 $B$,我们无法判断它是否是一个超递增序列,也无法还原出原来的序列 $A$。这就是背包加密算法的核心思想,通过模乘法来隐藏超递增序列的结构,使得攻击者无法轻易地破解密文。

而超递增序列构造的背包问题很好求解,因为每个元素都大于前面所有元素的和,所以我们可以从最大的元素开始,依次判断是否可以减去当前元素来得到目标值,直到找到一个解或者确定没有解,这个可以使用贪心算法解决。

Merkle-Hellman 背包加密过程 😁

一天,Alice 想要向 Bob 发送一条秘密消息。为了确保消息的安全性,Alice 决定使用 Merkle-Hellman 背包加密算法。

首先,Bob 选择一个超递增序列 $A$,并选择一个整数 $t$ 和一个模数 $k$,满足 $k > \sum_{i=1}^{n} s_i$,当然 $k$ 和 $t$ 互质。
接着,Bob 计算一个新的序列 $B = {b_i}$,其中 $b_i = (s_i \cdot t) \mod k$。
Bob 将序列 $B$ 作为公钥发送给 Alice,而私钥则由序列 $A$、整数 $t$ 和模数 $k$ 组成,Bob 将这些信息保密。

Alice 收到 Bob 的公钥后,首先将消息 $M$ 转换为一个二进制字符串,然后将其分成 $n$ 位的块(和 $B$ 序列元素个数相同)。
对于每个块,Alice 计算密文 $C$,其中 $C = \sum_{i=1}^{n} m_i \cdot b_i$,其中 $m_i$ 是块中的第 $i$ 位。然后,Alice 将密文 $C$ 发送给 Bob。

Bob 收到密文后,可以使用私钥中的信息来解密密文。首先,Bob 计算 $C’ = (C \cdot t^{-1}) \mod k$,其中 $t^{-1}$ 是 $t$ 的模逆元。然后,Bob 使用序列 $A$ 来还原出原始消息 $M$。

Merkle-Hellman 背包加密算法实现 🥰

下面是一个简单的 Merkle-Hellman 背包加密算法的 Python 实现示例:

from sage.all import randint, gcd, inverse_mod

# 1. 生成密钥 (Bob)
n = 80 # 背包长度,即一次能加密的比特数
w = [] # 超递增序列 (私钥)
current_sum = 0
for _ in range(n):
    # 生成超递增序列
    val = randint(current_sum + 1, current_sum + 100)
    w.append(val)
    current_sum += val

# 选择模数 q,使得 q > sum(w)
q = randint(current_sum + 1, current_sum * 2)

# 选择乘数 r,使得 gcd(r, q) = 1
while True:
    r = randint(2, q - 1)
    if gcd(r, q) == 1:
        break

# 计算公钥序列 beta
beta = [(val * r) % q for val in w]
public_key = beta
# 私钥为 (w, q, r)

# 2. 加密 (Alice)
M = "hello_Bob" # 待加密的消息
long_M = int.from_bytes(M.encode(), 'big') # 转为整数

# 转换为比特串
M_bin = bin(long_M)[2:]
if len(M_bin) > n:
    print("Error: Message is too long for the key size!")
else:
    # 补齐到 n 位
    M_bin = M_bin.zfill(n)
    m_bits = [int(b) for b in M_bin]

    # 计算密文 C = sum(m_i * beta_i)
    C = sum(m * b for m, b in zip(m_bits, beta))

    # 3. 解密 (Bob)
    # 计算 r 的逆元
    r_inv = inverse_mod(r, q)

    # 计算 c' = C * r^-1 mod q
    c_prime = (C * r_inv) % q

    # 求解超递增背包问题 (贪心算法)
    decrypted_bits = [0] * n
    current_val = c_prime

    # 从最大的元素开始判断
    for i in range(n - 1, -1, -1):
        if current_val >= w[i]:
            decrypted_bits[i] = 1
            current_val -= w[i]
        else:
            decrypted_bits[i] = 0

    # 将比特串还原为整数
    decrypted_int = int("".join(map(str, decrypted_bits)), 2)

    # 还原为字符串
    byte_len = (decrypted_int.bit_length() + 7) // 8
    M_val = int(decrypted_int) # 确保类型为 Python int
    flag = int.to_bytes(M_val, length=byte_len, byteorder='big').decode()
    print(flag)

实际上由于 sage 中的整数类型和 python 中的整数类型不同,所以在非必要情况下不要纯使用sage函数进行算法实现,可以多使用python的密码学库使用。

上一章:加解密模式分析 - ECC 加密算法 👈
下一章:加解密模式分析 - DH 密钥交换协议 👈
回到开始:关于我 👈