TLS Handshake Inspector

Step through a TLS 1.3 handshake over one fixed vector: decode what the cipher suite commits both sides to, and walk the HKDF key schedule with every derived secret in view. No network is touched at any point.

Rendered when the site was built, by the same code the browser runs. No JavaScript required, and no network at any point: the handshake below is a fixed vector held in the library this page is built from. With JavaScript on, this is replaced by the interactive stepper, which reveals one message at a time and opens one schedule node at a time.

The handshake, step by step

Seven messages, TLS_AES_128_GCM_SHA256 negotiated. The first two travel in the clear and everything after them is encrypted under the handshake keys, which is the change TLS 1.3 made over TLS 1.2.

The seven handshake messages
StepMessageDirectionProtectionBytesWhat happens
1ClientHelloC -> Sin the clear174The client offers everything it can do, and guesses a key share.
2ServerHelloS -> Cin the clear122The server picks one suite and returns its key share.
3EncryptedExtensionsS -> Cencrypted with handshake keys6The server's remaining answers, now under the handshake keys.
4CertificateS -> Cencrypted with handshake keys45The server presents its certificate chain, encrypted.
5CertificateVerifyS -> Cencrypted with handshake keys72The server signs the transcript, proving it holds the private key.
6Finished (server)S -> Cencrypted with handshake keys36The server MACs the transcript with its finished key.
7Finished (client)C -> Sencrypted with handshake keys36The client MACs the transcript in turn, and the handshake is done.
  • 1. ClientHello

    TLS 1.3's speed comes from a guess. The client does not wait to be told which group the server wants: it picks one, sends its public key with the very first message, and is usually right. The host name goes out in the clear here, which is the one part of a TLS 1.3 handshake an observer can still read.

    What it commits to: the client cannot later claim it offered a different set of suites or groups: every byte of this message is inside the transcript that both Finished messages sign.

    ClientHello: fields
    FieldValueWhat it does
    HandshakeType1 (0x01)The one byte that says which handshake message this is.
    length170 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    legacy_version0x0303Frozen at "TLS 1.2" forever. Middleboxes rejected anything else, so the real version moved into an extension.
    random000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f32 fresh bytes from the client. Pinned to a ramp in this vector.
    cipher_suites0x1301, 0x1302, 0x1303The suites the client will accept, best first. The server picks one.
    supported_versions0x0304 (TLS 1.3)The extension that actually negotiates the version, because legacy_version could not be moved.
    supported_groups0x001d (x25519)Which Diffie-Hellman groups the client can use.
    key_sharex25519, 606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7fThe client's public key, sent before the server has said which group it wants. Guessing right saves a whole round trip.
    signature_algorithms0x0403, 0x0804Which signatures the client will accept on the server's certificate.
    server_nameexample.invalidThe host being asked for, in the clear. This is the one field of a TLS 1.3 handshake that still leaks which site is being visited.
    Transcript hash through this message
    b73c4bb9b58ea046da272417b4ae31351fbaf5295aeb3d2465e4ad28eceb56af
  • 2. ServerHello

    One suite, one group, one key share. The moment this message lands, both sides can compute the ECDHE shared secret, and from it the Handshake Secret and everything underneath. This is the last message anyone on the path can read.

    What it commits to: the suite named here fixes the AEAD and the hash for the rest of the connection, and the hash fixes the size of every secret in the key schedule.

    ServerHello: fields
    FieldValueWhat it does
    HandshakeType2 (0x02)The one byte that says which handshake message this is.
    length118 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    legacy_version0x0303Frozen, exactly as in the ClientHello.
    random202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3fThe server's 32 bytes. Pinned to a ramp in this vector.
    cipher_suite0x1301 (TLS_AES_128_GCM_SHA256)The single suite chosen from the client's list. This fixes the AEAD and the hash for the whole connection.
    supported_versions0x0304 (TLS 1.3)The server agreeing to TLS 1.3, in the extension rather than the field.
    key_sharex25519, 808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fThe server's public key. With this the shared secret exists at both ends and the handshake stops being readable.
    Transcript hash through this message
    091d7eab961aedd8efd99e344015afdcf887e90d3a03c6daa1d3374a14009c6b
  • 3. EncryptedExtensions

    Everything the server has to say that is not the certificate: the protocol chosen by ALPN, whether the server name was accepted, and so on. In TLS 1.2 all of this was in the clear. Here it is the first encrypted message of the connection.

    What it commits to: nothing new about keys: this message exists to move answers off the readable part of the handshake.

    EncryptedExtensions: fields
    FieldValueWhat it does
    HandshakeType8 (0x08)The one byte that says which handshake message this is.
    length2 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    extensionsnone in this vectorA real connection carries ALPN, the server-name acknowledgement, and similar answers here.
    Transcript hash through this message
    c9ec5a5a2bdea736f5a2b323c0171125747f0f9f60d5b4660972615da193adf0
  • 4. Certificate

    The chain is encrypted, so an observer cannot tell which site was reached from the certificate alone. The certificate in this vector is a placeholder: reading a real one is a different job, and there is a tool for it.

    What it commits to: the server is now claiming an identity. The claim is worth nothing until the next message proves it holds the matching private key.

    Certificate: fields
    FieldValueWhat it does
    HandshakeType11 (0x0b)The one byte that says which handshake message this is.
    length41 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    certificate_request_contextemptyNon-empty only when the certificate answers a request made later on an open connection.
    certificate_list1 entry, 32 bytesA placeholder in this vector. Take a real chain apart in the Certificate Decoder instead.
    Transcript hash through this message
    e35778e8fcb0930abba1198e3fbe746ab645c28aa9dd7d8ba0bdfe5b6ce02349
  • 5. CertificateVerify

    The signature is over the whole handshake so far, not over a nonce the client chose. That means it proves two things at once: the server holds the certificate's private key, and it saw exactly the messages the client sent.

    What it commits to: the identity is now proved for this transcript only. The same signature cannot be replayed onto a different handshake, because a different handshake has a different transcript hash.

    CertificateVerify: fields
    FieldValueWhat it does
    HandshakeType15 (0x0f)The one byte that says which handshake message this is.
    length68 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    algorithm0x0403 (ecdsa_secp256r1_sha256)Which signature scheme was used, chosen from the client's list.
    signature64 bytes (placeholder)Over a fixed context string plus the transcript hash so far. Pinned here: producing a real one needs a private key, which this page has no business holding.
    Transcript hash through this message
    4f0c760aa1452be2ac406db4169ca622a9529c1c380f1488c7a6dd2157f6deb0
  • 6. Finished (server)

    An HMAC over the transcript under the server finished key. Only a party that derived the same schedule from the same messages can produce it, so it closes the handshake against any change on the path that got this far. With it, the application secrets become derivable.

    What it commits to: the server's half of the handshake is complete and both sides agree on every byte exchanged so far.

    Finished (server): fields
    FieldValueWhat it does
    HandshakeType20 (0x14)The one byte that says which handshake message this is.
    length32 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    verify_data32 bytes, computedHMAC of the transcript hash under this side's finished key. This one is not pinned: it is derived from the key schedule on this page, so a wrong derivation could not produce a consistent trace.
    Transcript hash through this message
    a163c65ca4b431037883e6f18c577e79d105d992a66fbd355940fd528230bfb3
  • 7. Finished (client)

    The client's matching HMAC, under its own finished key. After this the connection is open: application data flows under the application traffic keys, and the resumption secret is fixed so the server can issue a ticket for next time.

    What it commits to: both sides have proved they derived the same schedule. Anything after this is application data.

    Finished (client): fields
    FieldValueWhat it does
    HandshakeType20 (0x14)The one byte that says which handshake message this is.
    length32 bytesA three-byte length. The four header bytes plus this is the whole message, and every byte of it goes into the transcript hash.
    verify_data32 bytes, computedHMAC of the transcript hash under this side's finished key. This one is not pinned: it is derived from the key schedule on this page, so a wrong derivation could not produce a consistent trace.
    Transcript hash through this message
    053908e9b66d8b1251455d9b292919b3cf30cab64a6bbe5908a2d854e7fc9b08

