In TLS 1.2, how does resumption by SessionID session_id differ from an RFC 5077 SessionTicket in where state lives?
answer
- a handle versus the state itself
- who is holding the secret
- a cache needs affinity to be hit
- sealed state travels with the client
- any front end with the key resumes
basics
~20 sA SessionID session_id is only a handle: the server keeps the session state in its own cache. An RFC 5077 SessionTicket is the state itself, sealed under a key the server side holds and stored by the client.
solid answer
~40 sWith `SessionID session_id`, the server caches the master secret, the negotiated suite and the peer identity under an opaque identifier and hands the identifier out; resumption only works if the second connection reaches a server that has that cache entry. With an RFC 5077 `SessionTicket`, the server encrypts and authenticates the same state under a protection key it never shares with the client, delivers it in a `NewSessionTicket` message, and keeps nothing per session; the client stores the blob and presents it in the `SessionTicket` extension. The consequence is operational: identifier-based resumption needs cache affinity or a shared cache across a fleet, ticket-based resumption needs every front end that should resume to be able to open the ticket. The ticket's lifetime is also the window in which that sealed secret is worth stealing.
code
pseudocode · 13 lineson ClientHello:
if offer contains session_id and cache holds session_id:
state = cache lookup session_id
resume with state // echo the same session_id
else if offer contains a ticket:
state = open ticket with the server-side protection key
if state is authentic and not past its lifetime:
resume with state // echo the SessionTicket extension
else:
run a full handshake
issue a new ticket before Finishedgo deeper
Remember that one mechanism hands out a number and keeps the secret, and the other hands out the secret in a sealed form the client cannot open.
Be able to say what the stored state contains, which message delivers a ticket, and how each side signals acceptance on the next connection.
Reason about resumption rates across a set of front ends: what affinity buys, what sharing the ability to open a ticket implies, and how to tell the difference from handshake counts.
Treat ticket lifetime as a policy dial: it sets both the resumption rate and how long inherited authentication and a sealed secret stay useful to an attacker.
## Two answers to one question Both mechanisms answer "where does the secret from the earlier handshake live between connections?", and they answer it in opposite directions. - **`SessionID session_id`** - the server keeps the state and issues a *handle*. The identifier is opaque and carries nothing: strip it of the cache behind it and it is a meaningless number. - **RFC 5077 `SessionTicket`** - the server serialises the state, encrypts and authenticates it under a protection key that never leaves the server side, and gives the ciphertext to the client. The client holds the state without being able to read it. ## What the state actually is In TLS 1.2 the resumable state is the master secret, the negotiated cipher suite, and the peer identity established during the original handshake. That is the whole reason both mechanisms need protection: whoever holds it in the clear can derive the record keys of every connection resumed from it. ## How each one runs on the wire 1. **Identifier form.** The original `ServerHello` carries a new `SessionID session_id`. On a later `ClientHello` the client offers it; if the server finds the entry, it echoes the identifier and both sides run the abbreviated handshake. 2. **Ticket form.** The client advertises support with an empty `SessionTicket` extension. After the key exchange, and before its `ChangeCipherSpec` and `Finished`, the server sends a `NewSessionTicket` handshake message carrying the sealed blob. On a later connection the client puts that blob in its `SessionTicket` extension; the server signals acceptance by including the extension in its own `ServerHello` and goes straight to `Finished`. ## The comparison that matters in production | | SessionID session_id | RFC 5077 SessionTicket | |---|---|---| | Who stores the secret | the server, in a cache | the client, sealed | | Server memory per session | proportional to sessions | none | | Works across a fleet | only with affinity or a shared cache | for any front end holding the protection key | | What bounds reuse | the cache entry's lifetime | the lifetime the server encoded, checked on presentation | | What an attacker wants | read access to the cache | the ticket-protection key | ## Why this decides resumption rates Put thousands of turnstile readers in front of a set of front ends with no shared cache and no affinity. Every reader's second connection has a good chance of landing on a front end that has never heard of its identifier, so it silently runs a full handshake - resumption appears "enabled" and measurably does nothing. Tickets invert the failure: because the state travels with the client, any front end that can open the blob can resume, and a front end that cannot simply falls back to a full handshake. That inversion is the point of RFC 5077, and it is also its cost. The capability to resume is now the capability to decrypt tickets, so it follows the protection key around rather than following the cache. ## Lifetime and what it bounds A ticket carries a lifetime the server chose and the server checks on presentation; the client also treats it as an expiry hint for its own store. Two things are bounded by it: - how long inherited authentication stays valid without a fresh certificate check; - how long the sealed secret inside the blob remains useful to anyone who obtains both the blob and the ability to open it. Stateless tickets therefore do not remove the server's obligations; they move them from a cache to a key and a clock. ## Carrying the distinction into TLS 1.3 TLS 1.3 kept only the ticket direction. There is a `legacy_session_id_echo` field in the `ServerHello`, but it exists so that the handshake looks familiar to middleboxes, not so that a session can be resumed by identifier. Resumption in TLS 1.3 means a pre-shared key named by a ticket delivered in `NewSessionTicket` - and a server may still implement that ticket as a database key rather than as sealed state, which is exactly the identifier trade-off reappearing inside an object that looks like a ticket from the outside.
- Why does a fleet often measure near-zero resumption with SessionID session_id?Because the cache is per server. Without affinity or a shared cache, a returning connection usually lands on a front end with no entry for that identifier, so it runs a full handshake. Resumption looks configured and delivers nothing, which is only visible in handshake counts, not in errors.
- What can a client do with a SessionTicket it cannot decrypt?Only store it and present it again. To the client it is opaque bytes; it tracks the lifetime the server stated so it does not offer an expired blob. It cannot read, validate or repair the state inside, and it must be ready for the server to refuse it anyway.
- Does a stateless ticket mean the server keeps no state at all?No. It keeps no per-session record, but it must keep the protection key that opens tickets, and it enforces the lifetime it encoded. Statelessness moved the obligation from a cache to a key and a clock rather than removing it.
A cloakroom that keeps your coat and hands you a numbered tag is the session identifier: lose the cloakroom and the tag is worthless. Handing you your coat inside a bag only the cloakroom can unseal is the ticket: any branch with the right key can take it back.
saying these in an interview costs you the question
- Thinks the client can read or edit the ticket contents
- Says identifier-based resumption works across a fleet with no shared state
- Believes a ticket cannot expire because the server stores nothing
- Treats a resumption ticket as an application login credential
- Claims tickets were introduced by TLS 1.3