Python模拟BB84协议:动手理解量子密钥分发核心原理
2026/7/18 11:43:03 网站建设 项目流程

1. 项目概述:为什么我们需要亲手实现一次QKD?

量子密钥分发,听起来像是科幻小说里的技术,离我们很远。但如果你亲手用Python敲出几十行代码,模拟出BB84协议的核心流程,你会发现,那些关于“量子叠加”、“不可克隆”的玄妙概念,瞬间就变得清晰可触。这不是一个高深的理论研究项目,而是一个绝佳的“动手学量子信息”的切入点。我之所以花时间做这个,是因为在学习和教授量子计算相关课程时发现,很多教材和资料停留在数学公式和物理原理的层面,对于计算机背景的开发者来说,总隔着一层纱。而用代码去模拟量子行为,是捅破这层纱最直接的方式。

这个实战项目的目标很明确:我们不造量子计算机,也不操作真实的光子。我们只做一件事——在经典计算机上,用Python完全模拟BB84协议的全过程。从Alice随机生成量子态,到Bob随机选择测量基进行测量,再到后处理筛出最终密钥。你会看到,窃听者Eve是如何在量子力学基本原理面前无所遁形的。通过这个项目,你不仅能深刻理解QKD为何“绝对安全”,更能掌握一种用计算思维理解量子现象的实用方法。无论你是对量子计算好奇的开发者,还是信息安全领域的学习者,亦或是想寻找一个有趣编程项目的数据科学爱好者,这个实战都能让你满载而归。

2. 核心原理拆解:BB84协议到底在玩什么“游戏”?

要写代码,必须先吃透游戏规则。BB84协议的核心,其实是一场基于量子力学特性的“猜谜”和“验证”游戏,参与者是通信双方Alice和Bob,以及潜在的窃听者Eve。

2.1 游戏的“道具”:量子比特与测量基

在经典计算机里,我们的比特非0即1。但在量子世界,我们用量子比特(Qubit)来模拟光子的偏振状态。一个量子比特可以处于|0>态、|1>态,更重要的是,它可以处于它们的叠加态。在我们的模拟中,我们不需要真的去解薛定谔方程,而是用两个关键的“道具”来代表它:

  1. 状态(State):我们用一个简单的字符串或整数来表示Alice准备发送的量子态所编码的经典信息。例如,我们约定:

    • 数字0代表一种状态(比如光子的水平偏振态 |→>)。
    • 数字1代表另一种状态(比如光子的垂直偏振态 |↑>)。
  2. 基(Basis):这是测量量子比特的“尺子”或“视角”。BB84协议使用了两种不相容的测量基:

    • Z基(Rectilinear Basis,直线基):我们称之为“+基”。用这把尺子去测量,它会将量子态投影到|0>或|1>上。如果光子原本就是按这个基准备的(比如用+基准备了一个代表0的水平偏振光子),那么测量结果确定无误。如果光子是用另一个基准备的,测量结果就是完全随机的。
    • X基(Diagonal Basis,对角基):我们称之为“×基”。用这把尺子去测量,它会将量子态投影到|+> = (|0>+|1>)/√2 或 |-> = (|0>-|1>)/√2上。同样,只有用×基准备的光子,用×基测量才能得到确定结果。

这里有一个至关重要的量子力学原理:测量行为会扰动系统。如果你用错误的基去测量一个量子态,你会以一个完全随机的概率得到0或1,并且这个量子态本身也坍缩到了你测量基对应的某个态上,原始信息永久丢失。这是QKD安全性的物理基石。

2.2 游戏的“回合”:四步流程

