Quantum Key Distribution - B92

The Quantum Key Distribution B92 protocol was proposed in 1992 by Charles Bennett. It is a modified version of the BB84 protocol. This protocol is different from the BB84 in the following ways:

  1. It uses two possible states of qubits being sent instead of four.

  2. Alice and Bob do not need to compare bases at any point.

A detailed description of this protocol can be found here

First, we create a network with three hosts, Alice, Bob and Eve. Alice will send her qubits to Bob and Eve will, or will not, eavesdrop and manipulate the qubits she intercepts. The network will link Alice to Eve, Eve to Alice and Bob, and Bob to Eve. Here is also the place to define which function will Eve run if the eavesdropping is turned on.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def build_network_b92(eve_interception):

    network = Network.get_instance()
    network.start()

    host_alice = Host('Alice')
    host_bob = Host('Bob')
    host_eve = Host('Eve')

    host_alice.add_connection('Eve')
    host_eve.add_connections(['Alice', 'Bob'])
    host_bob.add_connection('Eve')
    # Adding the connections - Alice wants to transfer an encrypted message to Bob
    # The network looks like this: Alice---Eve---Bob

    host_alice.delay = 0.3
    host_bob.delay = 0.3

    # starting
    host_alice.start()
    host_bob.start()
    host_eve.start()

    network.add_hosts([host_alice, host_bob, host_eve])

    if eve_interception == True:
        host_eve.q_relay_sniffing = True
        host_eve.q_relay_sniffing_fn = eve_sniffing_quantum

    hosts = [host_alice,host_bob,host_eve]
    print('Made a network!')
    return network, hosts

First, a random encryption key of a certain length is generated. The key is a list of binary numbers.

1
2
3
4
5
6
def generate_key(key_length):
    generated_key = []
    for i in range(key_length):
        generated_key.append(randint(0, 1))
    print(f'Generated the key {generated_key}')
    return generated_key

We now implement the B92 protocol. For each bit in the encrypted key, Alice generates and sends a qubit to Bob. If the bit she wants to send is 0, she sends a \(|0\rangle\), and if the bit is 1, she sends \(|+\rangle\).

Then, after she sends the qubit, she waits for a (classical) message from Bob. If Bob tells her he measured the qubit and extracted the information, she prepares and sends the next one. Otherwise, she sends the information concerning this bit again and again, until a confirmation is received.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
def sender_qkd(alice, secret_key, receiver):
    sent_qubit_counter = 0
    for bit in secret_key:
        success = False
        while success == False:
            qubit = Qubit(alice)
            if bit == 1:
                qubit.H()
            # If we want to send 0, we'll send |0>
            # If we want to send 1, we'll send |+>
            alice.send_qubit(receiver, qubit, await_ack = True)
            message = alice.get_next_classical(receiver, wait = -1)
            if message is not None:
                if message.content == 'qubit successfully acquired':
                    print(f'Alice sent qubit {sent_qubit_counter + 1} to Bob')
                    success = True
                    sent_qubit_counter += 1
                # if, however, message says Bob failed to measure the qubit,
                # Alice will resend it.

Bob receives the qubit Alice sent, and randomly chooses a base (rectilinear or diagonal) for the qubit measurement. If he chooses the rectilinear basis, and the measurement yields the state \(|1\rangle\), then Bob knows that Eve’s qubit was \(|+\rangle\), and therefore the bit she sent was 1. Bob sends Alice a classical message after the measurement and tells her whether he succeeded or failed to extract the information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def receiver_qkd(bob, key_size, sender):
    key_array = []
    received_counter = 0
    #counts the key bits successfully measured by Bob
    while received_counter < key_size:
        base = randint(0,1)
             # 0 means rectilinear basis and 1 means diagonal basis
        qubit = bob.get_qubit(sender,wait = wait_time)
        if qubit is not None:
            if base == 1:
                qubit.H()
            bit = qubit.measure()
            if bit == 1:
                if base == 1:
                    resulting_key_bit = 0
                elif base == 0:
                    resulting_key_bit = 1
                message_to_send = 'qubit successfully acquired'
                key_array.append(resulting_key_bit)
                received_counter += 1
                print(f'Bob received qubit {received_counter}')
            else:
                message_to_send = 'fail'
            bob.send_classical(sender,message_to_send, await_ack = True)
    return key_array

