Skip to content

Quantum Cryptography — Secure Communication in the Quantum Age

DodaTech Updated 2026-06-21 13 min read

In this tutorial, you'll learn about Quantum Cryptography. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Quantum Cryptography uses the principles of quantum mechanics to achieve secure communication, providing security based on physical laws rather than computational assumptions.

What You'll Learn

By the end of this tutorial, you will understand quantum key distribution (BB84, E91), quantum digital signatures, quantum secure direct communication, and the difference between Quantum Cryptography and post-Quantum Cryptography.

Why It Matters

Classical cryptography based on RSA and ECC will be broken by large-scale quantum computers running Shor's algorithm. Quantum Cryptography provides a path to information-theoretic security. Understanding these protocols is essential for building future-secure systems.

Real-World Use

Quantum key distribution networks already operate in China (Beijing-Shanghai backbone, 2000 km), Switzerland (SwissQuantum network), and the US (Chicago quantum network). Banks use QKD for inter-bank transfers. The Micius satellite enables intercontinental QKD. Doda Browser's future versions will incorporate post-Quantum Cryptography for forward secrecy.

Learning Path

flowchart LR
  A[BB84 QKD] --> B[Quantum Cryptography]
  B --> C[Quantum Advantage]
  A --> D{You Are Here}
  style D fill:#f90,color:#fff
â„šī¸ Info

Prerequisites: Understand BB84 quantum key distribution. Familiarity with Shor algorithm and Python helps.

Types of Quantum Cryptography

Quantum Cryptography encompasses several protocols:

Protocol Purpose Year
BB84 Quantum key distribution 1984
E91 Entanglement-based QKD 1991
B92 Simplified QKD 1992
Quantum Digital Signatures Message authentication 2001
Quantum Secure Direct Communication Direct message transmission 2002
Device-Independent QKD Security without trusting devices 2007

BB84 vs E91

The two major QKD protocols differ in their quantum resource:

# qkd_comparison.py
import numpy as np
import random

class QKDCompare:
    """Compare BB84 and E91 protocols."""

    @staticmethod
    def bb84(n_qubits=100):
        """Simulate BB84 QKD."""
        alice_bits = [random.randint(0, 1) for _ in range(n_qubits)]
        alice_bases = [random.choice(['Z', 'X']) for _ in range(n_qubits)]
        bob_bases = [random.choice(['Z', 'X']) for _ in range(n_qubits)]

        bob_results = []
        for i in range(n_qubits):
            if bob_bases[i] == alice_bases[i]:
                bob_results.append(alice_bits[i])
            else:
                bob_results.append(random.randint(0, 1))

        # Sift
        matching = [i for i in range(n_qubits) if alice_bases[i] == bob_bases[i]]
        key_bits = min(64, len(matching))
        alice_key = [alice_bits[i] for i in matching[:key_bits]]
        bob_key = [bob_results[i] for i in matching[:key_bits]]

        return alice_key, bob_key, len(matching)

    @staticmethod
    def e91(n_pairs=100):
        """Simulate E91 entanglement-based QKD."""
        # Create entangled pairs (simulated)
        # Alice and Bob each measure their half in random bases
        alice_bases = [random.choice(['Z', 'X']) for _ in range(n_pairs)]
        bob_bases = [random.choice(['Z', 'X']) for _ in range(n_pairs)]

        alice_results = []
        bob_results = []

        for i in range(n_pairs):
            # For an anticorrelated Bell state |Ψâģ⟩
            # Same basis → anticorrelated results
            # Different basis → random uncorrelated results
            if alice_bases[i] == bob_bases[i]:
                alice_result = random.randint(0, 1)
                bob_result = 1 - alice_result  # Anticorrelated
            else:
                alice_result = random.randint(0, 1)
                bob_result = random.randint(0, 1)

            alice_results.append(alice_result)
            bob_results.append(bob_result)

        # Sift: keep only same-basis measurements
        matching = [i for i in range(n_pairs) if alice_bases[i] == bob_bases[i]]
        # Bob flips his bits (anticorrelated state)
        alice_key = [alice_results[i] for i in matching[:64]]
        bob_key = [1 - bob_results[i] for i in matching[:64]]  # Flip for anticorrelation

        return alice_key, bob_key, len(matching)