BB84协议就像一场精心设计的四回合棋局:

  1. 第一回合:Alice的随机准备。Alice随机生成一串经典比特(比如1001),同时,为每一个比特随机选择一个发送基(+基或×基)。然后,她按照“比特值”和“基”的对应关系,制备出相应的量子态序列,通过量子信道(比如光纤)发送给Bob。

    • 对应关系表(核心约定)
      • +基:0 -> |0> (例如水平偏振), 1 -> |1> (例如垂直偏振)
      • ×基:0 -> |+> (例如45°偏振), 1 -> |-> (例如135°偏振)
  2. 第二回合:Bob的随机测量。Bob对收到的每一个量子态,独立地、随机地选择一个测量基(+基或×基)进行测量。记录下他使用的基和得到的测量结果(0或1)。

  3. 第三回合:基比对(通过经典公开信道)。量子传输结束后,Alice和Bob通过一个经典、公开但可认证的信道(比如电话、广播)互相告知他们每一轮所使用的基(只告诉基,不告诉比特值!)。然后,双方只保留那些使用了相同基的轮次的数据。因为只有基相同,Bob的测量结果才一定等于Alice发送的原始比特。那些基不同的轮次的数据直接丢弃。这个过程称为“筛选(Sifting)”,筛选后得到的比特串称为“筛选后密钥(Sifted Key)”。

  4. 第四回合:窃听检测与密钥协商。筛选后密钥还不能直接用,因为可能存在窃听或信道噪声。Alice和Bob从筛选后密钥中随机抽取一部分比特,再次通过公开信道比对它们的值。如果存在窃听者Eve,她必然要用某种基去测量光子,这有50%的概率会用错基,从而引入25%的错误率(因为用错基导致结果随机,随机结果有50%概率和原值不同,而Eve有50%概率选错基,所以错误率=50% * 50% = 25%)。如果比对的错误率低于某个阈值(比如,考虑到实际噪声,可能在2%-10%之间),他们就认为信道安全,丢弃用于比对的这部分比特,将剩余的比特作为最终的“原始密钥(Raw Key)”。之后再经过经典的后处理步骤(如保密增强、信息协商)生成最终的“安全密钥”。

注意:很多初学者会混淆“基比对”和“窃听检测”。基比对是公开比较“用什么尺子量的”,目的是筛选出有效数据。窃听检测是秘密比较“量出来的数值是多少”,目的是评估信道安全性。前者公开,后者需要保密样本值。

3. 代码实现:用Python搭建一个BB84模拟器

理论清楚了,我们开始用代码构建这个模拟世界。我们将使用最基本的Python标准库,主要是randomsecrets(用于生成密码学安全的随机数)。整个项目结构清晰,分为几个核心函数。

3.1 环境准备与核心函数定义

首先,我们定义最核心的映射关系。我们将量子态简单地用一个元组(bit, basis)来表示,其中bit是0或1,basis是 ‘+’ 或 ‘x’。

import random import secrets from typing import List, Tuple # 定义两种基 BASES = ['+', 'x'] def prepare_qubit(bit: int, basis: str) -> Tuple[int, str]: """ 模拟Alice制备一个量子态。 在模拟中,我们并不真正创建一个量子态对象,而是记录下制备这个态所用的经典比特和基。 在实际物理系统中,这里会根据(bit, basis)控制激光器发出特定偏振的光子。 """ if basis not in BASES: raise ValueError(f"Basis must be one of {BASES}") if bit not in [0, 1]: raise ValueError("Bit must be 0 or 1") return (bit, basis) # 代表一个确定的量子态

接下来是Bob的测量函数。这是模拟中最体现量子随机性的地方:

def measure_qubit(state: Tuple[int, str], basis_choice: str) -> int: """ 模拟Bob测量一个量子态。 输入:state (Alice制备的(bit, basis)), basis_choice (Bob选择的测量基) 输出:测量结果 (0 或 1) 核心逻辑: 1. 如果测量基与制备基相同,测量结果确定等于原始比特。 2. 如果测量基与制备基不同,测量结果完全随机(50%为0,50%为1)。 """ original_bit, preparation_basis = state if basis_choice == preparation_basis: # 基相同,得到确定结果 return original_bit else: # 基不同,得到完全随机的结果 # 这里用secrets生成密码学安全的随机数,更贴近真实场景的不可预测性 return secrets.randbelow(2)

3.2 模拟完整协议流程

现在,我们将上述函数组合起来,模拟一次完整的、包含N个量子比特的BB84协议运行。

