TLS 1.2 Handshake

On this page

When the server sends a certificate and an ephemeral ECDHE public key, why can't the client simply verify the certificate and start encrypting? The certificate binds to a long-term identity, the handshake signature binds the ephemeral parameters to that identity, and the Finished message confirms that both parties have a consistent understanding of the negotiation history and keys. These three checks work together to form an authenticated secure channel.

What to verifyCorresponding mechanism
Is the peer the expected service?Certificate chain, service identity, and usage checks
Are the ephemeral key parameters not spoofed?ECDHE ServerKeyExchange signature
Do both parties derive the same shared secret?Key derivation and Finished verification
Are subsequent records not tampered with?Record protection specified by AEAD or the cipher suite
Why does reconnection reduce round trips?Resuming previous session state while still verifying the new Finished message

This article focuses on the full server-authenticated handshake using TLS 1.2, ECDHE, and AEAD, and also explains historical RSA key exchange and resumption differences. Not all historical options in TLS 1.2 are suitable for enabling today; modern deployment choices should consider security best practices and client compatibility. RFC 9325: TLS Security Best Current Practice

As of September 2026, the updated TLS 1.3 specification RFC 9846 has been published, replacing RFC 8446 and several older related documents, and also imposing new requirements on TLS 1.2 implementations. This article cites RFC 5246, etc., to accurately explain TLS 1.2 mechanisms, without treating historical options as current deployment baselines. RFC 9846

Distinguish TCP, TLS Record, and Handshake Messages

TLS typically runs over TCP. TCP segments, TLS records, and Handshake messages are three distinct boundaries. A single handshake message can span multiple records, and a single record can contain multiple handshake messages; capturing a TCP packet does not mean you see a complete Certificate message.

The fixed header of a TLS 1.2 record is 5 bytes, indicating content type, version, and length. Handshake messages have an additional 1-byte type and 3-byte length fields. The handshake transcript is accumulated according to the protocol's handshake message encoding, not by hashing all bytes of Ethernet frames or TCP segments directly. RFC 5246: Record Layer and Handshake

Four Flights: Approximately Two TLS Round Trips

TLS 1.2 Full ECDHE Handshake: Common Path Without Client Certificate Client Server ClientHello: Version, random, cipher suites, and extensions ServerHello, Certificate, ServerKeyExchange, ServerHelloDone ClientKeyExchange, CCS, Finished CCS, Finished Client verifies server certificate identity and ephemeral parameter signature Both parties independently compute the ECDH shared secret and derive keys using the same rules CCS is an independent protocol message that switches record protection; Finished verifies the handshake transcript. The diagram does not include TCP connection establishment or optional resumption.

The client sends ClientHello first, and the server responds with the selected parameters, certificate, and signed ephemeral parameters; after verification, the client sends its own ephemeral public key, switches to protected mode, and sends Finished; the server verifies and switches, then returns Finished. A standard full handshake takes approximately two RTTs, excluding TCP connection establishment, DNS, retries, and certificate processing, and does not have a fixed duration regardless of extensions.

In the ECDHE path, the server computes the shared secret; it does not "decrypt the pre_master_secret sent by the client using the RSA private key." The latter belongs to another historical key exchange method. Optional client certificate authentication would add messages such as CertificateRequest, client Certificate, and CertificateVerify, which are covered separately in the mTLS section.

ClientHello/ServerHello: Selection Requires Common Context

ClientHello includes version, 32-byte random, cipher suite list, and extensions; ServerHello selects the available version, cipher suite, and corresponding extension results. The historical Random format's time field should not be treated as a trusted clock or client system fingerprint; modern implementations should not rely on it for business time semantics.

SNI helps the server select the virtual host and certificate, but it cannot replace service identity verification for the client; ALPN negotiates the application protocol, such as h2 or http/1.1. Even if TLS succeeds, the application layer must adhere to the selected protocol and not continue sending content inconsistent with ALPN. RFC 6066: SNI, RFC 7301: ALPN

supported_groups, signature algorithms, and cipher suites are different constraints: supporting a curve does not mean accepting any certificate algorithm. Historically, TLS 1.2 could negotiate compression; modern security configurations should disable record compression. Do not claim that modern requirements were never defined in RFC 5246 for non-null compression.

Cipher Suite Name Breakdown: RSA Can Be Used Only for Signing

Take TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 as an example:

PartRole in this ExampleShould Not Be Misinterpreted As
ECDHEEphemeral Elliptic Curve DH key exchangeThe certificate must also use the same EC curve
RSASignature algorithm used for server authenticationRSA encrypts all application data or PMS
AES_128_GCM128-bit AES AEAD record protectionAdds an additional SHA256 record MAC
SHA256TLS 1.2 PRF hash specified by the suiteAll TLS 1.2 suites only allow SHA256

For example, the AES-256-GCM-SHA384 suite uses the SHA384 PRF. Cipher suites are registered as complete combinations; you cannot arbitrarily concatenate four strings to create a valid suite. The hash used for handshake signing is also constrained by the signature algorithm negotiation and does not have to equal the suffix of the suite name. RFC 5289: ECC Cipher Suites for TLS

ECDHE: Sign Ephemeral Parameters, Then Compute Shared Secret

The client's ephemeral private key is a, and the public key is aG. The server's ephemeral private key is b, and the public key is bG. After correct parameter checks and authentication, both parties compute a(bG) and b(aG) respectively, obtaining the same shared secret, without directly transmitting a, b, or the shared secret itself over the wire.

The server signs the ClientHello.random, ServerHello.random, and specified ECDHE parameter encoding using the private key corresponding to the certificate. The client must check the certificate chain and expected name, as well as verify this signature; accepting any arbitrary ECDHE public key leaves room for a man-in-the-middle to replace parameters. RFC 8422: TLS ECC Cipher Suite

Forward secrecy comes from the ephemeral key exchange and proper management of secrets: if long-term signing keys are leaked in the future, it should not be possible to recover destroyed ephemeral DH secrets from past packet captures. It does not guarantee protection of in-memory sessions if the running endpoint is compromised, nor does it automatically cover leaks of session ticket protection keys.

Historical RSA key exchange involves the client generating the PMS, encrypting it with the server's RSA public key, and the server decrypting it; if recorded traffic is saved and the long-term decryption private key is leaked, historical secrets are affected. It is included here to understand protocol differences and is not recommended for new deployments. RFC 5246: RSA and DH Computation

master_secret: Traditional Derivation and EMS

To facilitate comparison with old packet capture data, this section retains the variable names from RFC 5246. RFC 9846 renames master secret to main secret, premaster secret to preliminary secret, and EMS to Extended Main Secret; the PRF labels on the wire remain compatible, and literal labels in code should not be renamed accordingly. RFC 9846 Appendix D

The traditional TLS 1.2 derivation is:

master_secret = PRF(PMS, "master secret", client_random || server_random)[0:48]

After EMS negotiation succeeds, use the "extended master secret" label and the specified session_hash to bind the master secret to the current handshake context. Do not list only the old formula after discussing EMS protection and claim it applies to all handshakes. RFC 7627: Extended Master Secret

Next, generate the material needed for record protection:

key_block = PRF(master_secret, "key expansion", server_random || client_random)

The order of the two random values here is reversed compared to the traditional master_secret formula. The PRF label, input order, hash, and length all contribute to the result; you cannot arbitrarily concatenate just because both parties have the same PMS. The client write key is used for the client-to-server direction, and the server write key is used for the reverse direction.

AES-128-GCM Example: Directional Derivation of 40 B key_block master_secret: 48 B Derived from PMS and negotiated PRF/EMS rules client_write_key 16 B server_write_key 16 B client IV 4 B server IV 4 B This GCM suite has no independent record MAC key; the fixed IV and explicit per-record nonce together construct the nonce, which must not be reused.

The AES-128-GCM example requires two 16-byte write keys and two 4-byte fixed IVs, totaling 40 bytes; the independent write_MAC_key length is zero. Old articles fixing "six session keys" mistakenly treat AEAD as requiring additional record HMAC. The GCM nonce also includes an 8-byte explicit part per record; the fixed IV does not mean the exact same nonce can be reused for every record. RFC 5288: TLS GCM

CCS and Finished: Switching Protection and Confirming History

TLS 1.2 ChangeCipherSpec is an independent protocol message notifying the peer to use the negotiated protection state subsequently. Finished is a handshake message sent using new record protection; its verify_data is a PRF output based on master_secret, direction label, and transcript hash; in this example, it is 12 bytes. RFC 5246: Finished

Finished is not the server performing another digital signature with its certificate private key. Verifying it requires the shared secret and binds the negotiation history; the client's Finished transcript does not include itself, while the server's Finished transcript includes the client's Finished message already sent. CCS is not included in this transcript as a Handshake message.