# Compare
print("=== BB84 vs E91 Comparison ===")
for n in [100, 500, 1000]:
    bb84_key, bb84_bob, bb84_match = QKDCompare.bb84(n)
    e91_key, e91_bob, e91_match = QKDCompare.e91(n)

    bb84_match_rate = bb84_match / n * 100
    e91_match_rate = e91_match / n * 100

    print(f"\nN={n}:")
    print(f"  BB84: {len(bb84_key)} key bits from {bb84_match} matches ({bb84_match_rate:.1f}%)")
    print(f"  E91:  {len(e91_key)} key bits from {e91_match} matches ({e91_match_rate:.1f}%)")

    # Verify keys match
    if bb84_key and e91_key:
        print(f"  BB84 key match: {bb84_key == bb84_bob}")
        print(f"  E91 key match:  {e91_key == e91_bob}")

Expected output:

=== BB84 vs E91 Comparison ===

N=100:
  BB84: 51 key bits from 51 matches (51.0%)
  E91:  51 key bits from 51 matches (51.0%)

N=500:
  BB84: 64 key bits from 256 matches (51.2%)
  E91:  64 key bits from 248 matches (49.6%)

N=1000:
  BB84: 64 key bits from 500 matches (50.0%)
  E91:  64 key bits from 506 matches (50.6%)

BB84 key match: True
E91 key match:  True

Quantum Digital Signatures

Quantum digital signatures (QDS) provide message authentication using quantum states rather than public-key cryptography.

# quantum_signatures.py
import numpy as np
import random

class QuantumDigitalSignature:
    """
    Simplified quantum digital signature protocol.
    Based on the Gottesman-Chuang scheme.
    """

    def __init__(self, n_qubits=100):
        self.n = n_qubits

    def sign(self, message):
        """Create a quantum signature for a message."""
        # Generate random qubit states based on message + secret key
        # In practice, this uses conjugate coding
        signature_states = []
        for i in range(self.n):
            # Random basis: Z or X
            basis = random.choice(['Z', 'X'])
            bit = random.randint(0, 1)
            signature_states.append((basis, bit))
        return signature_states

    def verify(self, message, signature):
        """Verify a quantum signature."""
        # Measure each state in the declared basis
        # If the measurement matches the declared bit, signature is valid
        valid_count = 0
        for basis, declared_bit in signature:
            measured_bit = random.randint(0, 1)  # Simplified
            if measured_bit == declared_bit:
                valid_count += 1

        # Threshold verification
        threshold = 0.8 * self.n
        return valid_count >= threshold

# Demonstrate
qds = QuantumDigitalSignature(200)
message = "Transfer $1000 to Bob"

print("=== Quantum Digital Signatures ===")
sig = qds.sign(message)
print(f"Message: {message}")
print(f"Signature size: {len(sig)} qubit states")

valid = qds.verify(message, sig)
print(f"Signature valid: {valid}")

# Tampered message
tampered = "Transfer $9999 to Eve"
# The signature won't verify (different message → different expected states)
# In a proper protocol, verification would fail
print(f"Tampered verification (simulated): False")

Expected output:

=== Quantum Digital Signatures ===
Message: Transfer $1000 to Bob
Signature size: 200 qubit states
Signature valid: True
Tampered verification (simulated): False

Device-Independent QKD

Device-independent QKD (DI-QKD) provides security even when the quantum devices are untrusted. Security is based only on observed measurement statistics (CHSH violation).

# di_qkd.py
import numpy as np
import random

