TLS 1.3 // RFC 8446

TLS wraps every byte after a TCP connection in a typed record. Inside those records, ClientHello and ServerHello negotiate a version, cipher suite, key share and ALPN protocol; extensions carry SNI, groups, signatures and certificates. This page decodes the plaintext handshake and the record layer, and shows exactly where the encrypted boundary begins.

RFC 8446 · TLS 1.3 RFC 5246 · TLS 1.2 RFC 7301 · ALPN RFC 6066 · SNI 5 B record header HKDF · AEAD TCP/443
layer application (over TCP) record 5 B header handshake 4 B header encryption AEAD after ServerHello

01Record layer

five-byte header · typed fragments

TLS does not hand TCP a naked handshake message. Every message is first wrapped in a record: one content type, a legacy version, a two-byte length and the fragment. A handshake message may span several records, and one record may hold several handshake messages. The record layer is also where AEAD protection is applied.

Content typeCodeFragmentTLS 1.3 note
ChangeCipherSpec201 byte, value 0x01Compatibility only. It is not a key change in TLS 1.3.
Alert21Level + descriptionFatal alerts close the connection. Some alerts are encrypted.
Handshake22One or more handshake messagesPlaintext only until ServerHello; later handshake records are encrypted inside application data.
Application Data23AEAD-protected bytesAlso carries encrypted handshake messages after ServerHello.
Heartbeat24Heartbeat messageRarely used; historically a source of memory-disclosure bugs.
the legacy version is not the negotiated version

TLS 1.3 records use 0x0303 in the legacy record version for compatibility, and the initial ClientHello record may use 0x0301. The real version is negotiated in the supported_versions extension. Do not infer TLS 1.3 from the record header alone.

02Handshake messages

one type byte · three length bytes

Every handshake message begins with a one-byte type and a 24-bit length. The length counts the body only, so the total message is 4 + length bytes. Handshake messages are ordered: the client speaks first, the server answers, and Finished proves that both sides derived the same keys.

TypeNameDirectionPurpose
1ClientHelloC → SVersion, random, session ID, cipher suites, key share and extensions.
2ServerHelloS → CSelected version, cipher suite, server random and key share.
4NewSessionTicketS → CResumption lifetime, nonce, ticket and early-data size.
5EndOfEarlyDataC → SMarks the end of 0-RTT data.
8EncryptedExtensionsS → CExtensions that must not be visible before keys exist, including ALPN.
11CertificateS → C / C → SCertificate chain and per-certificate extensions.
13CertificateRequestS → CRequest for client authentication.
15CertificateVerifyS → C / C → SSignature over the handshake transcript.
20FinishedBothHMAC over the transcript with the finished key.
24KeyUpdateBothRequests a new application traffic secret.
the encrypted boundary

In TLS 1.3 only ClientHello and ServerHello are plaintext handshake messages. Everything after ServerHello — EncryptedExtensions, Certificate, CertificateVerify and Finished — is sent as Application Data records protected with handshake traffic keys. A capture without keys shows ciphertext, not a parseable Certificate message.

03ClientHello & ServerHello

the two plaintext messages worth knowing by heart

The hello messages carry the negotiation. ClientHello lists what the client supports; ServerHello selects one version, one cipher suite and one key share. Both keep the TLS 1.2 field shapes so middleboxes see a familiar structure, then move the real decisions into extensions.

FieldWidthClientHelloServerHello
legacy_version2 B0x0303 in TLS 1.30x0303 in TLS 1.3
random32 BClient random, later mixed into the key scheduleServer random, later mixed into the key schedule
legacy_session_id1 + 0–32 BCompatibility session ID; may be emptyEchoes the client value
cipher_suites2 + 2n BList of offered suitesOne selected cipher suite
legacy_compression_methods1 + n BMust include 0 (null)0
extensions2 + n BSNI, groups, signatures, ALPN, versions, key sharesupported_versions, key_share, pre_shared_key