The key schedule

RFC 8446 §7.1, run for real over the vector above. Every arrow reads downwards: a node is derived from the node above it, never the other way about. 21 nodes, and the two constants at the top are the ones every TLS 1.3 stack computes identically because a full handshake has no pre-shared key to mix in.

TLS 1.3 key schedule (TLS_AES_128_GCM_SHA256, HKDF-SHA-256)
Early Secret [extract]
    = 33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a
  +-- Derived (early) [expand]
      = 6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba
    +-- Handshake Secret [extract]
        = 68ebe593efe91a9762eb5e1994936dd566dc5039760c3e2d4e58724b6b8aac3f
      +-- client_handshake_traffic_secret [expand]
          = b668e55e5b90a554e228b32a7e9993b2d256aeb31bfe7188a6cc63fef41d650f
        +-- client handshake write key [expand]
            = 1cc8914229bfe84ad3607db4bd709e06
        +-- client handshake write IV [expand]
            = de79c4ccaee297f3cde08987
        +-- client finished key [expand]
            = 3b9875e00ae60e70e74b30467ec46ebebc69bbf6b75d713af3635244c8fb378d
      +-- server_handshake_traffic_secret [expand]
          = 8e80abbae21490892e50ed64d49279c80cef6d474dbc4d23815cfc1fb831a281
        +-- server handshake write key [expand]
            = d9004a408b71080389fae2ce54427cf6
        +-- server handshake write IV [expand]
            = aa3f6546c7db775b8ec16236
        +-- server finished key [expand]
            = b9eb8f26569e5c256ff43bf75e6107996a638c4b8eafe67396d1abf8255bd307
      +-- Derived (handshake) [expand]
          = d731f5738beb92107be34d5a2d82c74015d533bacbe3a76a7f86e6aca3be67fd
        +-- Master Secret [extract]
            = 2fabb717fc05145bb76f914fb49ea788b696edbc9f07482cc0447e13ac8ea77d
          +-- client_application_traffic_secret_0 [expand]
              = 7fc0acb341206c22ce894cf4f7d42f3ebbb8984f60a5d57246e0770229b4b110
            +-- client application write key [expand]
                = 145e1ba6147c71c2c30f64a0de100126
            +-- client application write IV [expand]
                = 228bead61f3f2ac89af50590
          +-- server_application_traffic_secret_0 [expand]
              = 640a34d23ac813ba0647727ab07cacc88710f2ce5e7ad79736127713b3786cc7
            +-- server application write key [expand]
                = c65d5fb0c387ccb342f09ab0516e973e
            +-- server application write IV [expand]
                = 688498b1f2ead690f4ff9047
          +-- exporter_master_secret [expand]
              = a2ad90d8fc4923c9014cae6c356e6ee1913f26d9df1d605ff9d46053ce3ec017
          +-- resumption_master_secret [expand]
              = a8aadcddac462c50f76bde3a1896bd326127c2efbbba650524183a6eeb23b6bd