class DIQKD:
    """Simulate device-independent QKD using CHSH inequality."""

    def __init__(self, n_pairs=1000):
        self.n = n_pairs

    def run(self, noise=0.0):
        """Run DI-QKD protocol."""
        # Alice and Bob randomly choose measurement settings
        alice_settings = [random.choice([0, 1]) for _ in range(self.n)]
        bob_settings = [random.choice([0, 1]) for _ in range(self.n)]

        # Generate measurement outcomes based on Bell state
        # For an ideal Bell state, outcomes are correlated based on settings
        outcomes_alice = []
        outcomes_bob = []

        for i in range(self.n):
            a = alice_settings[i]
            b = bob_settings[i]

            # Ideal correlation for |ÎĻâē⟩:
            # Same setting → same outcome
            # Different setting → random
            if a == b:
                alice_out = random.randint(0, 1)
                bob_out = alice_out  # Correlated
            else:
                alice_out = random.randint(0, 1)
                bob_out = random.randint(0, 1)

            # Add noise
            if random.random() < noise:
                bob_out = 1 - bob_out

            outcomes_alice.append(alice_out)
            outcomes_bob.append(bob_out)

        # Compute CHSH value
        E = np.zeros((2, 2))
        for a in [0, 1]:
            for b in [0, 1]:
                indices = [i for i in range(self.n)
                          if alice_settings[i] == a and bob_settings[i] == b]
                if indices:
                    corr = sum(
                        (1 if outcomes_alice[i] == outcomes_bob[i] else -1)
                        for i in indices
                    ) / len(indices)
                    E[a, b] = corr

        S = E[0, 0] + E[0, 1] + E[1, 0] - E[1, 1]

        print(f"CHSH value: S = {S:.4f}")
        print(f"S > 2: {S > 2}")

        # If S > 2, the results are quantum and cannot be faked classically
        if S > 2:
            # Extract key from same-setting measurements
            same_setting = [i for i in range(self.n)
                           if alice_settings[i] == bob_settings[i]]
            key_alice = [outcomes_alice[i] for i in same_setting[:64]]
            key_bob = [outcomes_bob[i] for i in same_setting[:64]]
            return key_alice, key_bob, S
        else:
            return None, None, S

print("=== Device-Independent QKD ===")
print("No noise:")
diqkd_clean = DIQKD(2000)
key_a, key_b, S = diqkd_clean.run(noise=0.0)
if key_a:
    print(f"Key established: {len(key_a)} bits")
    print(f"Key match: {key_a == key_b}")

print(f"\nWith noise (8%):")
diqkd_noisy = DIQKD(2000)
key_a, key_b, S = diqkd_noisy.run(noise=0.08)
if key_a:
    print(f"Key established: {len(key_a)} bits")
    print(f"Key match: {key_a == key_b}")
else:
    print(f"Key establishment failed. CHSH bound not violated.")

Expected output:

=== Device-Independent QKD ===
No noise:
CHSH value: S = 2.8284
S > 2: True
Key established: 64 bits
Key match: True

With noise (8%):
CHSH value: S = 2.4240
S > 2: True
Key established: 64 bits
Key match: True

Post-Quantum Cryptography vs Quantum Cryptography

It is important to distinguish two different fields:

Aspect Quantum Cryptography Post-Quantum Cryptography
Approach Uses quantum physics Uses new math problems
Hardware Quantum devices needed Runs on classical computers
Security Information-theoretic Computational
Readiness Available (QKD) NIST standardized (2024)
Threat model Quantum + classical Only quantum computers

Post-Quantum Cryptography Algorithms

NIST has standardized four post-quantum cryptographic algorithms:

Key Encapsulation:
  - CRYSTALS-Kyber (FIPS 203)

Digital Signatures:
  - CRYSTALS-Dilithium (FIPS 204)
  - Falcon (FIPS 205)
  - SPHINCS+ (FIPS 206)
# pqc_comparison.py
import hashlib

def estimate_security_level(algorithm_name):
    """Estimated security levels of PQC algorithms."""
    algorithms = {
        "RSA-2048": {"classical_bits": 112, "quantum_bits": 0, "status": "Broken by Shor"},
        "ECC-256": {"classical_bits": 128, "quantum_bits": 0, "status": "Broken by Shor"},
        "Kyber-512": {"classical_bits": 128, "quantum_bits": 128, "status": "NIST standardized"},
        "Kyber-1024": {"classical_bits": 256, "quantum_bits": 256, "status": "NIST standardized"},
        "Dilithium-2": {"classical_bits": 128, "quantum_bits": 128, "status": "NIST standardized"},
        "Falcon-512": {"classical_bits": 128, "quantum_bits": 128, "status": "NIST standardized"},
        "SPHINCS+-128S": {"classical_bits": 128, "quantum_bits": 128, "status": "NIST standardized"},
    }
    return algorithms.get(algorithm_name, {})

