TLS 1.3 (Transport Layer Security)¶
The protocol behind HTTPS. One round trip agrees on keys, proves the server's identity with a certificate, and switches every byte after that to an AEAD cipher.
Standard: RFC 8446 (2018); QUIC (HTTP/3) runs the same handshake over UDP, RFC 9000 and RFC 9001 (2021). Implementations: OpenSSL, BoringSSL (Chrome, Android), NSS (Firefox), rustls, GnuTLS, SChannel (Windows), LibreSSL (macOS command line).
One connection, one algorithm per category¶
Real output from openssl s_client -connect www.google.com:443 on a Mac
(LibreSSL 3.3.6, Oct 2026):
Server Temp Key: ECDH, X25519, 253 bits
Protocol : TLSv1.3
Cipher : AEAD-CHACHA20-POLY1305-SHA256
| TLS name | Category | Algorithm inside |
|---|---|---|
x25519 key share |
Key exchange | Diffie-Hellman on Curve25519 |
| certificate key | Signature | Google's certificate holds an ECDSA P-256 key |
TLS_CHACHA20_POLY1305_SHA256 |
Cipher | ChaCha20-Poly1305 for the data |
| (same suite) | Hash / KDF | SHA-256 for the transcript and HKDF |
| MAC | MAC | Poly1305, built into the AEAD |
Why X25519 and not post-quantum? LibreSSL 3.3.6 is older than ML-KEM, so it
never offers X25519MLKEM768. Chrome talking to the same server gets the
hybrid. The library decides what you get: the Mac's system Python links an even
older LibreSSL (2.8.3) and only reaches TLS 1.2 (see the example below).
Unlike SSH, the server chooses in TLS: the client offers lists and the server picks from them.
How a connection works¶
- ClientHello: supported versions, cipher suites,
supported_groups,signature_algorithms, and akey_sharewith a public key for the group it guesses the server will pick (for exampleX25519MLKEM768andx25519). - ServerHello: the server picks a suite and a group and sends its own key share. Both sides now hold the shared secret, and everything after this message is encrypted.
- EncryptedExtensions, Certificate, CertificateVerify, Finished: the server sends its certificate chain, signs the transcript hash with the certificate's key, and sends an HMAC over the transcript.
- Client checks the chain against its trust store (see PKI), the signature and the Finished MAC, then sends its own Finished. Application data flows.
If the client guessed the wrong group, the server answers with a HelloRetryRequest and the handshake costs one more round trip. Resumed sessions can send 0-RTT early data in the first flight, but an attacker can replay it, so servers accept it only for safe requests.
Keys come from the HKDF key schedule (RFC 8446 §7.1): early secret → handshake secret → master secret, each step mixing in the transcript hash.
Algorithms by category¶
| Category | In use | Standard | Legacy or retired |
|---|---|---|---|
Key exchange (supported_groups) |
X25519MLKEM768, x25519, secp256r1, secp384r1, ffdhe2048 and larger |
IETF draft (ecdhe-mlkem), RFC 8446 + RFC 7748, RFC 8446, RFC 7919 | RSA key transport, static DH, custom DH groups (all removed in 1.3) |
Signature (signature_algorithms) |
ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256, ed25519; ML-DSA in drafts |
RFC 8446, IETF draft | rsa_pkcs1_sha1, ecdsa_sha1; rsa_pkcs1_* only inside certificates, never in the handshake |
| Cipher suites (AEAD) | TLS_AES_128_GCM_SHA256, TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_CCM_SHA256 |
RFC 8446 | CBC suites, RC4 (RFC 7465), 3DES, NULL and export suites (TLS 1.2 and older) |
| MAC | built into the AEAD; HMAC in Finished and HKDF | RFC 8446, RFC 2104 | HMAC-MD5 / HMAC-SHA-1 record MACs (old CBC suites) |
| Hash | SHA-256 or SHA-384, set by the cipher suite | FIPS 180-4 | MD5 + SHA-1 (TLS 1.0/1.1 PRF) |
| KDF | HKDF key schedule | RFC 5869, RFC 8446 §7.1 | TLS 1.2 PRF (RFC 5246) |
Every TLS 1.3 implementation must support TLS_AES_128_GCM_SHA256, secp256r1
and the rsa_pss_rsae_sha256 and ecdsa_secp256r1_sha256 signatures
(RFC 8446 §9.1). X25519MLKEM768 is on by default in Chrome and Firefox and in
OpenSSL 3.5 (2025).
Versions¶
| Version | Standard | Released | Deprecated | Status |
|---|---|---|---|---|
| SSL 2.0 | Netscape | 1995 | RFC 6176 (2011) | Broken |
| SSL 3.0 | RFC 6101 (historic) | 1996 | POODLE (2014); RFC 7568 (2015) | Broken |
| TLS 1.0 | RFC 2246 | 1999 | RFC 8996 (2021) | Legacy |
| TLS 1.1 | RFC 4346 | 2006 | RFC 8996 (2021) | Legacy |
| TLS 1.2 | RFC 5246 | 2008 | — | Acceptable |
| TLS 1.3 | RFC 8446 | 2018 | — | Recommended |
Seeing it on your machine¶
- Browser: Chrome DevTools → Security shows the version, group and cipher of the current page.
- Command line:
openssl s_client -connect www.google.com:443 -servername www.google.com </dev/null
- Python: prints the version and cipher your Python's TLS library gets.
import socket, ssl
host = input("host: ").strip() or "www.google.com"
ctx = ssl.create_default_context()
with socket.create_connection((host, 443)) as s:
with ctx.wrap_socket(s, server_hostname=host) as t:
print(t.version(), t.cipher()[0])
On the Mac's system Python (LibreSSL 2.8.3) this prints
TLSv1.2 ECDHE-ECDSA-CHACHA20-POLY1305. A Python built with OpenSSL 3 prints
TLS 1.3 and a TLS_ suite.
See also¶
- SSH: same building blocks, but there the client's list decides
- PKI: certificates, chains and the formats they come in
- ECDH / AEAD / HKDF: the algorithms in depth