QKD的物理原理

利用量子的测量特性来进行密钥方法而不是量子纠缠

基于以下物理原理

  • 量子具有4种自旋 |0> |1> |+> |-> ,分别在两个测量基 十 和 X 上

  • |0> |1> 自旋的量子只能在测量基 十 上测准

  • |+> |-> 自旋的量子只能在测量基 X 上测准

  • 使用错误测量基测量量子会扰动量子,使其在另一个测量基上的分量以1/2 1/2的概率分布到这个测量基,则只有1/2概率测准

  • 使用错误测量基测量量子会使量子自旋改变

基于上述原理的密钥基本方法

Alice发送特定自旋的光子给Bob

Bob选择随机测量基进行测量

Alice发送正确测量基

Bob将接收到的正确测量基与选择的测量基比较,判断是否选对测量基

Bob将测量基是否选择正确告知Alice

若测量基有效,则测量结果即为双方交换成功的秘密比特

进行若干次上述过程后,Alice和Bob就有了一组互相知道的密钥

抗第三方的原理

上面交换的过程是可以被监听的,第三方只要截获光子,根据Alice发送的正确测量基来获得测量结果后即可得到光子的状态并发送相同状态的光子给Bob

将以下QKD过程与上述进行比较:

Alice发送特定自旋的光子给Bob

Bob选择随机测量基进行测量

Bob发送选择的测量基

Alice将收到的测量基与正确测量基比较,结果告知Bob

若测量基有效,则测量结果即为双方交换成功的秘密比特

错误测量基会导致光子改变,因此若是第三方窃听了光子必然导致Bob收到的光子与Alice发出的不同,基于海森堡测不准原理,第三方不可能在不知道测量基的情况下复制出状态相同的光子。因此在第三方监听的情况下,第三方有1/2的概率选错测量基,1/2的概率由于测量使得光子自旋变化,这样就使得Bob收到的光子有1/4的概率与Alice发出时的状态不同。这为检测第三方监听提供了依据,但必须牺牲一部分交换结果来确认双方的结果是否相同才行。同时,若是第三方持续监听,将使得交换信道不可用。

从实现角度,由于光子的自旋即偏振,通过单光子发射器 发生的单光子通过偏振片即可生成垂直、水平、+45°或-45°的偏振态。

BB84协议

Alice发送需要交换的密钥长度给Bob

Alice发送一组特定自旋的光子给Bob

Bob选择随机测量基进行测量这组光子

Bob发送选择的测量基序列

Alice将收到的测量基序列与正确测量基序列比较,将结果序列告知Bob

将结果序列为1的位上的测量结果作为本轮交换得到的密钥比特

重复几轮可以得到一个密钥序列

Bob将一部分结果告知Alice,Alice返回验证结果,以此确认信道安全

若验证结果都为正确则剩下密钥可以使用,否则放弃之前交换的结果

重复上述过程至交换得到的密钥长度达到Alice一开始发送的目标长度

Alice使用得到的密钥对明文加密后发送给Bob

Bob使用该密钥对密文解密

例子

ANTCTF x D3CTF 2021 easyQuantum

先从流量包中提取所有data,可以看出是用pickle打包的numpy数组,逐个load得到数组流量如下