The key schedule, node by node
SecretHKDF stepDerived fromDerivationValueAvailableWhat it is for
Early Secretextractnothing: this is the rootHKDF-Extract(salt = 32 zero bytes, IKM = PSK)33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92abefore anything is sentThe top of the tree. A full handshake has no pre-shared key, so both inputs are zeros and this value is a fixed constant every TLS 1.3 stack in the world computes identically. It is not a secret, and it protects nothing on its own: it exists so that the resumption case and the full case can share one schedule.
Derived (early)expandEarly SecretHKDF-Expand-Label(Early Secret, "derived", Transcript-Hash(""), 32)6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611babefore anything is sentA spacer. HKDF-Extract wants a salt, and reusing the Early Secret directly would let one stage's output be another stage's salt unchanged. Expanding it under "derived" first keeps the stages cryptographically separate.
Handshake SecretextractDerived (early)HKDF-Extract(salt = Derived (early), IKM = ECDHE shared secret)68ebe593efe91a9762eb5e1994936dd566dc5039760c3e2d4e58724b6b8aac3fafter the ServerHelloThe first value in the schedule that an eavesdropper cannot compute. Everything above it is public arithmetic on zeros; this is where the key exchange finally mixes in, which is why the handshake stays readable on the wire right up until the ServerHello and not one byte longer.
client_handshake_traffic_secretexpandHandshake SecretHKDF-Expand-Label(Handshake Secret, "c hs traffic", Transcript-Hash(ClientHello..ServerHello), 32)b668e55e5b90a554e228b32a7e9993b2d256aeb31bfe7188a6cc63fef41d650fafter the ServerHelloThe client's secret for the rest of the handshake. It is bound to the transcript, so if anything on the path had edited the ClientHello or the ServerHello the two sides would derive different secrets here and the handshake would fail rather than continue quietly.
client handshake write keyexpandclient_handshake_traffic_secretHKDF-Expand-Label(client_handshake_traffic_secret, "key", "", 16)1cc8914229bfe84ad3607db4bd709e06after the ServerHelloThe AEAD key that actually encrypts records in this direction. Note the empty context: the traffic secret above it is already transcript-bound, so binding again would buy nothing.
client handshake write IVexpandclient_handshake_traffic_secretHKDF-Expand-Label(client_handshake_traffic_secret, "iv", "", 12)de79c4ccaee297f3cde08987after the ServerHelloThe base nonce. It is not used as-is: the record's sequence number is exclusive-ORed into its low bytes, so every record under one key gets a different nonce without any of them being sent.
client finished keyexpandclient_handshake_traffic_secretHKDF-Expand-Label(client_handshake_traffic_secret, "finished", "", 32)3b9875e00ae60e70e74b30467ec46ebebc69bbf6b75d713af3635244c8fb378dafter the ServerHelloThe key for this side's Finished message. Finished is an HMAC over the transcript under this key, which is how each side proves it derived the same schedule from the same messages.
server_handshake_traffic_secretexpandHandshake SecretHKDF-Expand-Label(Handshake Secret, "s hs traffic", Transcript-Hash(ClientHello..ServerHello), 32)8e80abbae21490892e50ed64d49279c80cef6d474dbc4d23815cfc1fb831a281after the ServerHelloThe server's half of the same pair. One label apart from the client's, and that single differing byte is the whole reason the two directions never share a key.
server handshake write keyexpandserver_handshake_traffic_secretHKDF-Expand-Label(server_handshake_traffic_secret, "key", "", 16)d9004a408b71080389fae2ce54427cf6after the ServerHelloThe AEAD key that actually encrypts records in this direction. Note the empty context: the traffic secret above it is already transcript-bound, so binding again would buy nothing.
server handshake write IVexpandserver_handshake_traffic_secretHKDF-Expand-Label(server_handshake_traffic_secret, "iv", "", 12)aa3f6546c7db775b8ec16236after the ServerHelloThe base nonce. It is not used as-is: the record's sequence number is exclusive-ORed into its low bytes, so every record under one key gets a different nonce without any of them being sent.
server finished keyexpandserver_handshake_traffic_secretHKDF-Expand-Label(server_handshake_traffic_secret, "finished", "", 32)b9eb8f26569e5c256ff43bf75e6107996a638c4b8eafe67396d1abf8255bd307after the ServerHelloThe key for this side's Finished message. Finished is an HMAC over the transcript under this key, which is how each side proves it derived the same schedule from the same messages.
Derived (handshake)expandHandshake SecretHKDF-Expand-Label(Handshake Secret, "derived", Transcript-Hash(""), 32)d731f5738beb92107be34d5a2d82c74015d533bacbe3a76a7f86e6aca3be67fdafter the ServerHelloThe same spacer trick one stage down, separating the handshake keys from the application keys. Learning a handshake traffic secret does not hand anyone the application ones.
Master SecretextractDerived (handshake)HKDF-Extract(salt = Derived (handshake), IKM = 32 zero bytes)2fabb717fc05145bb76f914fb49ea788b696edbc9f07482cc0447e13ac8ea77dafter the ServerHelloAn extract with nothing new to extract. The shape is kept so that all three stages look the same, which is what makes the schedule easy to reason about: every stage is an extract, then some labelled expands, then a spacer.
client_application_traffic_secret_0expandMaster SecretHKDF-Expand-Label(Master Secret, "c ap traffic", Transcript-Hash(ClientHello..server Finished), 32)7fc0acb341206c22ce894cf4f7d42f3ebbb8984f60a5d57246e0770229b4b110after the server FinishedThe client's secret for real traffic. The trailing zero in its RFC name is a generation counter: a long-lived connection can rotate to secret 1, 2 and onwards without another key exchange.
client application write keyexpandclient_application_traffic_secret_0HKDF-Expand-Label(client_application_traffic_secret_0, "key", "", 16)145e1ba6147c71c2c30f64a0de100126after the server FinishedThe AEAD key that actually encrypts records in this direction. Note the empty context: the traffic secret above it is already transcript-bound, so binding again would buy nothing.
client application write IVexpandclient_application_traffic_secret_0HKDF-Expand-Label(client_application_traffic_secret_0, "iv", "", 12)228bead61f3f2ac89af50590after the server FinishedThe base nonce. It is not used as-is: the record's sequence number is exclusive-ORed into its low bytes, so every record under one key gets a different nonce without any of them being sent.
server_application_traffic_secret_0expandMaster SecretHKDF-Expand-Label(Master Secret, "s ap traffic", Transcript-Hash(ClientHello..server Finished), 32)640a34d23ac813ba0647727ab07cacc88710f2ce5e7ad79736127713b3786cc7after the server FinishedThe server's half of the application pair, one label away from the client's again.
server application write keyexpandserver_application_traffic_secret_0HKDF-Expand-Label(server_application_traffic_secret_0, "key", "", 16)c65d5fb0c387ccb342f09ab0516e973eafter the server FinishedThe AEAD key that actually encrypts records in this direction. Note the empty context: the traffic secret above it is already transcript-bound, so binding again would buy nothing.
server application write IVexpandserver_application_traffic_secret_0HKDF-Expand-Label(server_application_traffic_secret_0, "iv", "", 12)688498b1f2ead690f4ff9047after the server FinishedThe base nonce. It is not used as-is: the record's sequence number is exclusive-ORed into its low bytes, so every record under one key gets a different nonce without any of them being sent.
exporter_master_secretexpandMaster SecretHKDF-Expand-Label(Master Secret, "exp master", Transcript-Hash(ClientHello..server Finished), 32)a2ad90d8fc4923c9014cae6c356e6ee1913f26d9df1d605ff9d46053ce3ec017after the server FinishedThe root of the exporter interface: a way for a protocol running over TLS to ask for key material of its own that is tied to this exact connection. It never protects TLS records itself.
resumption_master_secretexpandMaster SecretHKDF-Expand-Label(Master Secret, "res master", Transcript-Hash(ClientHello..client Finished), 32)a8aadcddac462c50f76bde3a1896bd326127c2efbbba650524183a6eeb23b6bdafter the client FinishedThe one secret bound to the whole transcript, client Finished included, because it outlives the connection: a ticket issued from it lets the next connection start from a pre-shared key instead of from zeros.