After sending all of the bits of the encrypted key, Alice and Bob should check whether Eve eavesdropped on the channel. For this purpose, each of them takes for comparison a certain part of the encrypted key they have. Alice classically sends part of the key she has to Bob. He compares it to the part he has. If it’s the same information, he sends her a message informing her that the key was transferred without eavesdropping. If, however, the part he receives is different from what he has, it means eavesdropping occurred and he sends a message to inform Alice about it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def check_key_sender(alice, key_check_alice, receiver):
    key_check_string = ''.join([str(x) for x in key_check_alice])
    print(f'Alice\'s key to check is {key_check_string}')
    alice.send_classical(receiver, key_check_string, await_ack=True)
    message_from_bob = alice.get_next_classical(receiver, wait = -1)
    # Bob tells Alice whether the key part is the same at his end.
    # If not - it means Eve eavesdropped.
    if message_from_bob is not None:
        if message_from_bob.content == 'Success':
            print('Key is successfully verified')
        elif message_from_bob.content == 'Fail':
            print('Key has been corrupted')
1
2
3
4
5
6
7
8
9
def check_key_receiver(bob, key_check_bob,sender):
    key_check_bob_string = ''.join([str(x) for x in key_check_bob])
    print(f'Bob\'s key to check is {key_check_bob_string}')
    key_from_alice = bob.get_next_classical(sender, wait = -1)
    if key_from_alice is not None:
        if key_from_alice.content == key_check_bob_string:
            bob.send_classical(sender, 'Success', await_ack=True)
        else:
            bob.send_classical(sender, 'Fail', await_ack=True)

If the eavesdropping is turned on, Eve runs the function eve_sniffing_quantum. She intercepts a qubit that is sent from Alice to Bob. She measures the qubit with probability 0.5. For the measurement of the qubit, she randomly chooses the basis (rectilinear or diagonal).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def eve_sniffing_quantum(sender,receiver,qubit):
    # Eve will manipulate only part of the qubits she intercepts
    # She chooses the base in which she measures at random.
    if sender == 'Alice':
        r = random()
        if r > 0.5:
            base = randint(0,1)
            if base == 1:
                qubit.H()
            qubit.measure(non_destructive = True)

The full example is given below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# This example implements the B92 QKD protocol using the QuNetSim package

from qunetsim.components import Host, Network
from qunetsim.objects import Qubit
from qunetsim.objects import Logger
from random import randint, random

Logger.DISABLED = True
wait_time = 60


def eve_sniffing_quantum(sender, receiver, qubit):
    # Eve will manipulate only part of the qubits she intercepts
    # She chooses the base in which she measures at random.
    if sender == 'Alice':
        r = random()
        if r > 0.5:
            base = randint(0, 1)
            if base == 1:
                qubit.H()
            qubit.measure(non_destructive=True)


def build_network_b92(eve_interception):

    network = Network.get_instance()

    nodes = ['Alice', 'Bob', 'Eve']
    network.start(nodes)

    host_alice = Host('Alice')
    host_bob = Host('Bob')
    host_eve = Host('Eve')

    host_alice.add_connection('Eve')
    host_eve.add_connections(['Alice', 'Bob'])
    host_bob.add_connection('Eve')
    # adding the connections - Alice wants to transfer an encrypted message to Bob
    # The network looks like this: Alice---Eve---Bob

    host_alice.delay = 0.3
    host_bob.delay = 0.3

    # starting
    host_alice.start()
    host_bob.start()
    host_eve.start()

    network.add_hosts([host_alice, host_bob, host_eve])

    if eve_interception == True:
        host_eve.q_relay_sniffing = True
        host_eve.q_relay_sniffing_fn = eve_sniffing_quantum

    hosts = [host_alice, host_bob, host_eve]
    print('Made a network!')
    return network, hosts


def generate_key(key_length):
    generated_key = []
    for i in range(key_length):
        generated_key.append(randint(0, 1))
    print(f'Generated the key {generated_key}')
    return generated_key


