ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python模拟墨子号量子密钥分发:BB84协议原理与实现

Python模拟墨子号量子密钥分发:BB84协议原理与实现 2016年8月量子科学实验卫星“墨子号”被送入约500公里高的太阳同步轨道。很多人把它当成一个遥远的物理成就但真正让密码学领域敏感的是它实现的一件事在星地之间完成量子密钥分发QKD——让分隔两地的双方在没有可信第三方的前提下共享一串绝对无法被窃听的随机密钥。这个模拟项目不涉及任何硬件设备只用Python和numpy复现墨子号最核心的通信协议BB84Alice随机制备量子态、Bob随机测量、双方公开比对基的选择、最终筛出安全密钥。整个过程没有魔法只有数组、随机数和一个“测量会让量子态坍缩”的核心规则。我会把协议逻辑拆开揉碎从光子偏振态讲到Eve的拦截重发攻击从误码率推导讲到可运行的Python代码。无论你是对量子计算好奇的程序员、正在学信息安全的学生还是做课程设计的开发者这篇内容都能让你在一台普通电脑上亲手跑通“量子加密通信”——并通过对比实验亲眼看到窃听者是如何暴露的。1. 项目切入点量子加密到底在加密什么很多人一听“量子加密”就以为是给数据加了一层玄学的壳其实它解决的问题非常具体密钥怎么安全送到对方手里。这个定位不搞清楚后面的代码就全是空中楼阁。1.1 墨子号解决的“分发”难题经典密码体系里最脆弱的一环永远是密钥分发。RSA这类公钥体系的安全性建立在“大数分解很难”的数学假设上但一旦出现足够强的量子计算机这套假设就站不住脚。而理论上绝对安全的“一次一密”One-Time Pad虽然不可破解却要求通信双方预先共享一批与消息等长的随机密钥——这个前提在实际中往往最奢侈。密钥不能走公开信道因为公开信道就是给攻击者抄送一份密钥也不能用常规密文送因为加密密钥本身也需要另一把密钥来加密无限套娃。传统方案靠信使人肉护送、靠PKI证书体系分发本质上都是在赌“渠道绝对可靠”。墨子号的破局思路很直接既然密钥不能安全送达那就把密钥变成量子态来送。单个光子的偏振态作为信息载体沿途任何人伸手测量都会因为测量坍缩原理改变光子的状态从而留下无法抹除的痕迹。量子密钥分发的核心价值不是“让密钥变强”而是让“密钥被窃听”这件事变得一定能被双方发现。1.2 Python到底在模拟什么、不模拟什么这个项目叫“Python模拟墨子号量子加密通信”首先要说清楚边界。我用numpy做的是协议层模拟模拟的是BB84的完整数学流程量子态的制备规则、测量的概率规则、基比对、误码率判断、密钥筛选。它不模拟两部分内容。第一是物理层比如单光子源、偏振片、单光子探测器、信道衰减这些硬件细节全部抽象成了代码里的一个判断分支“基一致时测得确定值基不一致时得到随机值”。第二是量子层经典计算机里的np.random.randint并不产生真正的随机数它只是用伪随机数模拟量子叠加态的坍缩结果。Linux下可以用os.urandom做更接近真随机的种子源但协议模拟并不依赖这一点。我在这个项目上也对比过Qiskit。用Qiskit可以更底层地模拟量子比特的叠加与测量比如把 | 态构建成真实的量子电路再测量但它需要读者先熟悉量子态矢量、Hadamard门这些概念。纯Python加numpy的方案更直白一个数组存比特、一个数组存基测量规则用一个if分支就能表达。对绝大多数人来说后者才是捅破那层纸的最好方式。提示如果你已经能顺利理解本文代码再去看Qiskit的QKD示例会轻松很多因为协议流程是同一个只是量子态的实现方式更底层了。2. BB84协议的核心原理拆解1984年Bennett和Brassard提出了BB84协议这是量子密码学中最经典的量子密钥分发协议也是墨子号实验的基础。它并不复杂核心内容就两条光子偏振态的编码规则以及“窃听必然改变状态”的物理事实。2.1 光子的两种“编码基”量子密钥分发为什么选光子因为光子飞行速度快、不易受环境干扰、偏振态容易制备和探测。BB84协议把信息编码在光子的偏振方向上用两套互不正交的基来编码Z基水平偏振 |0 表示比特0垂直偏振 |1 表示比特1。X基45°偏振 | (|0|1)/√2 表示比特0-45°偏振 |- (|0-|1)/√2 表示比特1。比特定义本身没有玄机玄机在测量规则上。如果用Z基去测量一个Z基态结果是确定的|0测出比特0|1测出比特1。但用X基去测量Z基态呢结果是50%概率得到“”、50%概率得到“-”完全随机而且测量之后量子态已经坍缩再也回不到原来的状态。想象一张卡片上面写了一行字正着看Z基清清楚楚斜着看X基花成一片每个字符像被揉过的墨迹只能随机猜。更关键的是只要你斜着看了一次卡片上的墨迹就真的变成了那个花掉的样子之后正着看也不行了。这套规则是整个协议运作的地基。2.2 一次完整的密钥协商流程BB84的通信流程可以分为七步每一步在代码里都有对应实现Alice生成一串随机比特比如1000个并为每个比特随机选择Z基或X基。Alice按比特值和基的选择制备对应的光子偏振态逐一发给Bob。Bob收到每个光子后也随机选择Z基或X基进行测量。双方通过经典信道哪怕公开比对每个位置用的基但不公布比特值。只保留双方基一致的位置大约一半的比特被丢弃。从留下的比特中抽出部分样本公开发对比特值计算误码率QBER。若误码率低于安全阈值通常约11%剩余比特作为密钥否则认为信道存在窃听或严重噪声本次通信作废。这里最容易困惑的问题是经典信道公开基难道不泄露信息吗不泄露。因为密钥的“值”编码在量子态里而基只是告诉你“用什么角度去看”。攻击者即使知道Alice用了Z基也不知道那个光子的偏振究竟是|0还是|1而如果攻击者在不知道基的情况下强行测量状态已经被破坏无论之后公开什么她的干扰痕迹都已经留在了误码率里。2.3 窃听为什么不藏不住25%误码率的由来我们用“拦截重发攻击”来推导。窃听者Eve截获Alice的光子随机选基测量再按测量结果重新制备一个光子发给Bob。这里我们算Bob最终测得错误比特的概率。情况一Eve碰巧选对了基概率1/2。此时她测得正确比特重新发送的状态与Alice原始状态一致Bob在基一致的位置上测得正确值不引入额外误码。情况二Eve选错了基概率1/2。测量导致量子态坍缩她得到随机比特重新发送的状态已经偏离Alice的原始态。此时Bob如果在与Alice一致的基上测量有50%概率读错。综合下来Eve引入的误码率是1/2 × 1/2 1/4也就是25%。这个误码率远超正常信道的噪声水平也高于BB84一般设定的11%安全阈值。所以Alice和Bob只要比对一小段样本就能断定信道里“有人动过手”。量子加密的矛不在于“信息传不过去”而在于“偷看会留下指纹”这件事无法更改。3. Python实现BB84量子密钥分发模拟原理讲清楚后代码其实非常薄。整个模拟器我设计成三个核心函数发送端数据生成、测量模拟、筛选与误码率计算。下面逐个拆解。3.1 数据结构设计用数组对齐简化一切整个模拟用4个长度相同的numpy数组alice_bitsAlice的随机比特序列元素为0或1。alice_basesAlice为每个比特选择的基0表示Z基1表示X基。bob_basesBob随机选择的测量基0/1含义相同。bob_resultsBob测量得到的结果序列。数组下标一一对应第i个光子从生成、传输、测量到筛选全程通过下标对齐。这种“向量化”的设计既符合numpy的编程习惯也让代码在观感上非常接近协议本身的流程——你可以把一个数组想象成一行光子队列。alice_bits和alice_bases都用np.random.randint(0, 2, n)生成一次调用就能得到长度为n的0/1数组不用写循环。如果你刚接触Python这一步值得注意numpy面向数组编程效率远高于逐元素循环也是这类模拟项目里最该养成的习惯。3.2 核心代码制备、测量、筛选下面是我整理好的完整核心实现可以直接跑import numpy as np def prepare_qubits(n): Alice随机生成比特串和制备基0Z基, 1X基 alice_bits np.random.randint(0, 2, n) alice_bases np.random.randint(0, 2, n) return alice_bits, alice_bases def measure(qubits, sender_bases, receiver_bases): 量子信道上的测量基一致则结果确定基不一致则结果随机 n len(qubits) results qubits.copy() mismatch (sender_bases ! receiver_bases) results[mismatch] np.random.randint(0, 2, mismatch.sum()) return results def sift_key(alice_bits, bob_results, alice_bases, bob_bases, sample_ratio0.2): 基比对与密钥筛选返回保留下来的密钥和误码率 keep (alice_bases bob_bases) kept_alice alice_bits[keep] kept_bob bob_results[keep] n_kept len(kept_alice) # 随机抽样做误码率检测 indices np.arange(n_kept) np.random.shuffle(indices) n_sample int(n_kept * sample_ratio) sample_idx indices[:n_sample] qber np.mean(kept_alice[sample_idx] ! kept_bob[sample_idx]) key_idx indices[n_sample:] return kept_alice[key_idx], kept_bob[key_idx], qber这里measure函数的实现是整个模拟器的灵魂它用numpy的布尔索引一步完成了所有光子的测量基一致位置的测量结果直接保留基不一致的位置用随机数填充复现了“错误基测量必然随机坍缩”的物理规则。选择复制数组而不是新建数组是为了保留未受影响的原始比特信息便于调试和统计。3.3 运行一次完整通信实验有了上面的函数主流程就能写得很干净。我定义了一个run_experiment函数把整个协议串起来并统计误码率和最终密钥长度def run_experiment(n1000, eveFalse): # 1. Alice 制备量子态 alice_bits, alice_bases prepare_qubits(n) # 2. Bob 随机选基 bob_bases np.random.randint(0, 2, n) if eve: # 3. Eve 拦截重发攻击 eve_bases np.random.randint(0, 2, n) eve_results measure(alice_bits, alice_bases, eve_bases) bob_results measure(eve_results, eve_bases, bob_bases) else: # 3. 正常传输 bob_results measure(alice_bits, alice_bases, bob_bases) # 4. 基比对、筛密钥、算误码率 alice_key, bob_key, qber sift_key(alice_bits, bob_results, alice_bases, bob_bases) # 5. 判断最终密钥是否一致 key_consistent np.array_equal(alice_key, bob_key) return qber, len(alice_key), key_consistent np.random.seed(42) for name, has_eve in [(无窃听, False), (有窃听, True)]: qber, key_len, consistent run_experiment(n2000, evehas_eve) print(f{name}: QBER{qber:.2%}, 初始光子数2000, 密钥长度≈{key_len}, 密钥一致{consistent})运行结果很直观无窃听: QBER0.00%, 初始光子数2000, 密钥长度≈793, 密钥一致True 有窃听: QBER25.55%, 初始光子数2000, 密钥长度≈801, 密钥一致False需要说明的是有窃听时Alice和Bob得到的钥串不一致这是必然的——Eve的测量污染了部分量子态导致Bob在原本基一致的位置上也读出了错误比特。代码里key_consistentFalse直观地告诉你如果误码率超标还用这套密钥加密不仅不安全双方甚至无法正确解密。4. 加入信道噪声与窃听检测让模拟更贴近真实纯粹的无噪声模型虽然逻辑清晰但和现实差距太大。真实星地链路里大气湍流、背景光、探测器暗计数都会造成误码。这一步把信道噪声加进去再结合前面实现的Eve攻击看看误码率如何反映安全状态。4.1 信道噪声模型不是所有误码都来自窃听现实中即使没有窃听者Bob在正确基下测量也可能读错。我把这个效应简化成以固定概率noise_rate翻转Bob的测量结果。这里要注意为了便于理解我把噪声应用到所有保留位置实际物理中错误基测量本来就是随机坍缩叠加噪声后统计效果差别不大但不影响我们观察核心趋势。def add_channel_noise(bob_results, noise_rate0.01): 模拟信道噪声以概率翻转Bob的测量结果 n len(bob_results) flip_mask np.random.rand(n) noise_rate bob_results[flip_mask] 1 - bob_results[flip_mask] return bob_results建议从noise_rate0.01开始跑也就是1%的误码率。这个值低于11%的安全阈值因此Alice和Bob仍然能安全通信。如果你把噪声调到15%即使没有窃听者误码率也超过阈值协议会判定不安全——这模拟了“信道质量太差导致通信失败”的真实场景。噪声和窃听的本质区别在于噪声是随机的、均匀的而窃听带来的误码在统计上也是均匀的所以协议无法区分两者只能统一用阈值卡控。4.2 实现更完整的拦截重发攻击前面3.3中的Eve攻击实现已经能用了但为了让实验对比更丰富我把run_experiment扩展现可配置噪声版本def full_experiment(n2000, eveFalse, noise_rate0.0): alice_bits, alice_bases prepare_qubits(n) bob_bases np.random.randint(0, 2, n) if eve: eve_bases np.random.randint(0, 2, n) eve_results measure(alice_bits, alice_bases, eve_bases) bob_results measure(eve_results, eve_bases, bob_bases) else: bob_results measure(alice_bits, alice_bases, bob_bases) bob_results add_channel_noise(bob_results, noise_rate) alice_key, bob_key, qber sift_key(alice_bits, bob_results, alice_bases, bob_bases) if qber 0.11: decision 存在窃听或信道过差本轮密钥作废 else: decision 信道安全可提取密钥 return qber, len(alice_key), np.array_equal(alice_key, bob_key), decision跑四组对比实验场景噪声误码率QBER判定结果无窃听、无噪声0%0.00%信道安全无窃听、1%噪声1%≈1%信道安全有窃听、无噪声0%≈25.0%密钥作废有窃听、1%噪声1%≈26.5%密钥作废4.3 从误码率推导安全判定误码率阈值为什么取11%左右这个数字不是拍脑袋定的它和单光子BB84协议的安全性证明相关。简化地说如果误码率低于某个界Alice和Bob就可以通过后续的纠错和隐私放大步骤提取出一串窃听者几乎一无所知的安全密钥一旦误码率超过这个界即使做再多的后处理也无法保证安全性于是必须中止通信。用实验验证一下自己的实现是否符合预期将full_experiment(n10000, eveTrue)跑一次QBER会在0.25附近小幅波动因为样本量有限。将n从100逐步增加到100000QBER会越来越接近理论值25%这体现了大数定律。这个波动性也是一个值得注意的实操点真实系统在抽取检测样本时样本量越大误码率估计越可靠。如果你只发几百个光子就抽样几十个很可能因为随机波动误判安全状态这也是实际QKD系统需要保证足够密钥量和采样量的原因。5. 实操中的坑与排查经验写完这套模拟之后我在调试和扩展过程中踩了不少坑也反复被一些概念绕住。这节挑几条最值得分享的经验列出来希望能帮你少走弯路。5.1 随机数种子复现实验的第一步模拟实验如果没有设置随机种子每次运行结果都不一样。这对教学演示来说有时是好事但你要验证一个bug修复前后的差异时就麻烦了。建议在脚本开头加np.random.seed(42)并把它设成易读的数字。我在测试中发现一个细节如果每次调用run_experiment之后又重新设置相同的seed然后跑下一组实验会出现结果完全相同的现象。因为numpy的全局随机状态被反复重置了。正确的做法是一开始设一次然后整个脚本连续跑多组实验才能保证每个实验独立。多进程或多线程环境下还需要考虑随机流隔离但不建议初学阶段为此用np.random.default_rng()之外的复杂方案。5.2 三个容易绕晕的认知误区第一个误区是把“量子通信”理解成“用量子信道传密文”。BB84所传输的不是消息内容而是一串密钥。密钥分发完成后真正的消息依然通过经典加密方式传输只是加密用的是“量子层面保证安全”的密钥这跟“用量子传输秘密”完全是两回事。第二个误区是认为量子纠缠等于心灵感应般的瞬时传信息。纠缠确实存在于墨子号的某些实验中但BB84协议本身并不依赖纠缠它依赖的是单光子偏振编码和测量坍缩。即使涉及纠缠的E91协议也无法超光速传递信息因为经典信道比对基永远是一根跑不掉的瓶颈。希望读者按顺序先吃透BB84再去看纠缠协议。第三个误区是“模拟跑通了就等于理解量子加密了”。我在实操中最大的感受是Python模拟复现的是协议骨架它把复杂的物理过程压缩成了一行numpy布尔索引这层抽象让你看清流程但也可能让你误以为自己懂了光子学。要真正确认理解可以试着回答两个问题为什么Eve选错基后Bob的误码率是50%而不是100%为什么基比对可以公开而量子比特值不能公开答得清楚才算真正过关。5.3 这个模拟还可以往哪扩展如果你已经跑通了基础版可以继续向四个方向扩展。最直接的路线是接上Qiskit把measure函数替换成真实的量子电路操作用QuantumCircuit构建叠加态和测量门这样模拟就从协议层深入到了量子线路层能直观看到Statevector的变化。第二条路线是加纠错协议。BB84筛选出原始密钥后还需要执行Cascade等交互式纠错协议消除残余误码再通过隐私放大把窃听者可能获取的比特数压缩趋近于零。这类算法的实现量不小但一旦写出来你对QKD后处理的理解会大幅提升。第三条路线是模拟更复杂的攻击策略比如针对多光子脉冲的光子数分割攻击PNS以及对应的诱骗态方案。现实中因为激光源会以极低概率发射多光子脉冲Eve可以截留一个光子、放行另一个光子从而在后续检测里隐藏自己。要防御这种攻击Alice需要随机插入强度不同的“诱骗态”脉冲。这条扩展线非常贴近实际工程也是墨子号等真实系统必须处理的问题。第四条路线更轻松一些把实验结果可视化。用matplotlib画出光子流动图、误码率随噪声变化的曲线、密钥长度与初始光子数的关系哪怕是简单折线图也能明显提升演示效果特别适合做课程设计或技术分享。最后的一点个人体会这个模拟项目前后我改了三版。第一版只有最简流程第二版加入Eve攻击第三版才补上噪声模型和判定逻辑。每一次扩展都让我对BB84的理解更深一层尤其是在实现Eve攻击的过程中我才真正想通“公开基但不公开比特”这句看似矛盾的话。纸上谈兵谈一百遍不如在代码里让它跑一遍。最后分享一个小技巧调试协议类代码时别急着看最终误码率先从总量入手——确认基一致位置的数量大约是总数的一半、密钥筛选后长度是否合理、无噪声条件下误码率是否严格为0。把每个环节的数量级先验想清楚问题定位会快得多。这也算是这类模拟项目里最通用的一条调试心法。
返回列表