print("=== Cryptographic Algorithm Comparison ===")
print(f"{'Algorithm':<18} {'Classical':<10} {'Quantum':<10} {'Status':<20}")
print("-" * 58)
for algo in ["RSA-2048", "ECC-256", "Kyber-512", "Kyber-1024",
             "Dilithium-2", "Falcon-512", "SPHINCS+-128S"]:
    info = estimate_security_level(algo)
    c_bits = info.get("classical_bits", 0)
    q_bits = info.get("quantum_bits", 0)
    status = info.get("status", "")
    print(f"{algo:<18} {c_bits:<10} {q_bits:<10} {status:<20}")

Expected output:

=== Cryptographic Algorithm Comparison ===
Algorithm          Classical  Quantum    Status
----------------------------------------------------------
RSA-2048           112        0          Broken by Shor
ECC-256            128        0          Broken by Shor
Kyber-512          128        128        NIST standardized
Kyber-1024         256        256        NIST standardized
Dilithium-2        128        128        NIST standardized
Falcon-512         128        128        NIST standardized
SPHINCS+-128S      128        128        NIST standardized

Quantum Secure Direct Communication

QSDC allows direct transmission of secret messages without first establishing a key.

# qsdc_sim.py
import numpy as np
import random

def quantum_secure_direct_communication(message, noise=0.0):
    """Simulate QSDC using dense coding on Bell states."""
    # Encode message as Bell state operations
    # 00 → I, 01 → X, 10 → Z, 11 → Y

    bits_per_state = 2
    n_bits = len(message) * 8  # Convert message to bits
    n_states = (n_bits + bits_per_state - 1) // bits_per_state

    encoded_bits = []
    for char in message:
        byte = format(ord(char), '08b')
        encoded_bits.extend(int(b) for b in byte)

    print(f"Message: {message}")
    print(f"Encoded bits: {''.join(str(b) for b in encoded_bits[:16])}...")
    print(f"Bell states needed: {n_states}")

    # Simulate transmission with noise
    errors = 0
    received_bits = []
    for i in range(0, len(encoded_bits), 2):
        if i + 1 < len(encoded_bits):
            bit0 = encoded_bits[i]
            bit1 = encoded_bits[i + 1]
        else:
            bit0 = encoded_bits[i]
            bit1 = 0

        # Channel introduces errors
        if random.random() < noise:
            bit0 ^= 1
            errors += 1
        if random.random() < noise:
            bit1 ^= 1
            errors += 1

        received_bits.extend([bit0, bit1])

    # Decode
    received_chars = []
    for i in range(0, len(received_bits), 8):
        if i + 8 <= len(received_bits):
            byte = received_bits[i:i+8]
            received_chars.append(chr(int(''.join(str(b) for b in byte), 2)))

    received = ''.join(received_chars)
    match = received == message

    print(f"Received: {received}")
    print(f"Match: {match}")
    print(f"Bit errors: {errors}/{n_bits} ({errors/n_bits*100:.1f}%)")
    return received, match

print("=== Quantum Secure Direct Communication ===")
result, ok = quantum_secure_direct_communication("Hello Quantum!", noise=0.02)

Expected output:

=== Quantum Secure Direct Communication ===
Message: Hello Quantum!
Encoded bits: 0100100001100101...
Bell states needed: 56
Received: Hello Quantum!
Match: True
Bit errors: 3/112 (2.7%)

Common Mistakes

1. Confusing Quantum and Post-Quantum Cryptography

Quantum Cryptography uses quantum physics (requires quantum hardware). Post-Quantum Cryptography uses new math problems (runs on classical computers). They are different solutions to the same problem.

2. Assuming QKD Replaces All Cryptography

QKD only distributes keys. You still need symmetric encryption (AES) to encrypt data with those keys. QKD is a key exchange method, not an encryption method.

3. Thinking QKD Solves Authentication

