TLS Cipher Suites
AES-GCM, ChaCha20, ECDHE, ECDSA, HKDF β and why TLS 1.3 kept only 5
A cipher suite is a named bundle that specifies every cryptographic primitive
used in a TLS connection: the key exchange algorithm (how client and server
derive a shared secret), the authentication method (how the certificate is
verified), the bulk cipher (how data is encrypted), and the PRF/HKDF (how
keys are derived from the shared secret). The name TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
encodes all four.
TLS 1.2 had over 300 registered cipher suites, many weak or redundant. TLS 1.3 gutted the list to just five mandatory AEAD-only suites, separating key exchange from authentication and removing static RSA key exchange entirely. This made cipher suite negotiation simpler, removed entire vulnerability classes, and made it possible to prove security properties formally.
Cipher Suite Parser
Paste a cipher suite name (TLS 1.2 or TLS 1.3) to see it broken down into its component algorithms.
Modern vs. Legacy Suites
TLS 1.3 cipher suites are AEAD-only with mandatory forward secrecy. Legacy suites include CBC modes, static RSA, and often lack forward secrecy. Hover a row to highlight it.
| Cipher Suite | Version | Key Exchange | Auth | Cipher | Mode | Forward Secrecy | Status |
|---|---|---|---|---|---|---|---|
TLS_AES_128_GCM_SHA256 | TLS 1.3 | ECDHE | ECDSA/RSA | AES-128 | GCM | ✓ Yes | Recommended |
TLS_AES_256_GCM_SHA384 | TLS 1.3 | ECDHE | ECDSA/RSA | AES-256 | GCM | ✓ Yes | Recommended |
TLS_CHACHA20_POLY1305_SHA256 | TLS 1.3 | ECDHE | ECDSA/RSA | ChaCha20 | Poly1305 | ✓ Yes | Recommended |
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 | TLS 1.2 | ECDHE | RSA | AES-128 | GCM | ✓ Yes | Acceptable |
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 | TLS 1.2 | ECDHE | ECDSA | AES-128 | GCM | ✓ Yes | Acceptable |
TLS_DHE_RSA_WITH_AES_128_CBC_SHA | TLS 1.2 | DHE | RSA | AES-128 | CBC | ✓ Yes | Legacy |
TLS_RSA_WITH_AES_128_CBC_SHA | TLS 1.2 | RSA | RSA | AES-128 | CBC | ✗ None | Insecure |
TLS_RSA_WITH_3DES_EDE_CBC_SHA | TLS 1.2 | RSA | RSA | 3DES | CBC | ✗ None | Insecure |
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA | TLS 1.2 | ECDHE | RSA | AES-128 | CBC | ✓ Yes | Legacy |
TLS_AES_128_CCM_SHA256 | TLS 1.3 | ECDHE | ECDSA/RSA | AES-128 | CCM | ✓ Yes | Acceptable |
Cipher Suite Anatomy
A TLS 1.2 cipher suite name encodes four operations. TLS 1.3 separates them into independent extensions, but the old naming convention is still worth understanding for legacy debugging.
Key Exchange: DH, ECDH, DHE, ECDHE
Key exchange establishes the shared secret between client and server without an eavesdropper learning it. DH and ECDH are static (no forward secrecy); DHE and ECDHE are ephemeral (forward secrecy guaranteed).
{`# Diffie-Hellman (static) β RFC 2631
# Both parties have a long-term DH key pair.
# Vulnerable: if an attacker records traffic and later steals one
# party's private key, they can compute the shared secret retroactively.
#
# ClientDH: g^a mod p ServerDH: g^b mod p
# Shared: (g^b)^a = g^ab Shared: (g^a)^b = g^ab
#
# NOT USED in TLS (removed in TLS 1.3).
# ECDH (static) β RFC 4492
# Same as DH but on an elliptic curve.
# Uses the formula: shared_secret = a * B = b * A
# where (A, a) are client's keypair, (B, b) are server's keypair.
# NOT forward-secret. Removed in TLS 1.3.
# DHE (ephemeral DH) β forward secrecy via fresh random per session
# Client: (g^a mod p, a is fresh random)
# Server: (g^b mod p, b is fresh random)
# Shared: g^ab mod p
# After the handshake a and b are discarded.
# Compromise of either long-term key DOES NOT recover past sessions.
# ECDHE (ephemeral ECDH) β preferred in TLS 1.2 and TLS 1.3
# Same as DHE but on an elliptic curve.
# - P-256, P-384, P-521 (NIST curves)
# - X25519 (Curve25519, DJB's curve, constant-time implementation)
# - secp256k1 (used in Bitcoin, not common in TLS)
#
# X25519 example:
client_private = random(32 bytes)
client_public = X25519 scalar multiplication(G, client_private)
# Send client_public in ClientHello.key_share
# Server does: shared = X25519(client_private, server_public)
# Both get the same result; eavesdropper cannot (needs private key).
# Python equivalent with cryptography library
python3 << 'EOF'
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives import serialization
priv = X25519PrivateKey.generate()
pub = priv.public_key()
print("Private size:", priv.private_bytes_raw().__len__(), "bytes")
print("Public size: ", pub.public_bytes(serialization.RawEncoding,
serialization.PublicFormat.Raw).__len__(), "bytes")
EOF
# Private size: 32 bytes
# Public size: 32 bytes
`} Auth Methods: RSA, ECDSA, Ed25519
The authentication method in a cipher suite determines the signature algorithm
used to verify the certificate chain. In TLS 1.3 it is negotiated independently
via the signature_algorithms extension, not the cipher suite.
{`# RSA signatures (PKCS#1 v1.5) β legacy, still common
# Certificate contains an RSA public key; server signs the transcript hash
# with the corresponding RSA private key.
# - Slow signing (RSA-2048: ~0.5ms; RSA-4096: ~2ms)
# - Large signature (256β512 bytes)
# - Vulnerable to Bleichenbacher-style oracle attacks on PKCS#1 v1.5
# - Still widely deployed (Google, Microsoft CAs issue RSA-2048 certs)
# ECDSA (P-256 / P-384) β current standard
# Certificate contains an ECDSA public key (32 or 48 bytes);
# server signs with the corresponding ECDSA private key.
# - Fast signing (P-256: ~0.05ms)
# - Smaller signature (64 or 96 bytes vs 256β512 for RSA)
# - Deterministic (with RFC 6979 nonce generation) β no random failures
# - Widely supported; most modern CAs issue ECDSA P-256 certs
# Ed25519 β high-performance modern alternative
# Pure Edwards curve; certificate contains Ed25519 public key (32 bytes).
# - Signing: ~0.02ms (fastest of the three)
# - Signature: 64 bytes, constant-time verification
# - No nonce management issues (deterministic by design)
# - Supported by Let's Encrypt, Smallstep, and most ACME CAs
# - Not supported by all TLS libraries (OpenSSL 1.1.1+ yes; older no)
# TLS 1.3 signature_algorithms extension (RFC 8446 Β§4.2.3)
# Client advertises which algorithms it accepts; server picks one.
signature_algorithms = [
ecdsa_secp256r1_sha256, # ECDSA P-256 with SHA-256
rsa_pss_rsae_sha256, # RSA-PSS with RSA-2048
ed25519, # Ed25519
rsa_pkcs1_sha256, # legacy RSA PKCS#1 v1.5 (discouraged)
]`} Ciphers: AES-CBC vs AES-GCM vs ChaCha20-Poly1305
The bulk cipher encrypts application data. CBC is a block cipher mode requiring padding (which caused POODLE); GCM and ChaCha20-Poly1305 are AEAD modes providing both confidentiality and integrity in one primitive.
| Mode | Standard | Key Size | Nonce | Integrity | Vulnerabilities | Notes |
|---|---|---|---|---|---|---|
| AES-CBC | SP 800-38A | 128/192/256 | β | HMAC-SHA256 (separate) | POODLE, BEAST, Lucky13 | TLS 1.2 legacy; removed in TLS 1.3 |
| AES-GCM | SP 800-38D | 128/256 | 12 bytes | GMAC (integrated) | Weak nonce generation | Standard TLS 1.3 cipher; hardware-accelerated |
| ChaCha20-Poly1305 | RFC 7539 | 256 | 12 bytes | Poly1305 (integrated) | None significant | Mobile preferred (constant-time, no AES-NI needed) |
| AES-CCM | SP 800-38C | 128 | 11 bytes | CMAC (integrated) | β | Mandatory in TLS 1.3; used with 802.15.4 / IoT |
{`# AES-GCM: authenticated encryption with associated data
# 1. Generate per-record nonce (12 bytes, unique per record)
# 2. Encrypt: AES-CTR mode with counter starting at nonce
# 3. Authenticate: GMAC over AAD (additional authenticated data)
# AAD = TLS record header (5 bytes: type, version, sequence number)
# 4. Output: nonce || ciphertext || auth_tag (16 bytes)
# Security: unique nonce is essential; reuse = catastrophic (GCM reuse)
# ChaCha20-Poly1305: stream cipher with MAC
# 1. ChaCha20 stream cipher encrypts (32-byte key, 12-byte nonce, counter)
# 2. Poly1305 authenticator over: key || ciphertext || length
# 3. Poly1305 is a Wegman-Carter authenticator β information-theoretic security
# Advantage on low-end ARM: no AES-NI required; constant-time execution
# Why GCM is preferred for servers:
# - AES-NI instructions make it ~3x faster on x86-64 server CPUs
# - NIST-approved; widely certified
# Why ChaCha20 is preferred for mobile:
# - Constant-time on all architectures (no timing side-channels)
# - Lower CPU cycle count on devices without AES-NI
# OpenSSL: check what ciphers are available
openssl ciphers -v 'TLS_AES' # TLS 1.3 ciphers
openssl ciphers -v 'ECDHE+AESGCM' # TLS 1.2 modern
openssl ciphers -v 'TLSv1.2:!EXP:!LOW' # All TLS 1.2 except export/legacy`} MAC / PRF: HMAC-SHA256 and TLS 1.3 HKDF
TLS needs a pseudo-random function to derive keys from the master secret (TLS 1.2) or the handshake transcript (TLS 1.3). The PRF has evolved across TLS versions.
{`# TLS 1.2 PRF β RFC 5246
# PRF(master_secret, label, seed) =
# P_hash(SHA-256, master_secret, label || seed) <>
# P_hash(SHA-384, master_secret, label || seed) # for SHA-384 suites
#
# P_hash(Hash, secret, seed) = HMAC_Hash(secret, P_hash(secret, seed+1))
# HMAC_Hash(secret, P_hash(secret, seed+2)) ...
# Essentially: HMAC-based key derivation, iterated to expand arbitrarily.
#
# Used for: master_secret derivation, key expansion, Finished MAC
# TLS 1.3 HKDF β RFC 5869, integrated into RFC 8446
# HKDF has two phases:
# 1. HKDF-Extract(salt, IKM) β PRK (pseudorandom key)
# 2. HKDF-Expand-Label(PRK, label, context, length) β OKM (output key material)
#
# HKDF-Extract: HKDF-Extract(SHA-256, salt, IKM) = HMAC-SHA256(salt, IKM)
# HKDF-Expand-Label: uses HMAC in counter mode to expand PRK into key material
#
# Label format (RFC 8446 Β§7.1):
# HKDF-Expand-Label(Secret, Label, Context, Length) =
# HKDF-Expand(PRK, I2OSP(Length, 2) || label || 0x00 || Context, Length)
# where label = "tls13 " || ASCII label (e.g., "client_handshake_traffic_secret")
#
# Context = transcript hash (binds key to all handshake messages seen so far)
# This is the key to TLS 1.3's security reduction: the transcript hash
# ties derived keys to everything that happened in the handshake.
# Python: HKDF example
python3 << 'EOF'
import cryptography.hazmat.primitives.hazmat.primitives.hashes as hashes
import cryptography.hazmat.primitives.kdf.hkdf as hkdf
from cryptography.hazmat.backends import default_backend
ikm = b'\\x00' * 32 # pseudo shared secret from ECDHE
hkdf_expand = hkdf.HKDFExpand(
algorithm=hashes.SHA256(),
length=64,
context=b'tls13 client_application_traffic_secret',
backend=default_backend()
)
key_material = hkdf_expand.expand(ikm)
print("64-byte expanded key material:", key_material.hex())
EOF`} TLS 1.3 Cipher Suite Changes
TLS 1.3 (RFC 8446) dramatically simplified cipher suite negotiation. Only five cipher suites are registered; forward secrecy is mandatory; and auth is no longer part of the suite name.
{`# The five TLS 1.3 cipher suites:
TLS_AES_128_GCM_SHA256 # mandatory-to-implement
TLS_AES_256_GCM_SHA384 # for high security applications
TLS_CHACHA20_POLY1305_SHA256 # mobile / low-power devices
TLS_AES_128_CCM_SHA256 # mandatory (RFC 8446 Β§41.2.1)
TLS_AES_128_CCM_8_SHA256 # exportable variant (discouraged)
# What's different from TLS 1.2:
# 1. No static RSA key exchange β RSA can only sign, not encrypt keys
# (this removes the FREAK, Logjam, and DROWN attack families)
# 2. No CBC cipher modes β removes POODLE, BEAST, Lucky13
# 3. No RC4 β removed due to bias attacks
# 4. No export ciphers β removed (FREAK was an export cipher attack)
# 5. Auth is NOT in the cipher suite name
# (signature_algorithms extension negotiates auth separately)
# 6. Forward secrecy is mandatory (no RSA key exchange possible)
# 7. 0-RTT uses early_data secret but carries replay risk
# ClientHello in TLS 1.3 carries only the AEAD suite:
# cipher_suites = [
# TLS_AES_128_GCM_SHA256, # 0x1301
# TLS_AES_256_GCM_SHA384, # 0x1302
# TLS_CHACHA20_POLY1305_SHA256 # 0x1303
# ]
# Server picks one. That's it.
# TLS 1.2 carried auth + cipher + PRF all in one suite:
# cipher_suites = [
# TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, # ECDHE key exchange
# # RSA authentication
# # AES-128-GCM cipher
# # SHA-256 PRF
# ]
# Python: enumerate TLS 1.3 cipher suites
python3 -c "
import ssl, hashlib
suites = [
('TLS_AES_128_GCM_SHA256', b'\\x13\\x01'),
('TLS_AES_256_GCM_SHA384', b'\\x13\\x02'),
('TLS_CHACHA20_POLY1305_SHA256', b'\\x13\\x03'),
('TLS_AES_128_CCM_SHA256', b'\\x13\\x04'),
('TLS_AES_128_CCM_8_SHA256', b'\\x13\\x05'),
]
for name, code in suites:
print(name, '->', code.hex())
"`} Configuring Cipher Suites
Server-side cipher suite configuration is where theory meets production. A misconfigured server can accept weak suites that compromise security.
{`# nginx: TLS 1.3 only, modern suites only
ssl_protocols TLSv1.3;
ssl_ciphers TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
ssl_prefer_server_ciphers off; # client chooses among server's list
# Apache
SSLProtocol TLSv1.3
SSLCipherSuite TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
SSLHonorCipherOrder Off
# HAProxy
ssl-default-bind-ciphers TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256
# TLS 1.2 fallback (legacy clients only)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
# Check your server's cipher suites:
openssl s_client -connect example.com:443 -tls1_3 -cipher TLS_AES_128_GCM_SHA256 &1 | grep -i cipher`} Tradeoffs
AES-GCM vs ChaCha20 on mobile
AES-GCM with AES-NI is 3x faster on desktop/server x86-64 but slower on low-end ARM devices that lack AES-NI. ChaCha20-Poly1305 is constant-time on all architectures and wins on mobile. Google serves ChaCha20 to Android clients, AES-GCM to desktop Chrome.
RSA vs ECDSA certificate cost
RSA-2048 signing on the server side costs ~0.5ms; ECDSA P-256 costs ~0.05ms β a 10x difference in CPU cost at high throughput. For services terminating millions of connections per day, ECDSA certs save real money on CPU.
AES-128 vs AES-256
AES-128 is considered secure against all classical attacks. AES-256 provides a larger security margin against future quantum attacks (NIST predicts a quantum computer would need ~2^40 QC operations to break AES-256 vs ~2^29 for AES-128). The performance difference is negligible on modern hardware.
Forward secrecy's operational cost
ECDHE requires the server to perform an additional scalar multiplication per handshake. At very high connection rates (millions/minute), this adds CPU pressure. DHE with large groups (FFDHE-3072) is even more expensive. X25519 is the most efficient option for forward secrecy.
FAQ
Why does TLS 1.3 have only five cipher suites?
TLS 1.2 had ~300 combinations of key exchange, auth, cipher, and MAC, most redundant or weak. TLS 1.3 separated these concerns: key exchange is ECDHE only (forward secrecy mandatory), auth is in a separate extension, and only AEAD ciphers are allowed. That leaves just the AEAD primitive choice: AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305, AES-128-CCM, and AES-128-CCM-8.
What happened to RC4 in TLS?
RC4 was widely used because it was fast and didn't require padding (resisting BEAST). But it has known biases β after ~2^26 encryptions the first bytes of the stream leak key material. Practical RC4 attacks (Andriesse et al., 2016) demonstrated recovery of user credentials from RC4-encrypted TLS sessions. RC4 was formally prohibited by RFC 7465 in 2015.
What is a null cipher suite?
TLS_NULL_WITH_NULL_NULL (0x0000) is the cipher suite that provides no security β no encryption, no MAC, nothing. It's used as a sentinel in protocol testing and as a special case in the protocol specification. No production system should ever negotiate it, but checking for it is part of formal protocol validation.
Why was static RSA removed from TLS 1.3?
Static RSA key exchange allowed an eavesdropper who recorded traffic to later decrypt it by stealing the server's RSA private key β no forward secrecy. Beyond that, RSA key exchange was vulnerable to the Bleichenbacher oracle attack family (ROBOT, Return of Bleichenbacher's Oracle Threat, 2017), which exploited the padding structure of RSA PKCS#1 v1.5. Removing it eliminates an entire vulnerability class.
What are FFDHE groups and why do some servers ban them?
Finite-field DHE (FFDHE) uses modular arithmetic on large primes. The IANA registry defines safe primes (RFC 7919) at 2048, 3072, and 4096 bits. Some operators disable FFDHE-2048 because the 2048-bit group is smaller than the ECDHE P-256 group (which provides ~128-bit security) and consumes more CPU per handshake. Others ban small groups to prevent Logjam attacks.
What cipher does Chrome negotiate with example.com?
As of 2024, Chrome on desktop negotiates TLS 1.3 with TLS_AES_256_GCM_SHA384 when the server supports it. If the server only supports TLS 1.2, it falls back to TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 or TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384. Use Chrome DevTools → Security tab or openssl s_client to check your specific server.