50000->52926:312
50000->52926:[array([0.+0.j, 1.+0.j]), array([0.70710678+0.j, 0.70710678+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([0.70710678+0.j, 0.70710678+0.j])]
52926->50000:[1, 1, 1, 0]
50000->52926:[0, 1, 1, 0]
50000->52926:[array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([0.+0.j, 1.+0.j]), array([0.+0.j, 1.+0.j])]
52926->50000:[1, 0, 0, 0]
50000->52926:[1, 0, 1, 1]
50000->52926:[array([1.+0.j, 0.+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([0.+0.j, 1.+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j])]
52926->50000:[0, 1, 1, 0]
50000->52926:[1, 1, 0, 0]
50000->52926:[array([0.70710678+0.j, 0.70710678+0.j]), array([1.+0.j, 0.+0.j]), array([1.+0.j, 0.+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j])]
52926->50000:
50000->52926:[array([1.+0.j, 0.+0.j]), array([1.+0.j, 0.+0.j]), array([0.70710678+0.j, 0.70710678+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j])]
52926->50000:
50000->52926:[array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j]), array([0.70710678+0.j, 0.70710678+0.j]), array([ 0.70710678-8.65956056e-17j, -0.70710678+8.65956056e-17j])]
52926->50000:[0, 0, 0, 0]
50000->52926:[0, 0, 0, 0]
...

50000为主动方, 52926 接收方

其中[array(x)]*4形式的就是模拟的光子发送

使用复数数组表示的量子不够直观,这里我们将其替换成易读的形式

中间有些由于Bob未响应而被放弃掉的光子序列我们也直接忽略掉

50000->52926:312
50000->52926:['|1>', '|+>', '|->', '|+>']
52926->50000:[1, 1, 1, 0]
50000->52926:[0, 1, 1, 0]
50000->52926:['|->', '|->', '|1>', '|1>']
52926->50000:[1, 0, 0, 0]
50000->52926:[1, 0, 1, 1]
50000->52926:['|0>', '|->', '|1>', '|->']
52926->50000:[0, 1, 1, 0]
50000->52926:[1, 1, 0, 0]
50000->52926:['|->', '|->', '|+>', '|->']
52926->50000:[0, 0, 0, 0]
50000->52926:[0, 0, 0, 0]
...

|0> 与 |+>的测量结果定义为0

|1> 与 |->的测量结果定义为1

对于第一组光子,则根据50000返回的有效数组[0,1,1,0]得知光子第二和第三个光子的测量结果是有效的,即得到密钥01,重复该过程即可得到整个密钥

继续处理下数据,并编写脚本

['|1>', '|+>', '|->', '|+>']
[1, 1, 1, 0]
[0, 1, 1, 0]
['|->', '|->', '|1>', '|1>']
[1, 0, 0, 0]
[1, 0, 1, 1]
['|0>', '|->', '|1>', '|->']
[0, 1, 1, 0]
[1, 1, 0, 0]
['|->', '|->', '|+>', '|->']
[0, 0, 0, 0]
[0, 0, 0, 0]
...
with open('qqq.txt','r') as f:
    qdata=f.readlines()
qdata=list(map(eval,qdata))

def nxor4(l1,l2):
    return [not l1[i]^l2[i] for i in range(4)]
def checkQbit(qbit):
    if qbit=='|0>' or qbit=='|+>':
        return 0
    if qbit=='|1>' or qbit=='|->':
        return 1
    print('[!]Not qbit')
    exit(1)
result=[]
for i in range(0,len(qdata),3):
    qbits=qdata[i]
    bob=qdata[i+1]
    vaild=qdata[i+2]
    for j in range(4):
        if vaild[j]:
            result.append(checkQbit(qbits[j]))
print(''.join(map(str,result)))
print(len(result))
cipher=0x1ff55f399084148d6d3c2f0d249e035f04726537e6449556c2aa5c088d7ada2d61099611b88fbb
cipher=list(map(eval,bin(cipher)[2:].rjust(312,'0')))
print(''.join(map(str,cipher)))
print(len(cipher))
def xor312(l1,l2):
    return [l1[i]^l2[i] for i in range(312)]
def array2str(array):
    raw=''.join(map(lambda x:'1' if x else '0',array))
    raw=[raw[i:i+8] for i in range(0,len(raw),8)]
    raw=map(lambda x:chr(int(x,2)),raw)
    raw=''.join(raw)
    return raw
res=xor312(result,cipher)
print(array2str(res))

#011110111100011000111100010011011111011011111111011011011011110000101001010111110101101001001011010100011100011101110100000111000110001100100000000000110110111111010101011101111110000000000010111100111100011000111011010110111111010001001000111011010100011100111000010000001110010101010111100011001110011011000110
#312
#000111111111010101011111001110011001000010000100000101001000110101101101001111000010111100001101001001001001111000000011010111110000010001110010011001010011011111100110010001001001010101010110110000101010101001011100000010001000110101111010110110100010110101100001000010011001011000010001101110001000111110111011
#312
#d3ctf{y1DcuFuYwCgRfX33uT1lgSy27jYIsF4i}

参考资料

GitHub-Qiskit实现的bb84过程

CSDN-量子密码

Zhihu-量子计算