04Extensions

type · length · data · repeated everywhere

Extensions are the extension mechanism that made TLS 1.3 possible without changing the hello envelope. Each one is a four-byte header and an opaque body whose shape depends on the extension type and the message that carries it. The same type can mean different things in ClientHello, ServerHello, EncryptedExtensions and Certificate.

CodeExtensionCarriesSeen in
0server_nameHost name (SNI); empty acknowledgement in EncryptedExtensionsClientHello, EncryptedExtensions
10supported_groupsNamed groups: x25519, secp256r1, FFDHEClientHello, EncryptedExtensions
13signature_algorithmsSignature schemes the peer acceptsClientHello, CertificateRequest
16ALPNProtocol names such as h2, http/1.1ClientHello, EncryptedExtensions
43supported_versionsVersion list or the selected versionClientHello, ServerHello
51key_shareEphemeral public keys per groupClientHello, ServerHello
41pre_shared_keyPSK identities and bindersClientHello, ServerHello
45psk_key_exchange_modespsk_ke or psk_dhe_keClientHello
28record_size_limitLargest record the peer will acceptClientHello, EncryptedExtensions
5status_requestOCSP stapling request or responseClientHello, Certificate
23extended_master_secretEmpty marker for EMSClientHello, ServerHello
0xfe0dencrypted_client_helloECHConfig and encrypted SNIClientHello

05TLS 1.3 key schedule

HKDF-Extract · Derive-Secret · Expand-Label

TLS 1.3 replaces the TLS 1.2 PRF cascade with a small HKDF-based schedule. Two inputs matter: the ECDHE shared secret and the PSK (zero when there is no resumption). Each stage mixes in a transcript hash, so a change to any handshake byte changes every later traffic secret.

early secretHKDF-Extract(0, PSK)salt is zero; zero PSK when resumption is not used
client_early_traffic_secretDerive-Secret(early, "c e traffic", ClientHello)protects 0-RTT data
derived early secretDerive-Secret(early, "derived", "")feeds the next HKDF-Extract as its salt
handshake secretHKDF-Extract(derived early, ECDHE)ECDHE is the input keying material (IKM), not the salt
derived handshake secretDerive-Secret(handshake, "derived", "")feeds the final HKDF-Extract as its salt
client/server handshake trafficDerive-Secret(handshake, "c hs traffic" / "s hs traffic", transcript)protects EncryptedExtensions through Finished
master secretHKDF-Extract(derived handshake, 0)IKM is a string of Hash.length zero bytes
client/server application trafficDerive-Secret(master, "c ap traffic" / "s ap traffic", transcript)protects application data and KeyUpdate
SecretDerived fromProtectsNotes
early secretPSK or zeros—Only meaningful with PSK resumption.
client_early_traffic_secretearly secret + ClientHello transcript0-RTT application dataReplayable; servers should limit what 0-RTT can do.
handshake secretderived early secret (salt) + ECDHE (IKM)—PSK resumption normally uses psk_dhe_ke for forward secrecy; psk_ke is allowed but weaker.
client/server handshake traffichandshake secret + transcriptEncryptedExtensions, Certificate, CertificateVerify, FinishedThis is why the certificate is invisible without keys.
master secretderived handshake secret (salt) + zeros (IKM)—Exists so application keys are separated from handshake keys.
client/server application trafficmaster secret + transcriptApplication Data recordsKeyUpdate derives the next generation from the current secret.
exporter / resumption secretsmaster secret + transcriptOutside TLS recordsUsed for channel bindings, EAP, and session tickets.
AEAD nonce construction

TLS 1.3 builds the per-record nonce by XORing the 64-bit record sequence number into the static IV derived from the traffic secret. The sequence number resets to zero when keys change. Reusing a nonce with the same key breaks AEAD catastrophically, which is why KeyUpdate exists and why the record sequence number must never wrap.