Cipher suites

TLS 1.3 threw the old registry away rather than pruning it. Five suites remain, and a name now says only which AEAD protects the records and which hash drives the key schedule. The key exchange is always (EC)DHE and the certificate type is negotiated separately, so neither appears in the name any more.

The five TLS 1.3 cipher suites
SuiteCode pointAEADHashKeyNonceTag
TLS_AES_128_GCM_SHA2560x1301AES-128-GCMSHA-25616 B12 B16 B
TLS_AES_256_GCM_SHA3840x1302AES-256-GCMSHA-38432 B12 B16 B
TLS_CHACHA20_POLY1305_SHA2560x1303ChaCha20-Poly1305SHA-25632 B12 B16 B
TLS_AES_128_CCM_SHA2560x1304AES-128-CCMSHA-25616 B12 B16 B
TLS_AES_128_CCM_8_SHA2560x1305AES-128-CCM with a short tagSHA-25616 B12 B8 B
  • TLS_AES_128_GCM_SHA256

    TLS_AES_128_GCM_SHA256: field by field
    PartValueWhat it means
    Code point0x1301The two bytes that actually travel. The ClientHello offers a list of these and the ServerHello echoes back exactly one.
    PrefixTLS_Every TLS 1.3 name begins here and then goes straight to a cipher. In TLS 1.2 the next words were the key exchange and the certificate type; in TLS 1.3 they are gone from the name because they are no longer negotiated by it.
    AEADAES-128-GCMAES in Galois/Counter Mode. The mandatory-to-implement suite, and the one almost every connection uses, because chips have had AES instructions for years and GCM authenticates as it encrypts.
    Write key16 bytesDerived per direction from that side's traffic secret, with the label "key". The client and the server never share a key.
    Nonce12 bytesDerived with the label "iv", then combined with the record's sequence number so no two records under one key ever reuse a nonce.
    Tag16 bytesAppended to every record. A record whose tag does not check is not decrypted and not retried: the connection fails.
    HashSHA-256Two jobs at once: it is the hash inside HKDF for the whole key schedule, and it is the Transcript-Hash that binds every secret to the messages sent so far.
    Secret size32 bytesEvery secret in the key schedule is one hash output long, so the hash sets the size of the whole tree.
    Key exchangenot named here(EC)DHE always, agreed by the supported_groups and key_share extensions instead. TLS 1.3 removed static RSA key transport, so every connection has forward secrecy whether or not anyone asked for it.
    Authenticationnot named hereAgreed by signature_algorithms and proved by CertificateVerify. The suite has nothing to say about which certificate the server presents.
  • TLS_AES_256_GCM_SHA384

    TLS_AES_256_GCM_SHA384: field by field
    PartValueWhat it means
    Code point0x1302The two bytes that actually travel. The ClientHello offers a list of these and the ServerHello echoes back exactly one.
    PrefixTLS_Every TLS 1.3 name begins here and then goes straight to a cipher. In TLS 1.2 the next words were the key exchange and the certificate type; in TLS 1.3 they are gone from the name because they are no longer negotiated by it.
    AEADAES-256-GCMThe same mode with a 256-bit key, and the only registered suite that moves the hash up to SHA-384 as well, so its whole key schedule works in 48-byte secrets rather than 32-byte ones.
    Write key32 bytesDerived per direction from that side's traffic secret, with the label "key". The client and the server never share a key.
    Nonce12 bytesDerived with the label "iv", then combined with the record's sequence number so no two records under one key ever reuse a nonce.
    Tag16 bytesAppended to every record. A record whose tag does not check is not decrypted and not retried: the connection fails.
    HashSHA-384Two jobs at once: it is the hash inside HKDF for the whole key schedule, and it is the Transcript-Hash that binds every secret to the messages sent so far.
    Secret size48 bytesEvery secret in the key schedule is one hash output long, so the hash sets the size of the whole tree.
    Key exchangenot named here(EC)DHE always, agreed by the supported_groups and key_share extensions instead. TLS 1.3 removed static RSA key transport, so every connection has forward secrecy whether or not anyone asked for it.
    Authenticationnot named hereAgreed by signature_algorithms and proved by CertificateVerify. The suite has nothing to say about which certificate the server presents.
  • TLS_CHACHA20_POLY1305_SHA256

    TLS_CHACHA20_POLY1305_SHA256: field by field
    PartValueWhat it means
    Code point0x1303The two bytes that actually travel. The ClientHello offers a list of these and the ServerHello echoes back exactly one.
    PrefixTLS_Every TLS 1.3 name begins here and then goes straight to a cipher. In TLS 1.2 the next words were the key exchange and the certificate type; in TLS 1.3 they are gone from the name because they are no longer negotiated by it.
    AEADChaCha20-Poly1305A stream cipher with a separate authenticator. It is fast in software, which is why phones and embedded devices without AES instructions prefer it.
    Write key32 bytesDerived per direction from that side's traffic secret, with the label "key". The client and the server never share a key.
    Nonce12 bytesDerived with the label "iv", then combined with the record's sequence number so no two records under one key ever reuse a nonce.
    Tag16 bytesAppended to every record. A record whose tag does not check is not decrypted and not retried: the connection fails.
    HashSHA-256Two jobs at once: it is the hash inside HKDF for the whole key schedule, and it is the Transcript-Hash that binds every secret to the messages sent so far.
    Secret size32 bytesEvery secret in the key schedule is one hash output long, so the hash sets the size of the whole tree.
    Key exchangenot named here(EC)DHE always, agreed by the supported_groups and key_share extensions instead. TLS 1.3 removed static RSA key transport, so every connection has forward secrecy whether or not anyone asked for it.
    Authenticationnot named hereAgreed by signature_algorithms and proved by CertificateVerify. The suite has nothing to say about which certificate the server presents.
  • TLS_AES_128_CCM_SHA256

    TLS_AES_128_CCM_SHA256: field by field
    PartValueWhat it means
    Code point0x1304The two bytes that actually travel. The ClientHello offers a list of these and the ServerHello echoes back exactly one.
    PrefixTLS_Every TLS 1.3 name begins here and then goes straight to a cipher. In TLS 1.2 the next words were the key exchange and the certificate type; in TLS 1.3 they are gone from the name because they are no longer negotiated by it.
    AEADAES-128-CCMCounter with CBC-MAC. It reuses a block cipher a constrained device already has, instead of asking it for the extra multiplication GCM needs.
    Write key16 bytesDerived per direction from that side's traffic secret, with the label "key". The client and the server never share a key.
    Nonce12 bytesDerived with the label "iv", then combined with the record's sequence number so no two records under one key ever reuse a nonce.
    Tag16 bytesAppended to every record. A record whose tag does not check is not decrypted and not retried: the connection fails.
    HashSHA-256Two jobs at once: it is the hash inside HKDF for the whole key schedule, and it is the Transcript-Hash that binds every secret to the messages sent so far.
    Secret size32 bytesEvery secret in the key schedule is one hash output long, so the hash sets the size of the whole tree.
    Key exchangenot named here(EC)DHE always, agreed by the supported_groups and key_share extensions instead. TLS 1.3 removed static RSA key transport, so every connection has forward secrecy whether or not anyone asked for it.
    Authenticationnot named hereAgreed by signature_algorithms and proved by CertificateVerify. The suite has nothing to say about which certificate the server presents.
  • TLS_AES_128_CCM_8_SHA256

    TLS_AES_128_CCM_8_SHA256: field by field
    PartValueWhat it means
    Code point0x1305The two bytes that actually travel. The ClientHello offers a list of these and the ServerHello echoes back exactly one.
    PrefixTLS_Every TLS 1.3 name begins here and then goes straight to a cipher. In TLS 1.2 the next words were the key exchange and the certificate type; in TLS 1.3 they are gone from the name because they are no longer negotiated by it.
    AEADAES-128-CCM with a short tagThe same as CCM but with the tag cut to 8 bytes, for links where 8 bytes a record is worth saving. A shorter tag is a weaker forgery bound, so this is a deliberate trade rather than a free win.
    Write key16 bytesDerived per direction from that side's traffic secret, with the label "key". The client and the server never share a key.
    Nonce12 bytesDerived with the label "iv", then combined with the record's sequence number so no two records under one key ever reuse a nonce.
    Tag8 bytesAppended to every record. A record whose tag does not check is not decrypted and not retried: the connection fails.
    HashSHA-256Two jobs at once: it is the hash inside HKDF for the whole key schedule, and it is the Transcript-Hash that binds every secret to the messages sent so far.
    Secret size32 bytesEvery secret in the key schedule is one hash output long, so the hash sets the size of the whole tree.
    Key exchangenot named here(EC)DHE always, agreed by the supported_groups and key_share extensions instead. TLS 1.3 removed static RSA key transport, so every connection has forward secrecy whether or not anyone asked for it.
    Authenticationnot named hereAgreed by signature_algorithms and proved by CertificateVerify. The suite has nothing to say about which certificate the server presents.

What is pinned

Everything not in this table is computed by the code that built the page. These are the values a real handshake would take from outside, and each says why it could not be derived here.

The pinned inputs of the worked handshake
InputValueWhy it is pinned
Client random000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f32 bytes the client draws fresh for every connection. A ramp here, so it is obvious this is a teaching vector.
Server random202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3fThe server's own 32 bytes, drawn the same way.
Legacy session id404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5fA leftover from TLS 1.2 session resumption. TLS 1.3 does not use it; the client sends something and the server echoes it so middleboxes see a familiar shape.
Client key share606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7fThe client's X25519 public key. Generating one needs curve arithmetic, which this module does not do.
Server key share808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fThe server's X25519 public key, sent in its ServerHello.
ECDHE shared secreta0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfWhat both sides compute from their own private key and the other side's share. Deriving it really would need X25519; this module implements the key schedule, not the curve, so it is pinned and fed in as the schedule's one secret input.
Certificate and signature32 bytes of certificate, 64 of signaturePlaceholders. Reading a real certificate chain is the Certificate Decoder's job, not this page's.

Either side of this page has a tool of its own: the Packet Analyzer takes a real captured record apart down to its bytes, and the Certificate Decoder reads the chain this walk only carries a placeholder for. The primitives underneath, HKDF and SHA-256 included, are workbenches in the Cryptography Lab, and the connection this all rides on is built in the TCP Handshake Animator.

About this lab3 paragraphs

The handshake is one fixed vector, not a live connection. Nothing here reaches a network, and nothing here will: reading a real server's handshake would need a proxy that saw which site you were visiting, which is exactly the thing this site does not build.

What is derived is really derived. The key schedule runs on the same HKDF the Cryptography Lab uses, and the transcript hashes are taken over the actual message bytes on this page, so both Finished messages check out against the schedule beside them. The values a real handshake takes from outside (the two randoms, the key shares, the shared secret, the certificate) are pinned to obvious byte ramps and each one says so.

The negotiation and the schedule are the subject. Taking a captured record apart byte by byte is the Packet Analyzer's job, and reading a real certificate chain is the Certificate Decoder's; both are linked below. Session resumption, 0-RTT and the TLS 1.2 comparison are out of scope here.