def sender_qkd(alice, secret_key, receiver):
    sent_qubit_counter = 0
    for bit in secret_key:
        success = False
        while success == False:
            qubit = Qubit(alice)
            if bit == 1:
                qubit.H()
            # If we want to send 0, we'll send |0>
            # If we want to send 1, we'll send |+>
            alice.send_qubit(receiver, qubit, await_ack=True)
            message = alice.get_next_classical(receiver, wait=-1)
            if message is not None:
                if message.content == 'qubit successfully acquired':
                    print(f'Alice sent qubit {sent_qubit_counter+1} to Bob')
                    success = True
                    sent_qubit_counter += 1
                # if, however, message says Bob failed to measure the qubit, Alice will resend it.


def receiver_qkd(bob, key_size, sender):
    key_array = []
    received_counter = 0
    # counts the key bits successfully measured by Bob
    while received_counter < key_size:
        base = randint(0, 1)
        # 0 means rectilinear basis and 1 means diagonal basis
        qubit = bob.get_qubit(sender, wait=wait_time)
        if qubit is not None:
            if base == 1:
                qubit.H()
            bit = qubit.measure()
            if bit == 1:
                if base == 1:
                    resulting_key_bit = 0
                elif base == 0:
                    resulting_key_bit = 1
                message_to_send = 'qubit successfully acquired'
                key_array.append(resulting_key_bit)
                received_counter += 1
                print(f'Bob received qubit {received_counter}')
            else:
                message_to_send = 'fail'
            bob.send_classical(sender, message_to_send, await_ack=True)
    return key_array


def check_key_sender(alice, key_check_alice, receiver):
    key_check_string = ''.join([str(x) for x in key_check_alice])
    print(f'Alice\'s key to check is {key_check_string}')
    alice.send_classical(receiver, key_check_string, await_ack=True)
    message_from_bob = alice.get_next_classical(receiver, wait=-1)
    # Bob tells Alice whether the key part is the same at his end.
    # If not - it means Eve eavesdropped.
    if message_from_bob is not None:
        if message_from_bob.content == 'Success':
            print('Key is successfully verified')
        elif message_from_bob.content == 'Fail':
            print('Key has been corrupted')


def check_key_receiver(bob, key_check_bob, sender):
    key_check_bob_string = ''.join([str(x) for x in key_check_bob])
    print(f'Bob\'s key to check is {key_check_bob_string}')
    key_from_alice = bob.get_next_classical(sender, wait=-1)
    if key_from_alice is not None:
        if key_from_alice.content == key_check_bob_string:
            bob.send_classical(sender, 'Success', await_ack=True)
        else:
            bob.send_classical(sender, 'Fail', await_ack=True)


def alice_func(host, bob_id, length_of_check, key_length):
    encryption_key_binary = generate_key(key_length)
    sender_qkd(host, encryption_key_binary, bob_id)
    print('Sent all the qubits sucessfully!')
    key_to_test = encryption_key_binary[0:length_of_check]
    check_key_sender(host, key_to_test, bob_id)


def bob_func(host, alice_id, length_of_check, key_length):
    secret_key_bob = receiver_qkd(host, key_length, alice_id)
    key_to_test = secret_key_bob[0:length_of_check]
    check_key_receiver(host, key_to_test, alice_id)


def b92_protocol(eve_interception, key_length, length_of_check):
    network, hosts = build_network_b92(eve_interception)
    alice = hosts[0]
    bob = hosts[1]
    bob_id = bob.host_id
    alice_id = alice.host_id

    thread_1 = alice.run_protocol(alice_func, (bob_id, length_of_check, key_length,))
    thread_2 = bob.run_protocol(bob_func, (alice_id, length_of_check, key_length,))

    thread_1.join()
    thread_2.join()

    network.stop(True)
    exit()


if __name__ == '__main__':
    key_length = 10
    length_of_check = round(key_length / 2)
    # length of part of the key used to check whether Eve listened
    eve_interception = True
    # the eavesdropping can be turned on and off
    b92_protocol(eve_interception, key_length, length_of_check)