The weak spot when dealing with key distribution is … the key being distributed. More precisely, you cannot know for sure the key has not been hijacked somewhere in between. If hijacked the key can be read. copied and re-sent without the receiver or sender knowing about it. Quantum computing helps here because:

- one cannot clone qubit. While bits can be cloned and re-transferred with qubits you can only instantiate new ones. Of course, new qubits with a particular value are indistinguishable of older ones with the same value but this assumes you know the value of the old ones.
- when you read a qubit you destroy it. Not literally making it disappear, but the collapse of the wave-function cannot be rolled back. A quantum state has no memory of how it was before a measurement turned (i.e. forced) it into a particular state. If you measure it, you alter it.

Taken together, this means that any attempt to measure or clone a qubit will alter the qubit or fail respectively. If two parties exchange a series of qubits they can detect if a third party tried to fiddle with the series and this is the essence of the BB84 protocol. From a philosophical point of view the essence of it relies on the fact that no-one, not anybody can see beyond the quantum randomness. When you collapse a quantum particle there is absolutely no way to tell where is will go (if using a different base than the original).

The BB84 protocol uses a quantum and a classical channel. Both channels can be completely open; anyone can listen but only the sender and receiver will in the end know the shared key. You can have only one received because any other will appear as eavesdropping. The algorithm goes as follows (using as usual Bob, Alice and Eve):

- Alice uses a light source to create a photon. Any two-state qubit realization is fine as well.
- The photon is sent through a polarizer and randomly given one of four possible polarization and bit designations — Vertical (One bit), Horizontal (Zero bit), 45 degree right (One bit), or 45 degree left (Zero bit).
- The photon travels to Bob’s location.
- Bob has two beamsplitters — a diagonal and vertical/horizontal – and two photon detectors.
- Bob randomly chooses one of the two beamsplitters and checks the photon detectors.
- The process is repeated until the entire key has been transmitted to Bob.
- Bob then tells Alice in sequence which beamsplitter he used.
- Alice compares this information with the sequence of polarizers she used to send the key.
- Alice tells Bob where in the sequence of sent photons he used the right beamsplitter.
- Now both Alice and Bob have a sequence of bits (sifted key) they both know.

The important part in this is that the actual bits, i.e. the actual measurement values, are not shared. The series of polarization filters are shared but not the bits. Together with the uncertainty inherent to the measurement it means that anyone can listen but can only guess what the measurement has yield.

Provided Alice and Bob perform this type of exchange long enough they will end up with a key of a certain length. Also note that the potential errors between the two parties could be due to the inevitable noise, so the detection of eavesdropping is bound to some threshold rathe than a true/false.

Below is a fun implementation of the B84 process. The essence of the quantum sits in the measurement function:

