量子密钥分发B92协议实战:如何用Python模拟两态协议(附代码)

量子通信听起来总是笼罩着一层神秘的面纱,仿佛离我们日常的开发工作很遥远。但作为一名对前沿技术充满好奇的开发者,我始终相信,理解其核心原理最好的方式,就是动手把它“跑”起来。B92协议,作为量子密钥分发(QKD)家族中一个优雅而简洁的成员,它用最少的两个量子态就实现了安全密钥的协商,这本身就充满了数学和物理的美感。与其在复杂的公式和理论中打转,不如我们直接打开代码编辑器,用Python来一场从零开始的模拟实验。本文将带你一步步构建一个完整的B92协议模拟器,从偏振态的生成、测量,到最终的密钥协商与误差分析,我们不仅会看到协议如何工作,更会深入探究其背后的“非正交态”原理,以及在实际模拟中可能遇到的坑。准备好了吗?让我们开始这段从量子理论到可执行代码的奇妙旅程。

1. 环境准备与核心概念铺垫

在开始敲代码之前,我们需要确保手头的工具齐全,并对B92协议的几个核心思想有一个直观的理解。这不同于理论学习,我们的目标是建立一种“计算直觉”。

首先,创建一个干净的Python环境至关重要。我推荐使用condavenv来管理依赖,避免包版本冲突。核心库只需要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 密钥协商流程

  1. 结果比对:Bob通过经典信道告知Alice哪些位置(索引)的测量是有效的。他并不透露他用了哪种测量基,也不透露测量结果本身
  2. 密钥提取:Alice和Bob各自保留有效位置对应的比特。对于Alice,就是她当初发送的原始比特。对于Bob,则需要根据他的测量结果和有效性规则,推导出对应的比特值。根据B92协议设计,在无噪声无窃听的情况下,他们推导出的比特应该是完全相同的。
  3. 形成原始密钥:上述步骤后,双方就获得了一段相同的二进制序列,称为原始密钥。
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创建一系列图表:

  1. 协议流程概览图:用时间线或流程图展示Alice的比特、选择的态、Bob的测量基、结果及有效性标记,适合少量比特(如20个)的演示。
  2. 密钥协商过程堆叠图:展示原始比特数、筛选后密钥比特数、抽样检测比特数、最终安全密钥比特数随协议步骤的演变。
  3. 误码率(QBER)分析图:在多次独立模拟中,绘制QBER的分布直方图。在无窃听无噪声情况下,QBER应集中在0附近;引入窃听或噪声后,QBER分布会向右移动。
  4. 协议效率统计:计算并展示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_bitis_measurement_valid函数的实现需要严格遵循B92协议的有效性判定表,这是整个模拟正确与否的关键。

动手实现一遍之后,你会发现量子密钥分发不再是遥不可及的理论,而是一系列清晰、可计算的步骤。这种从原理到代码的映射,是理解任何复杂协议最有效的方式。你可以尝试扩展这个模拟器:加入简单的窃听者模型,研究误码率随窃听强度的变化;或者模拟不同强度的信道噪声,观察协议安全阈值的实际表现。这些探索,会让你对“安全性由物理定律保证”这句话,有更深刻和具体的认识。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