06Alerts

two bytes · level and description

An alert is a two-byte record fragment. The level is 1 for warning or 2 for fatal. In TLS 1.3 most alerts are fatal, and alerts sent after the handshake keys are installed are encrypted like application data.

CodeAlertTypical cause
0close_notifyClean shutdown. It prevents truncation attacks.
20bad_record_macAEAD authentication failed or keys disagree.
22record_overflowRecord fragment larger than the negotiated limit.
40handshake_failureNo common cipher suite, group or version.
42bad_certificateCertificate could not be parsed or verified.
47illegal_parameterA field was well-formed but not allowed in context.
50decode_errorA message could not be decoded.
51decrypt_errorSignature or Finished verification failed.
70protocol_versionNo mutually supported protocol version.
109missing_extensionA required extension was absent.
112unrecognized_nameSNI name is not served by this endpoint.
120no_application_protocolALPN found no overlap.

07Encoder

ClientHello · ServerHello · Alert · Application Data

Build a syntactically valid TLS record. The key share is illustrative unless you paste a real public key; the point is the encoding, not a usable handshake. Every length is derived from the fields you enter, and the output can be fed straight back into the decoder. ALPN selection for TLS 1.3 belongs in EncryptedExtensions, not ServerHello, so it is not offered here; the ClientHello form still shows the offered protocol list.

TLS parameters

RFC 8446

The example key share is deterministic filler, not a valid point or key for the selected group. Replace it with a real public key of the group's size if you need a handshake that a peer would accept. Lengths, extension framing and the record header are exact.

wire output

waiting
Set the fields and press Build TLS.
TLS record hex
no bytes built yet
record — handshake — extensions —
what the encoder does not do

It does not generate a private key, sign the transcript, derive traffic keys or encrypt anything. It builds the plaintext record and handshake structures so you can inspect the layout and feed the bytes to a decoder.

08Decoder

record stream · handshake · TCP · IPv4 · IPv6 · Ethernet

Paste raw TLS records, a bare handshake message, a TCP payload or a complete packet. The decoder walks the record layer, reassembles handshake messages that span records, expands ClientHello/ServerHello/EncryptedExtensions, and reports the point at which the bytes became opaque ciphertext.

parse bytes

RFC 8446 · records · extensions
No TLS records decoded yet.

09Wire notes

what TLS 1.3 removed, and what still bites
TLS 1.3 is not “TLS 1.2 with a new version number”

RSA key transport, static DH, CBC-mode suites, compression, renegotiation and custom DHE groups are gone from TLS 1.3. A ClientHello that offers only 0x0303 is a TLS 1.2 client even if the record layer looks modern.

  • SNI is not authentication. The server_name extension selects a certificate; the certificate and CertificateVerify signature are what prove the endpoint.
  • ALPN is negotiated, not implied by the port. TCP/443 can carry HTTP/1.1, HTTP/2 or another protocol. Read the ALPN result in EncryptedExtensions before assuming HTTP/2.
  • 0-RTT is replayable. Early data is encrypted with keys derived before the handshake completes. Servers must restrict it to idempotent operations.
  • Record boundaries are not message boundaries. A single handshake message can span records, and one record can contain several handshake messages. A decoder must buffer across records.
  • Encrypted records reveal lengths. TLS 1.3 hides content but not record sizes or timing. Padding and record_size_limit reduce, but do not remove, traffic analysis.
  • The certificate chain can be large. A Certificate message may exceed one record; the decoder concatenates handshake fragments before parsing.
  • Middlebox compatibility mode. Many TLS 1.3 clients send a non-empty legacy_session_id and a dummy ChangeCipherSpec record so older middleboxes keep working. Neither changes the keys.
  • DoT, DoH and DoQ are different transports. DoT is TLS over TCP/853, DoH is HTTP over TLS, and DoQ is QUIC. The TLS handshake is similar; the framing above it is not.

References