
BB84 in Qiskit: How Random Bases Help Two Users Share a Key
BB84 encodes random bits in two incompatible bases. Learn why matching-basis rounds form a raw key, how intercept-resend can add errors, and simulate sifting in Qiskit.
BB84 is a way for two people to establish a shared secret key by sending quantum states, then checking for disturbance. It does not send a finished key through the quantum channel. Alice prepares qubits from random bits and random bases. Bob measures them in bases he chooses independently. Later, they compare only their basis choices over a public classical channel and keep the rounds where they matched.
Two bases, four states
The protocol uses two measurement bases. In the Z basis, the possible states are |0⟩ and |1⟩. In the X basis, they are |+⟩ = (|0⟩ + |1⟩) / √2 and |−⟩ = (|0⟩ − |1⟩) / √2. A Hadamard gate converts between the two bases.
Within one basis, the two states are orthogonal, so a measurement in that basis can distinguish them perfectly. Across the two bases, the states are not orthogonal. Measuring a qubit in the wrong basis gives a random result. That simple fact is what lets the protocol detect disturbance.
For each signal, Alice picks a random bit and a random basis, then prepares the corresponding state. Bob independently picks a basis and measures. Once the quantum transmissions are over, Alice and Bob announce their basis choices publicly, but keep the bit values private. They discard rounds where the bases differ. The remaining bits are called the sifted key.
How an intercept-resend attack changes the statistics
Imagine Eve intercepts every qubit, measures it in a randomly selected basis, and sends a new qubit in the state she measured. If Eve happens to use Alice's basis, she learns the bit without changing that signal. If she chooses the other basis, her result is random and the state she sends may not match Alice's preparation.
Among the rounds Alice and Bob keep, Eve's basis is wrong half the time. In those rounds, Bob has a one-half chance of getting a bit that differs from Alice's. The expected error rate in the sifted key is therefore 1/2 × 1/2 = 1/4, or 25 percent, for this idealized attack on every signal. This is a prediction for one simple attack, not a universal security threshold. Ordinary device and channel noise can also cause errors, and a measured error rate is statistical evidence rather than proof of who caused it.
In real BB84, Alice and Bob reveal a small random sample of sifted bits to estimate the quantum bit error rate, then discard that sample. If the observed errors are acceptable under their security analysis, they still need classical error correction and privacy amplification to produce a final secret key. The raw sifted key is not the finished key.
Simulate basis sifting in Qiskit
The short example below models Alice's preparation and Bob's measurement for 32 ideal signals. A basis value of 0 means Z and 1 means X. It deliberately has no Eve and no noise, so matching-basis rounds should agree exactly. The Python random module is fine for this demonstration, but it is not suitable for generating a real cryptographic key.
import random
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
random.seed(7)
n = 32
alice_bits = [random.randrange(2) for _ in range(n)]
alice_bases = [random.randrange(2) for _ in range(n)] # 0 = Z, 1 = X
bob_bases = [random.randrange(2) for _ in range(n)]
circuits = []
for bit, alice_basis, bob_basis in zip(alice_bits, alice_bases, bob_bases):
qc = QuantumCircuit(1, 1)
if bit:
qc.x(0) # prepare |1> in the Z basis
if alice_basis == 1:
qc.h(0) # encode in the X basis
if bob_basis == 1:
qc.h(0) # measure in the X basis
qc.measure(0, 0)
circuits.append(qc)
backend = AerSimulator()
compiled = transpile(circuits, backend)
result = backend.run(compiled, shots=1).result()
bob_bits = []
for i in range(n):
counts = result.get_counts(i)
bob_bits.append(1 if counts.get("1", 0) else 0)
kept = [i for i in range(n) if alice_bases[i] == bob_bases[i]]
alice_raw_key = [alice_bits[i] for i in kept]
bob_raw_key = [bob_bits[i] for i in kept]
print("matching-basis rounds:", len(kept))
print("raw keys match in ideal simulation:", alice_raw_key == bob_raw_key)
Each circuit applies Alice's encoding first. If Bob chose X, the final Hadamard changes the measurement basis before the computational-basis measurement. Qiskit Aer runs the list of circuits and returns a separate counts result for each one. See the AerSimulator guide for the simulation pattern.
This is a teaching model, not a secure QKD system. Practical security also depends on authenticated classical communication, physical devices, randomness quality, and careful post-processing. The useful lesson from this small program is narrower: basis choices let Alice and Bob sift compatible measurements, while an intervention in the wrong basis can leave a detectable statistical footprint.