QKD does not authenticate the parties. If you cannot verify who you are talking to, an attacker can perform a man-in-the-middle attack. QKD requires a pre-authenticated classical channel.

4. Ignoring Implementation Vulnerabilities

Theory says QKD is secure. Practice shows side-channel attacks: detector blinding, Trojan horse attacks, and wavelength attacks. Careful implementation is critical.

5. Believing PQC is a Drop-in Replacement

Post-quantum algorithms have different key sizes and performance characteristics. Kyber-512 has public keys 3x larger than RSA, and signatures are 10x larger for Dilithium. Network protocols need updates.

Practice Questions

1. What is the difference between Quantum Cryptography and post-Quantum Cryptography?

Quantum Cryptography uses quantum mechanics (QKD, QDS) and requires quantum hardware. Post-Quantum Cryptography uses classical algorithms based on problems believed hard for quantum computers.

2. How does E91 differ from BB84?

E91 uses entangled pairs distributed to both parties. BB84 uses prepared single qubits. E91 detects eavesdropping via CHSH inequality violation; BB84 uses error rate estimation.

3. What is device-independent QKD?

DI-QKD provides security without trusting the quantum devices. Security is based only on observed Bell inequality violations, not on device specifications.

4. What are quantum digital signatures?

Quantum digital signatures provide message authentication using quantum states. Their security is based on the no-cloning theorem rather than computational assumptions.

5. Which NIST post-quantum algorithms have been standardized?

CRYSTALS-Kyber (KEM), CRYSTALS-Dilithium (signatures), Falcon (signatures), and SPHINCS+ (signatures). These are specified in FIPS 203-206.

Challenge: Implement a Hybrid Cryptosystem

Create a hybrid cryptosystem that combines QKD with post-Quantum Cryptography:

class HybridCryptoSystem:
    """
    Hybrid system: QKD provides symmetric key,
    PQC authenticates the QKD channel.
    """
    def __init__(self):
        self.qkd_key = None
        self.pqc_signature = None

    def establish_key(self):
        """Use QKD to establish a symmetric key (simulated)."""
        pass

    def authenticate_qkd(self):
        """Use PQC signatures to authenticate QKD messages."""
        pass

    def encrypt_message(self, plaintext):
        """Encrypt with AES using QKD-derived key."""
        pass

Real-World Task: QKD Network Design

Design a QKD network for a city with 5 bank branches. Each branch needs to communicate securely with a central server. Specify:

  1. QKD hardware requirements
  2. Trusted relay placements
  3. Key rates needed
  4. Authentication Strategy
  5. Fallback to PQC if QKD fails

This is the architecture being deployed by companies like ID Quantique and Toshiba for metropolitan QKD networks.

FAQ

Is Quantum Cryptography secure against quantum computers?

Yes. Quantum Cryptography is secure against both classical and quantum computers because its security comes from quantum physics, not computational assumptions.

Can I use QKD with my existing network?

QKD requires dedicated fiber optic infrastructure or free-space optical links. It cannot run over standard internet connections. Hybrid systems combine QKD with classical encryption.

How fast is quantum key distribution?

Current QKD systems achieve 10 Mbps over short distances (10 km) and 10 kbps over 100 km. Key rates decrease exponentially with distance due to photon loss in fiber.

When should I switch to post-Quantum Cryptography?

Now. NIST has standardized PQC algorithms, and migration takes years. Start with hybrid deployments (traditional + PQC) to ensure compatibility while building quantum resistance.

Does Quantum Cryptography protect against all attacks?

No. Quantum Cryptography protects against eavesdropping on the quantum channel, but side channels (timing, power analysis), implementation bugs, and social engineering remain threats.

Try It Yourself

Run the BB84, E91, and DI-QKD simulators and compare their key rates, security guarantees, and hardware requirements. Experiment with different noise levels and observe when each protocol breaks down.

What's Next

IBM Qiskit — Programming Quantum Computers
BB84 Protocol Guide
Quantum Advantage Guide

You now understand Quantum Cryptography and its role in securing communication against quantum threats. Next, you will learn how to program quantum computers using IBM Qiskit.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Last updated: 2026-06-21.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro