量子密钥分发B92协议实战:如何用Python模拟两态协议(附代码)
量子密钥分发B92协议实战:如何用Python模拟两态协议(附代码)
量子通信听起来总是笼罩着一层神秘的面纱,仿佛离我们日常的开发工作很遥远。但作为一名对前沿技术充满好奇的开发者,我始终相信,理解其核心原理最好的方式,就是动手把它“跑”起来。B92协议,作为量子密钥分发(QKD)家族中一个优雅而简洁的成员,它用最少的两个量子态就实现了安全密钥的协商,这本身就充满了数学和物理的美感。与其在复杂的公式和理论中打转,不如我们直接打开代码编辑器,用Python来一场从零开始的模拟实验。本文将带你一步步构建一个完整的B92协议模拟器,从偏振态的生成、测量,到最终的密钥协商与误差分析,我们不仅会看到协议如何工作,更会深入探究其背后的“非正交态”原理,以及在实际模拟中可能遇到的坑。准备好了吗?让我们开始这段从量子理论到可执行代码的奇妙旅程。
1. 环境准备与核心概念铺垫
在开始敲代码之前,我们需要确保手头的工具齐全,并对B92协议的几个核心思想有一个直观的理解。这不同于理论学习,我们的目标是建立一种“计算直觉”。
首先,创建一个干净的Python环境至关重要。我推荐使用conda或venv来管理依赖,避免包版本冲突。核心库只需要numpy用于向量和矩阵运算,matplotlib用于结果可视化,seaborn可以让图表更美观。如果你追求极致的模拟速度,可以后续考虑引入numba进行加速,但初期理解阶段,纯Python和NumPy足矣。
# 创建并激活虚拟环境(以conda为例)
conda create -n qkd_b92 python=3.9
conda activate qkd_b92
# 安装核心依赖
pip install numpy matplotlib seaborn
B92协议的精髓在于“两个非正交的量子态”。什么是非正交?在经典比特世界里,0和1是截然不同、完全可区分的(正交)。但在量子比特(qubit)的希尔伯特空间中,情况变了。我们可以把量子态想象成一个指向布洛赫球面上某个点的箭头。两个非正交的态,意味着它们的箭头方向既不重合(不是同一个态),也不完全相反(不是正交态),而是成一个锐角。正是这种“既不完全相同,又不完全相反”的模糊性,构成了量子安全性的基石——量子不可克隆定理。攻击者无法完美复制一个未知的非正交态,任何尝试区分的测量都会不可避免地扰动系统,从而留下窃听的痕迹。
在B92协议中,发送方Alice使用两个特定的非正交态来编码比特0和1。常见的编码方式是使用光子的偏振方向:
- Alice用 水平偏振 (|H>) 或 45度偏振 (|+>) 来代表比特 0。
- Alice用 垂直偏振 (|V>) 或 135度偏振 (|->) 来代表比特 1。
注意,|H>和|+>是非正交的,|V>和|->也是非正交的。接收方Bob则随机选择两种测量基中的一种进行测量:水平/垂直基 或 45/135度基(对角基)。协议巧妙之处在于,只有当Bob的测量基恰好与Alice发送的态“部分匹配”时,他才能得到一个确定的、且与Alice原始比特相关的测量结果。其他情况,他的测量结果是完全随机的,这些结果会被双方在后续步骤中丢弃。
提示:在代码中,我们将用二维复向量来表示这些量子态。例如,|H> = [1, 0]^T, |V> = [0, 1]^T, |+> = [1/sqrt(2), 1/sqrt(2)]^T, |-> = [1/sqrt(2), -1/sqrt(2)]^T。
2. 构建B92协议模拟器:从态制备到测量
现在,我们进入核心的模拟环节。我们将把协议流程拆解成几个独立的函数,这样逻辑清晰,也便于调试和测试。
2.1 定义量子态与测量基
首先,我们在Python中定义协议所需的四个偏振态和两种测量基。测量基实际上是一组正交基矢,测量就是将量子态投影到某个基矢上,其概率是投影幅度的平方。
import numpy as np
# 定义四个偏振态 (均为列向量)
def quantum_states():
"""返回B92协议使用的四个量子态"""
H = np.array([[1], [0]], dtype=complex) # 水平偏振 |H>
V = np.array([[0], [1]], dtype=complex) # 垂直偏振 |V>
P = np.array([[1/np.sqrt(2)], [1/np.sqrt(2)]], dtype=complex) # 45度偏振 |+>
M = np.array([[1/np.sqrt(2)], [-1/np.sqrt(2)]], dtype=complex) # 135度偏振 |->
return {'H': H, 'V': V, 'P': P, 'M': M}
# 定义两种测量基
def measurement_bases():
"""返回两种测量基,每种基包含两个正交基矢"""
# 水平/垂直基 (Z基)
basis_Z = [np.array([[1], [0]], dtype=complex), # |H>
np.array([[0], [1]], dtype=complex)] # |V>
# 45/135度基 (X基)
basis_X = [np.array([[1/np.sqrt(2)], [1/np.sqrt(2)]], dtype=complex), # |+>
np.array([[1/np.sqrt(2)], [-1/np.sqrt(2)]], dtype=complex)] # |->
return {'Z': basis_Z, 'X': basis_X}
2.2 Alice的随机态制备
Alice需要生成一串随机的原始密钥比特,并为每个比特随机选择一个编码态。在B92中,编码规则是:比特0对应{|H>, |+>}中的一个随机选择,比特1对应{|V>, |->}中的一个随机选择。
def alice_prepare_keys(key_length):
"""
Alice准备原始密钥和对应的量子态。
参数:
key_length: 要生成的密钥比特长度
返回:
raw_bits: Alice的原始密钥比特列表 (0/1)
chosen_states: 为每个比特选择的量子态名称列表 ('H','V','P','M')
state_vectors: 对应的量子态向量列表
"""
states = quantum_states()
# 编码映射: 比特0 -> ['H', 'P']; 比特1 -> ['V', 'M']
encoding_map = {0: ['H', 'P'], 1: ['V', 'M']}
raw_bits = np.random.randint(0, 2, key_length)
chosen_states = []
state_vectors = []
for bit in raw_bits:
# 从对应比特的两个态中随机选一个
possible_states = encoding_map[bit]
chosen_name = np.random.choice(possible_states)
chosen_states.append(chosen_name)
state_vectors.append(states[chosen_name])
return raw_bits, chosen_states, state_vectors
2.3 Bob的随机测量与结果筛选
Bob收到(在模拟中就是接收到)Alice发送的态向量列表后,对每个态独立地随机选择一种基进行测量。量子测量是一个概率过程:我们将态向量投影到所选测量基的两个基矢上,计算投影概率,然后根据这个概率随机坍缩到一个确定的结果。
def bob_measure_states(state_vectors):
"""
Bob对接收到的量子态进行随机测量。
参数:
state_vectors: Alice发送的量子态向量列表
返回:
chosen_bases: Bob为每个态选择的测量基列表 ('Z' 或 'X')
results: 测量结果列表 (0 或 1,对应所选基的第一个或第二个基矢)
valid_mask: 布尔列表,标记哪些测量结果是“有效的”(可用于生成密钥)
"""
bases = measurement_bases()
basis_names = ['Z', 'X']
chosen_bases = []
results = []
valid_mask = []
for state_vec in state_vectors:
# 1. 随机选择测量基
basis_choice = np.random.choice(basis_names)
chosen_bases.append(basis_choice)
measurement_basis = bases[basis_choice]
# 2. 计算投影到两个基矢上的概率
# 概率 = |<basis_state|state>|^2
prob0 = np.abs(np.dot(measurement_basis[0].conj().T, state_vec).item())**2
prob1 = np.abs(np.dot(measurement_basis[1].conj().T, state_vec).item())**2
# 理论上 prob0 + prob1 = 1,但浮点数计算可能有微小误差
prob0 = prob0 / (prob0 + prob1)
# 3. 根据概率随机得到测量结果 (0 或 1)
measurement_outcome = 0 if np.random.rand() < prob0 else 1
results.append(measurement_outcome)
# 4. 判断该结果是否“有效”
# B92协议的有效结果规则:
# - 如果Bob用Z基测量得到结果1(即|V>),那么Alice发送的必须是|+>(比特0)或|->(比特1)?等等,需要仔细推导。
# 更准确的判断:只有当Bob的测量结果能唯一推断出Alice发送的是两个非正交态中的哪一个时,该结果才有效。
# 让我们用一个映射表来实现这个逻辑判断。
valid_mask.append(is_measurement_valid(state_vec, measurement_basis, measurement_outcome))
return chosen_bases, results, valid_mask
def is_measurement_valid(state_vec, basis, outcome):
"""
根据B92协议规则判断一次测量结果是否有效。
简化逻辑:在B92中,只有当Bob的测量基与Alice发送的态“正交”到另一个态时,结果才是确定且有效的。
例如:Alice发送|H>(0),Bob用X基测量,若得到|->(1),则这个结果是有效的,因为|H>在|-<上的投影为0,得到|->的唯一可能是Alice发了|+>?不,这里需要查表。
为了避免复杂的实时计算,我们使用预定义的有效性对照表。
"""
# 我们将态向量简化为其名称进行查表。这是一个更工程化的方法。
# 注意:在实际完整代码中,我们需要将state_vec反向映射到态名称('H','V','P','M')。
# 此处为说明原理,我们假设已知态名称。
pass # 具体查表逻辑将在完整代码示例中实现
为了更清晰地理解Bob在何种情况下能获得有效结果,我们来看下面这个对应关系表。它列出了Alice发送的态、Bob选择的测量基以及可能结果的有效性。
| Alice发送的态 (及编码比特) | Bob选择的测量基 | Bob得到结果0 (对应基矢1) | Bob得到结果1 (对应基矢2) | 结果是否有效 (可生成密钥) |
|---|---|---|---|---|
| H> (比特0) | Z基 ( | H>, | V>) | |
| H> (比特0) | X基 ( | +>, | ->) | |
| +> (比特0) | Z基 ( | H>, | V>) | |
| +> (比特0) | X基 ( | +>, | ->) | |
| V> (比特1) | Z基 ( | H>, | V>) | |
| V> (比特1) | X基 ( | +>, | ->) | |
| -> (比特1) | Z基 ( | H>, | V>) | |
| -> (比特1) | X基 ( | +>, | ->) |
注意:上表是一个简化的原理示意。严格来说,有效性判断基于“测量结果是否排除了另一个非正交态的可能性”。例如,当Alice发送|H>,Bob用X基测量并得到|->时,这个结果不可能在Alice发送|+>的情况下发生(因为<+|-> = 0)。因此,Bob可以100%确定Alice发送的是|H>,这是一个有效结果。模拟代码需要精确实现这一逻辑。
3. 密钥协商、窃听检测与误差分析
在Bob完成测量并标记出有效结果后,双方需要通过经典信道(在我们的模拟中就是内存共享)进行协商,最终得到一致的密钥。
3.1 密钥协商流程
- 结果比对:Bob通过经典信道告知Alice哪些位置(索引)的测量是有效的。他并不透露他用了哪种测量基,也不透露测量结果本身。
- 密钥提取:Alice和Bob各自保留有效位置对应的比特。对于Alice,就是她当初发送的原始比特。对于Bob,则需要根据他的测量结果和有效性规则,推导出对应的比特值。根据B92协议设计,在无噪声无窃听的情况下,他们推导出的比特应该是完全相同的。
- 形成原始密钥:上述步骤后,双方就获得了一段相同的二进制序列,称为原始密钥。
def sift_raw_keys(alice_bits, bob_results, valid_mask):
"""
筛原始密钥:双方保留有效测量位置对应的比特。
参数:
alice_bits: Alice的原始比特序列
bob_results: Bob的测量结果序列
valid_mask: 有效性布尔序列
返回:
sifted_alice_key: Alice筛选后的密钥
sifted_bob_key: Bob筛选后的密钥
sifted_indices: 被筛选位置的索引列表
"""
sifted_indices = [i for i, is_valid in enumerate(valid_mask) if is_valid]
sifted_alice_key = [alice_bits[i] for i in sifted_indices]
# Bob需要将测量结果映射为密钥比特。在B92中,映射关系取决于他使用的基和结果。
# 我们需要一个额外的函数,根据Bob的测量基和结果,以及有效性标志,来推断比特值。
# 此处简化表示,假设一个函数 bob_result_to_bit 存在。
sifted_bob_key = [bob_result_to_bit(bob_results[i], chosen_bases[i]) for i in sifted_indices]
return sifted_alice_key, sifted_bob_key, sifted_indices
3.2 窃听检测与误码率计算
量子密钥分发的安全性建立在窃听会引入错误这一基础上。为了检测潜在的窃听者Eve,Alice和Bob会从协商好的原始密钥中随机抽取一部分比特进行公开比较。
def estimate_error_rate(sifted_alice_key, sifted_bob_key, sample_ratio=0.2):
"""
通过随机抽样部分比特进行公开比对,估计量子比特误码率(QBER)。
参数:
sifted_alice_key, sifted_bob_key: 筛选后的密钥
sample_ratio: 抽样比例
返回:
qber: 量子比特误码率 (0到1之间)
sample_indices: 被抽样的索引位置
disclosed_bits_alice: Alice公开的抽样比特
disclosed_bits_bob: Bob公开的抽样比特
"""
key_len = len(sifted_alice_key)
sample_size = int(key_len * sample_ratio)
if sample_size < 1:
return 0.0, [], [], []
# 随机选择抽样位置
sample_indices = np.random.choice(key_len, size=sample_size, replace=False)
sample_indices.sort()
disclosed_bits_alice = [sifted_alice_key[i] for i in sample_indices]
disclosed_bits_bob = [sifted_bob_key[i] for i in sample_indices]
# 计算错误比特数
error_count = sum(1 for a, b in zip(disclosed_bits_alice, disclosed_bits_bob) if a != b)
qber = error_count / sample_size if sample_size > 0 else 0.0
# 重要:公开比对后,这些抽样比特必须从最终密钥中丢弃!
return qber, sample_indices, disclosed_bits_alice, disclosed_bits_bob
如果计算出的QBER低于某个安全阈值(例如,对于B92协议,在理想无噪信道中,任何窃听都会引入至少25%的错误?这里需要根据协议安全性证明来设定,通常是一个较小的值,如11%或15%),那么双方就可以认为信道是安全的,或者窃听不存在/可容忍。随后,他们丢弃用于检测的比特,对剩余的密钥进行隐私放大等后处理步骤,最终生成高度安全且随机的最终密钥。如果QBER超过阈值,则协议终止,本次密钥分发失败。
3.3 模拟误差来源与分析
在真实的物理系统中,误差主要来自信道噪声、探测器暗计数、光源不完美等。在我们的数值模拟中,虽然不存在物理噪声,但我们可以通过程序引入“模拟误差”来研究协议的性能。
- 信道损耗模拟:以一定概率随机“丢弃”光子(即Bob未接收到任何信号)。这会导致有效密钥长度减少。
def apply_channel_loss(state_vectors, loss_probability): """模拟信道损耗,以loss_probability概率将态向量设为None""" transmitted_states = [] for state in state_vectors: if np.random.rand() < loss_probability: transmitted_states.append(None) # 光子丢失 else: transmitted_states.append(state) return transmitted_states - 测量误差模拟:在Bob的测量结果中,以一个小概率翻转比特(0变1,1变0),模拟探测器误判。
- 窃听者攻击模拟:可以引入一个简单的“拦截-重发”攻击模型。Eve拦截Alice发送的态,自己随机选择基进行测量,然后根据她的测量结果,重新制备一个她认为正确的态发送给Bob。这必然会引入额外的错误。
通过调整这些误差参数并运行多次模拟,我们可以绘制出密钥生成率、最终密钥长度与信道损耗、误码率之间的关系图,直观地理解B92协议在不同环境下的鲁棒性。
4. 结果可视化与协议效率探讨
一次模拟运行后,我们需要直观的工具来评估结果。可视化不仅能验证代码正确性,还能加深对协议动态过程的理解。
4.1 关键指标可视化
我们可以用matplotlib创建一系列图表:
- 协议流程概览图:用时间线或流程图展示Alice的比特、选择的态、Bob的测量基、结果及有效性标记,适合少量比特(如20个)的演示。
- 密钥协商过程堆叠图:展示原始比特数、筛选后密钥比特数、抽样检测比特数、最终安全密钥比特数随协议步骤的演变。
- 误码率(QBER)分析图:在多次独立模拟中,绘制QBER的分布直方图。在无窃听无噪声情况下,QBER应集中在0附近;引入窃听或噪声后,QBER分布会向右移动。
- 协议效率统计:计算并展示B92协议的理论效率(~25%)与实际模拟效率的对比。效率定义为最终安全密钥长度除以Alice发送的总光子数。
import matplotlib.pyplot as plt
import seaborn as sns
def visualize_protocol_run(alice_bits, bob_results, valid_mask, sifted_indices, qber):
"""可视化单次协议运行的主要结果"""
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# 图1: Alice和Bob的比特序列对比 (前50个位置)
ax1 = axes[0, 0]
positions = np.arange(min(50, len(alice_bits)))
ax1.stem(positions, alice_bits[:len(positions)], linefmt='b-', markerfmt='bo', basefmt=' ', label="Alice's Bits")
# 需要将bob_results对齐,无效位置设为-1以示区别
bob_display = []
for i in range(len(positions)):
if i < len(valid_mask) and valid_mask[i]:
bob_display.append(bob_results[i])
else:
bob_display.append(-0.5) # 无效位置用特殊值表示
ax1.stem(positions, bob_display[:len(positions)], linefmt='r--', markerfmt='rx', basefmt=' ', label="Bob's Valid Results")
ax1.set_xlabel('Photon Sequence Index')
ax1.set_ylabel('Bit Value / Result')
ax1.set_title('Bit Comparison (First 50 Photons)')
ax1.legend()
ax1.grid(True, alpha=0.3)
# 图2: 密钥生成各阶段长度
ax2 = axes[0, 1]
stages = ['Raw Bits', 'Sifted Key', 'After Error\nEstimation']
# 假设抽样比例为20%
sample_ratio = 0.2
sifted_len = len(sifted_indices)
final_len = int(sifted_len * (1 - sample_ratio))
values = [len(alice_bits), sifted_len, final_len]
bars = ax2.bar(stages, values, color=['skyblue', 'lightgreen', 'salmon'])
ax2.set_ylabel('Number of Bits')
ax2.set_title('Key Length at Different Stages')
for bar, v in zip(bars, values):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height()+0.5, str(v), ha='center')
# 图3: 有效性标记分布
ax3 = axes[1, 0]
valid_count = sum(valid_mask)
invalid_count = len(valid_mask) - valid_count
ax3.pie([valid_count, invalid_count], labels=['Valid', 'Invalid'], autopct='%1.1f%%', colors=['gold', 'lightcoral'])
ax3.set_title(f'Measurement Validity (Total: {len(valid_mask)})')
# 图4: 误码率显示
ax4 = axes[1, 1]
ax4.bar(['Estimated QBER'], [qber*100], color='orange') # 以百分比显示
ax4.set_ylabel('Quantum Bit Error Rate (%)')
ax4.set_ylim(0, max(20, qber*100*1.5))
ax4.text(0, qber*100/2, f'{qber*100:.2f}%', ha='center', va='center', color='white', fontweight='bold')
ax4.set_title('Error Rate from Public Comparison')
plt.tight_layout()
plt.show()
4.2 B92协议效率的深入思考
通过多次模拟,你会直观地验证B92协议的一个关键特性:其固有效率较低。即使在理想情况下,也只有约25%的发送光子能贡献给最终密钥。这与BB84协议的50%效率形成对比。为什么?
根源在于两态非正交编码的设计。Bob的测量有一半的概率选择“错误”的基(相对于Alice的态而言),这导致结果完全随机,必须丢弃。即使在选择“正确”基的情况下,也只有部分测量结果能提供确定信息。这种低效率是B92为简化系统(只用两个态)所付出的代价。
然而,效率低不等于无用。在某些对光源要求苛刻、或者需要极大简化发射端设备的场景中,B92的简单性可能成为优势。我们的模拟可以帮助量化这种权衡:你可以修改代码,在同样的信道损耗和噪声模型下,对比B92和BB84(需要实现BB84模拟)的最终密钥生成率和鲁棒性。
注意:在编写完整模拟代码时,务必处理好所有边界条件,例如当信道损耗导致Bob未接收到态时,测量函数应返回一个特殊标识。此外,
bob_result_to_bit和is_measurement_valid函数的实现需要严格遵循B92协议的有效性判定表,这是整个模拟正确与否的关键。
动手实现一遍之后,你会发现量子密钥分发不再是遥不可及的理论,而是一系列清晰、可计算的步骤。这种从原理到代码的映射,是理解任何复杂协议最有效的方式。你可以尝试扩展这个模拟器:加入简单的窃听者模型,研究误码率随窃听强度的变化;或者模拟不同强度的信道噪声,观察协议安全阈值的实际表现。这些探索,会让你对“安全性由物理定律保证”这句话,有更深刻和具体的认识。
更多推荐


所有评论(0)