Only after verifying the server's Finished message can you be confident that this phase is complete according to the standard full handshake flow. Some optimizations allow sending early under specific conditions; do not use them to arbitrarily rewrite the baseline flow. TLS alerts and TCP disconnects are also different; protocol closure semantics and whether the application has completed its own messages need to be handled separately.

Which secrets change when one input changes?

In legacy mode, changing another handshake message preserves the main and record keys but changes Finished. Under EMS the change propagates through session_hash. Colors compare actual bytes rather than scripted highlights.

Preparing the visual
Which secrets change when one input changes?

Derivation is a directed dependency graph. Legacy main secret lacks a full transcript binding; EMS binds session_hash, while Finished binds its covered history.

Record Overhead: Application Bytes Do Not Equal Wire Bytes

In this TLS 1.2 AES-GCM record example, with 1000 bytes of application plaintext, there are additionally 8 bytes of explicit nonce, 16 bytes of tag, and 5 bytes of record header, totaling 1029 bytes at the TLS layer; TCP/IP and link headers are not yet included. Different AEAD/CBC suites have different overheads; do not apply the 29-byte overhead to TLS 1.3.

Record sequence numbers and other information participate in authentication; the receiver processes them according to connection state. You cannot arbitrarily swap or modify records and still obtain the same valid application stream. If the application itself needs message-level deduplication and transaction acknowledgment, TLS does not provide these semantics for it.

Resumption: Reusing Session Secrets, Deriving New Connection State

The Session ID method involves the server looking up the cached session using the ID; the ticket method allows the server to hand over the session state to the client in a recoverable, confidential, and integrity-protected form. RFC 5077 negotiates via extension and issues tickets via the NewSessionTicket handshake message; you cannot claim that the initial ticket is just a blob stuffed into the ServerHello extension. RFC 5077: Session Tickets

Upon successful resumption, a simplified handshake is used. Typically, the server first sends ServerHello, CCS, and Finished, and the client responds with CCS and Finished, resulting in approximately one RTT of TLS interaction. Both parties derive connection keys using new random values, rather than directly reusing all previous record keys/nonces/sequence numbers.

Resumption may be rejected and fall back to a full handshake, depending on ticket/cache lifetime, keys, parameters, and policies. The ticket mechanism reduces per-session server storage but does not mean the server is completely stateless: ticket protection keys, rotation, policies, and resources still require management.

A publicly observable encrypted ticket itself leaking is not the same event as the server protection key capable of decrypting the ticket leaking. The latter may expose encapsulated session secrets and related historical traffic; theft of the client-side stored resumption secret also has impacts. Confidentiality and lifecycle should be analyzed across the entire resumption mechanism; do not generalize with "ticket leak definitely decrypts all historical sessions."

Local PRF and Encoding Budget

import hashlib
import hmac

def prf(secret,label,seed,length):
    seed = label+seed
    A = seed
    out = b''
    while len(out)<length:
        A = hmac.digest(secret,A,'sha256')
        out += hmac.digest(secret,A+seed,'sha256')
    return out[:length]

# Fixed public teaching material, not production keys.
pms = bytes(range(32))
client_random,server_random = bytes(range(32)),bytes(range(32,64))
master = prf(pms,b'master secret',client_random+server_random,48)
assert len(master)==48
assert master != prf(pms,b'master secret',server_random+client_random,48)
transcript_hash = hashlib.sha256(b'teaching handshake transcript').digest()
ems = prf(pms,b'extended master secret',transcript_hash,48)
assert ems != master
block = prf(master,b'key expansion',server_random+client_random,40)
client_key,server_key,civ,siv = block[:16],block[16:32],block[32:36],block[36:40]
assert tuple(map(len,(client_key,server_key,civ,siv)))==(16,16,4,4)
assert client_key != server_key
finished = prf(master,b'client finished',transcript_hash,12)
changed = hashlib.sha256(b'changed handshake transcript').digest()
assert finished != prf(master,b'client finished',changed,12)
assert finished != prf(master,b'server finished',transcript_hash,12)
assert 1000+8+16+5==1029
print('TLS 1.2 PRF input order, EMS separation, directional keys, Finished, and GCM overhead passed')

This code demonstrates SHA256 P_hash composition and dependencies; the transcript consists of teaching placeholder bytes, not a complete TLS handshake; it does not generate real certificates, handshake with external services, or modify TLS configurations.

Continue Reading