What cryptography gives you

Cryptography is the practice of protecting information with mathematical techniques so that only intended parties can read it, and so that changes and forgeries can be detected. It provides four services, which map onto the goals in What Is Information Security?:

Service Question it answers Tool
Confidentiality Can only the intended reader see it? Encryption
Integrity Has it been changed? Hash functions, HMAC, digital signatures
Authentication Did it really come from who it claims? HMAC, digital signatures, certificates
Non-repudiation Can the sender later deny having sent it? Digital signatures only

The vocabulary: plaintext is the readable data, ciphertext the encrypted result. An algorithm (or cipher) describes how to encrypt and decrypt, and a key is the secret value that makes the result unique. The algorithms are public and studied by researchers worldwide; all secrecy lies in the key. This idea, known as Kerckhoffs's principle, is why a secret, home-made algorithm is not more secure but less: nobody has had the chance to find its flaws except, eventually, an attacker.

Symmetric and asymmetric encryption

Symmetric encryption

Symmetric encryption uses one and the same secret key to encrypt and to decrypt. It is fast enough to encrypt disks, network traffic and backups at full speed. The standard algorithm is the Advanced Encryption Standard (AES), with 128- or 256-bit keys, usually in Galois/Counter Mode (GCM), which also detects tampering; ChaCha20-Poly1305 is a common alternative on devices without AES hardware support. Older algorithms such as DES (Data Encryption Standard) and 3DES are obsolete.

Its weakness is the key distribution problem: both sides need the same key, and sending it over the same network as the data would let an eavesdropper take it. Every pair of people also needs its own key, so the number of keys grows quickly:

def symmetric_keys(people):
    return people * (people - 1) // 2   # one secret key per pair of people


def asymmetric_keys(people):
    return people * 2                   # one key pair (public + private) per person


print(f"{'people':>8}{'symmetric keys':>17}{'asymmetric keys':>18}")
for people in (2, 10, 100, 1000):
    print(f"{people:>8}{symmetric_keys(people):>17,}{asymmetric_keys(people):>18,}")
  people   symmetric keys   asymmetric keys
       2                1                 4
      10               45                20
     100            4,950               200
    1000          499,500             2,000

Asymmetric encryption

Asymmetric encryption (public-key cryptography) uses a mathematically linked key pair. The public key may be shared with anyone: it is used to encrypt data for the owner and to verify the owner's signatures. The private key is kept secret by its owner alone: it decrypts what was encrypted with the public key and creates signatures. Knowing the public key does not let anyone work out the private key.

This solves key distribution: to send Ada a secret, you only need her public key, which she can publish. The best-known algorithms are RSA (named after Rivest, Shamir and Adleman; keys of 2048 bits or more) and elliptic curve cryptography (ECC), which reaches similar strength with much shorter keys — a 256-bit elliptic curve key is roughly as strong as a 3072-bit RSA key. Diffie–Hellman key exchange (today mostly its ephemeral elliptic curve variant, Elliptic Curve Diffie–Hellman Ephemeral, ECDHE) lets two parties agree on a shared secret over an open network without ever sending it. The price: asymmetric operations are hundreds to thousands of times slower than symmetric ones, so they are used on small amounts of data such as keys and signatures.

Symmetric versus asymmetric encryption. Left: the sender encrypts plaintext with a shared secret key, and the receiver decrypts the ciphertext with the same key. Right: the sender encrypts with the receiver's public key, and only the receiver's private key can decrypt it. Symmetric Asymmetric Plaintext Ciphertext Plaintext same secret key same secret key fast · the key must be shared first Plaintext Ciphertext Plaintext receiver's public key receiver's private key slow · no shared secret needed
Symmetric: one key, which both sides must already have. Asymmetric: anyone can encrypt with the public key; only the holder of the private key can decrypt.

Hybrid encryption in practice

Because each kind has a weakness, real systems combine them. When your browser opens an HTTPS site, Transport Layer Security (TLS) roughly does this:

  1. Browser and server run an elliptic curve Diffie–Hellman exchange to agree on a fresh shared secret, which is never sent over the network.
  2. The server proves it is the real site by signing the handshake with the private key that belongs to its certificate; the browser verifies the signature with the public key in the certificate (see PKI and Certificates).
  3. From the shared secret, both derive symmetric session keys, and all data is encrypted with AES-GCM or ChaCha20-Poly1305.

Because the Diffie–Hellman values are ephemeral (fresh for each connection and thrown away afterward), and the server's private key is only used to sign, a later theft of that key does not expose recorded past sessions. This property is called forward secrecy, and TLS 1.3 always provides it. Encrypted email, virtual private networks and disk encryption follow the same hybrid pattern.

Hash functions

A hash function (cryptographic hash) turns input of any size into a fixed-length output, the hash or digest, in a way that cannot be reversed. A good one has these properties:

  • One-way: from the hash, you cannot work back to the input.
  • Fixed length: SHA-256 (from the Secure Hash Algorithm 2 family) always gives 256 bits, for an empty file or a disk image.
  • Deterministic: the same input always gives the same hash.
  • Collision-resistant: it is infeasible to find two different inputs with the same hash.
  • Avalanche effect: changing one character changes about half of the output bits, unpredictably.
import hashlib

messages = [b"Pay 100 euros to Alice", b"Pay 900 euros to Alice"]
digests = [hashlib.sha256(m).digest() for m in messages]

for message, digest in zip(messages, digests):
    print(message.decode(), "->", digest.hex()[:32] + "...")

# Count the bits that differ between the two 256-bit digests.
a, b = (int.from_bytes(d, "big") for d in digests)
changed = bin(a ^ b).count("1")
print(f"{changed} of 256 bits changed ({changed / 256:.0%})")