from numpy import matrix from math import pow, sqrt from random import randint import sys, argparse class qubit(): def __init__(self,initial_state): if initial_state: self.__state = matrix([[0],[1]]) else: self.__state = matrix([[1],[0]]) self.__measured = False self.__H = (1/sqrt(2))*matrix([[1,1],[1,-1]]) self.__X = matrix([[0,1],[1,0]]) def show(self): aux = "" if round(matrix([1,0])*self.__state,2): aux += "{0}|0>".format(str(round(matrix([1,0])*self.__state,2)) if round(matrix([1,0])*self.__state,2) != 1.0 else '') if round(matrix([0,1])*self.__state,2): if aux: aux += " + " aux += "{0}|1>".format(str(round(matrix([0,1])*self.__state,2)) if round(matrix([0,1])*self.__state,2) != 1.0 else '') return aux def measure(self): if self.__measured: raise Exception("Qubit already measured!") M = 1000000 m = randint(0,M) self.__measured = True if m < round(pow(matrix([1,0])*self.__state,2),2)*M: return 0 else: return 1 def hadamard(self): if self.__measured: raise Exception("Qubit already measured!") self.__state = self.__H*self.__state def X(self): if self.__measured: raise Exception("Qubit already measured!") self.__state = self.__X*self.__state class quantum_user(): def __init__(self,name): self.name = name def send(self,data,basis): assert len(data) == len(basis), "Basis and data must be the same length!" qubits = list() for i in range(len(data)): if not basis[i]: #Base computacional if not data[i]: qubits.append(qubit(0)) else: qubits.append(qubit(1)) else: #Base Hadamard if not data[i]: aux = qubit(0) else: aux = qubit(1) aux.hadamard() qubits.append(aux) return qubits def receive(self,data,basis): assert len(data) == len(basis), "Basis and data must be the same length!" bits = list() for i in range(len(data)): if not basis[i]: bits.append(data[i].measure()) else: data[i].hadamard() bits.append(data[i].measure()) return bits def generate_random_bits(N): aux = list() for i in range(N): aux.append(randint(0,1)) return aux def QKD(N,verbose=False,eve_present=False): alice_basis = generate_random_bits(N) alice_bits = generate_random_bits(N) alice = quantum_user("Alice") alice_qubits = alice.send(data=alice_bits,basis=alice_basis) if eve_present: eve_basis = generate_random_bits(N) eve = quantum_user("Eve") eve_bits = eve.receive(data=alice_qubits,basis=eve_basis) alice_qubits = eve.send(data=eve_bits,basis=eve_basis) bob_basis = generate_random_bits(N) bob = quantum_user("Bob") bob_bits = bob.receive(data=alice_qubits,basis=bob_basis) alice_key = list() bob_key = list() for i in range(N): if alice_basis[i] == bob_basis[i]: alice_key.append(alice_bits[i]) bob_key.append(bob_bits[i]) if alice_key != bob_key: key = False length = None print("Encription key mismatch, eve is present.") else: key = True length = len(bob_key) print("Successfully exchanged key!") print("Key Length: " + str(length)) if verbose: print("Alice generates {0} random basis.".format(str(N))) input() print(''.join(str(e) for e in alice_basis)) input() print("Alice generates {0} random bits.".format(str(N))) input() print(''.join(str(e) for e in alice_bits)) input() print("Alice sends to Bob {0} encoded Qubits.".format(str(N))) input() aux = "" for q in alice_qubits: aux += q.show() + " " print(aux) input() if eve_present: print("Eve intercepts Qubits!") input() print(''.join(str(e) for e in eve_basis)) input() print("Eve's bits.") input() print(''.join(str(e) for e in eve_bits)) input() print("Bob generates {0} random basis.".format(str(N))) input() print(''.join(str(e) for e in bob_basis)) input() print("Bob receives and decodes Alice's Qubits.") input() print(''.join(str(e) for e in bob_bits)) input() print("Alice and Bob interchange basis through Internet and compare their basis.") input() return key if __name__ == "__main__": parser = argparse.ArgumentParser(description='BB84 QKD demonstration with Python.') requiredNamed = parser.add_argument_group('Required arguments') optionalNamed = parser.add_argument_group('Optional arguments') requiredNamed.add_argument('-q','--qubits', required=True, help='Number of Qubits.') optionalNamed.add_argument('-i','--iterate',required=False, help='Number of iterations.') optionalNamed.add_argument('-e','--eve', action='store_true',default=False,required=False, help='Is EVE present?') optionalNamed.add_argument('-v','--verbose', action='store_true',default=False,required=False, help='Verbose logs.') args = parser.parse_args() assert int(args.qubits) ret = list() if args.iterate: assert int(args.iterate) N = int(args.iterate) else: N = 1 for i in range(N): print("############# {0} #############".format(str(i))) ret.append(QKD(int(args.qubits),verbose=args.verbose,eve_present=args.eve)) print("###############################".format(str(i))) print("############################") print("############################") t = "{0:.2f}".format(float(ret.count(True))*100.0/float(N)) u = "{0:.2f}".format(float(ret.count(False))*100.0/float(N)) print("True: {0} <{1}%>".format(ret.count(True),str(t))) print("False: {0} <{1}%>".format(ret.count(False),str(u)))

Read the original article by Bennett and Brassart entitled “Quantum cryptography: public key distribution and coin tossing”.