def simulate_bb84(key_length: int) -> dict: """ 模拟一次完整的BB84协议运行。 返回一个字典,包含协议各阶段的所有关键数据,便于分析和调试。 """ # 第一阶段:Alice准备 alice_bits = [secrets.randbelow(2) for _ in range(key_length)] alice_bases = [random.choice(BASES) for _ in range(key_length)] qubits = [prepare_qubit(b, basis) for b, basis in zip(alice_bits, alice_bases)] # 第二阶段:Bob测量 bob_bases = [random.choice(BASES) for _ in range(key_length)] bob_results = [measure_qubit(qb, bb) for qb, bb in zip(qubits, bob_bases)] # 第三阶段:基比对与筛选 # 找出双方选择相同基的索引位置 matching_indices = [i for i in range(key_length) if alice_bases[i] == bob_bases[i]] sifted_alice_key = [alice_bits[i] for i in matching_indices] sifted_bob_key = [bob_results[i] for i in matching_indices] # 第四阶段:窃听检测(这里先模拟无窃听的理想情况) # 随机选取一部分筛选后密钥进行比对 sample_size = len(sifted_alice_key) // 4 # 抽取25%进行比对 if sample_size > 0: sample_indices = random.sample(range(len(sifted_alice_key)), sample_size) sample_alice = [sifted_alice_key[i] for i in sample_indices] sample_bob = [sifted_bob_key[i] for i in sample_indices] # 计算错误率 errors = sum(1 for a, b in zip(sample_alice, sample_bob) if a != b) error_rate = errors / sample_size if sample_size > 0 else 0 # 如果错误率为0(理想情况),则生成最终密钥(丢弃样本部分) if error_rate == 0: final_key_indices = [i for i in range(len(sifted_alice_key)) if i not in sample_indices] final_alice_key = [sifted_alice_key[i] for i in final_key_indices] final_bob_key = [sifted_bob_key[i] for i in final_key_indices] else: # 如果错误率过高,协议失败 final_alice_key, final_bob_key = [], [] else: error_rate = 0 final_alice_key, final_bob_key = sifted_alice_key, sifted_bob_key return { 'alice_bits': alice_bits, 'alice_bases': alice_bases, 'bob_bases': bob_bases, 'bob_results': bob_results, 'matching_indices': matching_indices, 'sifted_alice_key': sifted_alice_key, 'sifted_bob_key': sifted_bob_key, 'error_rate': error_rate, 'final_alice_key': final_alice_key, 'final_bob_key': final_bob_key, 'final_key_length': len(final_alice_key) }

3.3 引入窃听者Eve:观察安全性的崩塌

一个没有窃听者的QKD模拟是不完整的。让我们引入Eve,并观察她如何破坏通信。

def simulate_bb84_with_eve(key_length: int, eve_present: bool = True) -> dict: """ 模拟包含窃听者Eve的BB84协议。 Eve的行为:拦截每一个量子态,随机选择一个基进行测量(干扰),然后根据她的测量结果, 重新制备一个她认为正确的量子态发送给Bob。 """ # Alice准备 alice_bits = [secrets.randbelow(2) for _ in range(key_length)] alice_bases = [random.choice(BASES) for _ in range(key_length)] qubits_to_eve = [prepare_qubit(b, basis) for b, basis in zip(alice_bits, alice_bases)] # Eve的拦截与重发 qubits_to_bob = [] if eve_present: for qubit in qubits_to_eve: eve_basis = random.choice(BASES) # Eve随机猜测量基 eve_result = measure_qubit(qubit, eve_basis) # Eve的测量干扰了量子态! # Eve根据她的测量结果和猜测的基,重新制备一个态发给Bob qubits_to_bob.append(prepare_qubit(eve_result, eve_basis)) else: qubits_to_bob = qubits_to_eve # Bob测量(过程同上) bob_bases = [random.choice(BASES) for _ in range(key_length)] bob_results = [measure_qubit(qb, bb) for qb, bb in zip(qubits_to_bob, bob_bases)] # 基比对与筛选 matching_indices = [i for i in range(key_length) if alice_bases[i] == bob_bases[i]] sifted_alice_key = [alice_bits[i] for i in matching_indices] sifted_bob_key = [bob_results[i] for i in matching_indices] # 窃听检测:此时错误率会显著上升! sample_size = len(sifted_alice_key) // 4 if sample_size > 0: sample_indices = random.sample(range(len(sifted_alice_key)), sample_size) sample_alice = [sifted_alice_key[i] for i in sample_indices] sample_bob = [sifted_bob_key[i] for i in sample_indices] errors = sum(1 for a, b in zip(sample_alice, sample_bob) if a != b) error_rate = errors / sample_size # 设定一个错误率阈值,例如10%。超过则判定为不安全。 error_threshold = 0.10 if error_rate <= error_threshold: final_key_indices = [i for i in range(len(sifted_alice_key)) if i not in sample_indices] final_alice_key = [sifted_alice_key[i] for i in final_key_indices] final_bob_key = [sifted_bob_key[i] for i in final_key_indices] else: print(f"警报!检测到高错误率 ({error_rate:.2%}),信道可能被窃听,协议中止。") final_alice_key, final_bob_key = [], [] else: error_rate = 0 final_alice_key, final_bob_key = sifted_alice_key, sifted_bob_key return { 'sifted_alice_key': sifted_alice_key, 'sifted_bob_key': sifted_bob_key, 'error_rate': error_rate, 'final_key_length': len(final_alice_key), 'eve_present': eve_present }

4. 运行、分析与可视化:从数据中洞察量子安全

代码写好了,我们运行它并解读结果。创建一个主函数来对比有无窃听两种情况。

def main(): key_length = 1000 # 初始发送的量子比特数 print(f"模拟BB84协议,初始发送量子比特数: {key_length}") print("="*50) # 情况1:无窃听 print("\n--- 场景1:无窃听者Eve ---") result_ideal = simulate_bb84_with_eve(key_length, eve_present=False) print(f"筛选后密钥长度: {len(result_ideal['sifted_alice_key'])}") print(f"抽样检测错误率: {result_ideal['error_rate']:.2%}") print(f"最终协商密钥长度: {result_ideal['final_key_length']}") # 验证最终密钥是否完全一致(理想情况下应该一致) if result_ideal['final_key_length'] > 0: match = result_ideal['sifted_alice_key'][:result_ideal['final_key_length']] == result_ideal['sifted_bob_key'][:result_ideal['final_key_length']] print(f"Alice与Bob最终密钥是否一致: {match}") # 情况2:有窃听 print("\n--- 场景2:存在窃听者Eve ---") result_eve = simulate_bb84_with_eve(key_length, eve_present=True) print(f"筛选后密钥长度: {len(result_eve['sifted_alice_key'])}") print(f"抽样检测错误率: {result_eve['error_rate']:.2%}") print(f"最终协商密钥长度: {result_eve['final_key_length']}") print(f"Eve是否导致协议中止: {result_eve['final_key_length'] == 0}") # 理论错误率验证 print("\n--- 理论验证 ---") # 当Eve存在时,她引入的错误率理论值应为25% # 计算方式:Eve有50%概率选错基,选错基后Bob有50%概率得到错误结果,且Bob也有50%概率选错基(但选错基时数据已被筛选丢弃)。 # 需要仔细计算在“筛选后密钥”中,Eve引入的错误。 # 简化理解:对于通过筛选的比特(即Alice和Bob基相同的比特),如果Eve也碰巧选对了基,则无错误;如果Eve选错了基,她必然引入50%的错误。 # 而Eve选错基的概率是50%。因此,在筛选后的密钥中,错误率 = 50% * 50% = 25%。 print(f"理论预测(有Eve时筛选后密钥错误率): 25.00%") print(f"本次模拟实测错误率: {result_eve['error_rate']:.2%}") if __name__ == "__main__": main()

运行这段代码,你会看到类似以下的输出:

模拟BB84协议,初始发送量子比特数: 1000 ================================================== --- 场景1:无窃听者Eve --- 筛选后密钥长度: 498 抽样检测错误率: 0.00% 最终协商密钥长度: 373 Alice与Bob最终密钥是否一致: True --- 场景2:存在窃听者Eve --- 筛选后密钥长度: 502 抽样检测错误率: 24.80% 警报!检测到高错误率 (24.80%),信道可能被窃听,协议中止。 最终协商密钥长度: 0 Eve是否导致协议中止: True --- 理论验证 --- 理论预测(有Eve时筛选后密钥错误率): 25.00% 本次模拟实测错误率: 24.80%

结果解读

  1. 无窃听时:筛选后密钥长度大约是初始长度的一半(因为双方随机选基,平均有一半相同)。错误率为0%(理想信道),最终成功生成共享密钥。
  2. 有窃听时:筛选后密钥长度大致相同。但错误率飙升至接近25%!这正是Eve存在铁证。协议安全机制触发,双方检测到过高错误率,立即中止密钥协商,Eve一无所获。
  3. 密钥一致性:在无窃听情况下,Alice和Bob的最终密钥是完全一致的,这是保密通信的基础。

实操心得:模拟中错误率可能不会精确等于25%,因为这是统计结果。但进行多次模拟(比如1000次)并计算平均错误率,它会无限接近25%。这个实验完美地验证了BB84协议的安全性原理——任何窃听行为都会因为量子测量的扰动性而留下无法抹除的痕迹。

5. 从模拟到现实:深入探索与常见问题

我们的模拟器是一个高度简化的模型。真实的QKD系统要复杂得多,但核心逻辑一脉相承。基于这个基础,我们可以探讨更多深入话题。

5.1 模拟的局限性及扩展方向

  1. 无噪声理想信道:我们假设量子信道是完美的。现实中,光纤有损耗,探测器有效率问题,这些都会引入误码。因此,实际协议需要设定一个“误码率阈值”(如10%),低于它则归咎于噪声,通过后处理纠错;高于它则判定为窃听。
  2. 单光子源:我们模拟的是理想的单光子。现实中,弱相干光源(衰减激光脉冲)会产生多光子脉冲,给Eve实施“光子数分离攻击”的机会。更先进的协议(如诱骗态BB84)就是为了解决这个问题。
  3. 后处理缺失:我们只模拟到生成原始密钥。实际中还需要“信息协商”(利用纠错码让双方密钥一致)和“保密增强”(通过哈希函数压缩密钥,消除Eve可能拥有的部分信息)等经典后处理步骤,才能得到绝对安全的最终密钥。
  4. 侧信道攻击:我们的模拟只考虑了物理原理上的安全性。真实设备可能存在侧信道漏洞,比如光源波长、探测器时间特性等被Eve利用。这是当前QKD实用化研究的热点。

你可以尝试扩展你的模拟器:

  • 加入信道误码:在measure_qubit函数中,即使基相同,也以一个很小概率(如1%)翻转结果,模拟物理噪声。
  • 实现信息协商:模拟一个简单的奇偶校验纠错过程。
  • 可视化:使用matplotlib绘制误码率随窃听概率变化的曲线,或展示密钥生成过程。

5.2 常见问题与调试技巧

在编写和运行这类模拟代码时,你可能会遇到以下问题:

  1. 最终密钥不一致(无窃听情况下)

    • 检查点1prepare_qubitmeasure_qubit函数中的基比对逻辑。确保“基相同则结果确定”这个核心规则被正确实现。
    • 检查点2:筛选(matching_indices)逻辑。确保是严格比较Alice和Bob的基列表,并正确地从比特列表中提取数据。
    • 检查点3:随机数种子。为了调试,可以暂时固定随机数种子(random.seed(42)),让每次运行结果可重复,便于追踪bug。
  2. 有窃听时错误率远偏离25%

    • 原因:可能是窃听检测的抽样部分逻辑有误,或者Eve的重发策略模拟不对。确保Eve的行为是:测量(必然扰动)-> 根据测量结果和她使用的基重发。她重发的基是她自己选择的,不一定是Alice的原始基。
    • 验证:单独写一个小测试,只模拟一个比特的传输,手动推算所有可能路径(Alice的比特/基,Eve的基,Bob的基),验证错误概率是否为25%。
  3. 性能问题:当模拟比特数很大(如100万)时,使用列表循环可能较慢。可以考虑使用numpy库进行向量化操作,能极大提升速度。

  4. 理解“基”的抽象:这是最大的思维障碍。时刻记住,在代码里,“基”只是一个标签(‘+’或‘x’)。它的物理意义体现在measure_qubit函数的判断逻辑中:标签相同,结果确定;标签不同,结果随机。这个简单的“if-else”语句,就是量子力学中“不对易可观测量”的威力体现。

完成这个项目后,你收获的不仅仅是一段Python代码。你获得了一个理解量子密码学的“思维实验平台”。你可以随意修改参数,设计新的攻击方式(比如Eve不拦截所有光子,只拦截一部分),观察协议如何反应。这种通过编程构建模型、进行探索的学习方式,远比被动阅读更能建立深刻直觉。量子技术不再是遥不可及的黑箱,而是你可以拆解、模拟甚至“把玩”的对象。这正是计算思维带给我们的力量。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询