# Same length, whatever the input size.
print(len(hashlib.sha256(b"").digest()), len(hashlib.sha256(b"x" * 1_000_000).digest()))
Pay 100 euros to Alice -> 0c8cf5481f079d3f9797347c041279b8...
Pay 900 euros to Alice -> 15294248e8a85afb471ea5db47c79222...
127 of 256 bits changed (50%)
32 32

One digit changed, and half of the 256 bits flipped — the two hashes look completely unrelated. Hashes are used to verify downloads (compare the published hash with the hash of the file you received), to detect changed files, to identify known malware samples, inside digital signatures and, with salts and slow functions, to store passwords (see how systems store passwords).

MD5 and SHA-1 are broken for security purposes: researchers can create collisions, two different files with the same hash (for SHA-1 publicly demonstrated in 2017). They are still found as checksums against accidental corruption, but must not be used for signatures or certificates. Use SHA-256 or stronger (SHA-384, SHA-512, or the SHA-3 family) there. Passwords need a deliberately slow, salted function such as Argon2id, scrypt, bcrypt or PBKDF2, never a plain hash (see how systems store passwords).

HMAC: integrity with a shared key

A plain hash proves nothing about who made it: an attacker who changes a message simply computes a new hash. An HMAC (hash-based message authentication code) mixes a secret key into the hash calculation. Only someone with the key can produce or check the tag, so a valid tag proves the message was not changed and came from a key holder. HMACs protect application programming interface (API) requests, session tokens, webhooks and the one-time codes of authenticator apps. The key in this example is a fixed demo value:

import hashlib
import hmac

KEY = b"demo-key-shared-by-sender-and-receiver"  # real keys: random, from a key store


def tag(key, message):
    return hmac.new(key, message, hashlib.sha256).hexdigest()


original = b"Transfer 100 EUR to account 12345"
modified = b"Transfer 900 EUR to account 12345"
sent_tag = tag(KEY, original)  # the sender sends the message plus this tag
print("tag:", sent_tag[:32] + "...")

# The receiver recomputes the tag with the shared key and compares in constant time.
for received in (original, modified):
    ok = hmac.compare_digest(tag(KEY, received), sent_tag)
    print(received.decode(), "->", "authentic" if ok else "REJECTED")

# An attacker who changes the message cannot make a matching tag without the key.
forged_tag = tag(b"attacker-guess", modified)
print("forged tag accepted:", hmac.compare_digest(tag(KEY, modified), forged_tag))
tag: d5c2b57420452919df7be2cdd78d0edd...
Transfer 100 EUR to account 12345 -> authentic
Transfer 900 EUR to account 12345 -> REJECTED
forged tag accepted: False

Digital signatures

A digital signature is the asymmetric counterpart of an HMAC. The signer hashes the message and transforms the hash with their private key; anyone can check the signature with the signer's public key. If the message changed by one bit, or the signature was made with another key, verification fails.

Digital signature. The signer hashes the document and signs the hash with their private key. The verifier hashes the received document and checks the signature with the signer's public key; if they match, the document is unchanged and was signed by the holder of the private key. Signer Verifier Document Hash Signature private key sent together Hash the document; check the signature with the public key valid or invalid
Only the private key can create the signature; anyone with the public key can verify it.

Signatures give integrity, authentication and — unlike an HMAC — non-repudiation. With an HMAC, sender and receiver hold the same key, so either of them could have made the tag, and a judge cannot tell who did. With a signature, only the holder of the private key could have made it. Signatures protect software updates (code signing), documents, emails, the TLS handshake and the certificates themselves. Common algorithms are RSA, ECDSA (Elliptic Curve Digital Signature Algorithm) and Ed25519.

Key management: the hard part

Modern algorithms, used correctly, are not what attackers break. They steal, guess or find the keys. Key management covers the whole life of a key:

  • Generation with a cryptographically secure random source (in Python the secrets module, never random).
  • Storage where it cannot be copied: a key management service (KMS), a hardware security module (HSM), a Trusted Platform Module (TPM) in a laptop — never in source code or next to the data it protects. See key management services.
  • Access control and logging: who may use the key, and a record of every use.
  • Rotation: replacing keys on a schedule and immediately after a suspected compromise.
  • Backup and recovery: lose the key, lose the data — for encrypted backups and disk encryption recovery keys, this is the most common disaster.
  • Revocation and destruction: retiring keys that are no longer needed or no longer trusted (for certificates, see PKI and Certificates).

Where data is encrypted also matters: encryption at rest protects stored data, encryption in transit protects data on the network. Both are explained, with their limits, in Cloud Security and Compliance; for laptops, see full-disk encryption on the Endpoint Security page.

Post-quantum cryptography

A sufficiently large quantum computer could break RSA, elliptic curve cryptography and Diffie–Hellman, using Shor's algorithm. No such machine exists at the time of writing, and estimates of when one might differ widely. Symmetric algorithms and hashes are much less affected: AES-256 and SHA-256 are expected to remain secure.

The urgency comes from "harvest now, decrypt later": an adversary can record encrypted traffic today and decrypt it once a quantum computer is available. Data that must stay secret for many years — state secrets, health records, critical infrastructure designs — is at risk already. In 2024 NIST (the United States National Institute of Standards and Technology) published its first post-quantum standards: ML-KEM (FIPS 203) for key establishment and ML-DSA (FIPS 204) and SLH-DSA (FIPS 205) for signatures. At the time of writing, major browsers and some large services already use a hybrid key exchange that combines a classical elliptic curve with ML-KEM, so the connection stays secure if either one holds. For organizations, the practical first step is an inventory: which systems use which algorithms, so they can be migrated when products support it.