>_ josetilabs.com
← blog

17 out 2026 · segurança, tls, linux · 6 min

O que acontece no handshake TLS

O handshake TLS 1.3 completo leu 778 bytes do servidor. Retomando a sessao com o ticket que o servidor tinha entregado, a mesma conexao leu 241 bytes. Nenhuma dessas linhas trocadas vai para o log do seu servidor, e mesmo assim e o que decide se a conexao esta cifrada ou e lixo puro.

A primeira mensagem e a chave nova

O ClientHello tem 306 bytes e carrega duas coisas que importam: a versao maxima que o cliente aceita e uma chave publica X25519 gerada na hora. Nao e a chave do servidor, nem nada guardado em disco. E uma chave descartavel, criada para aquela conexao e esquecida depois.

O openssl s_client mostra o que foi enviado quando voce pede o estado da conversa:

bash
openssl s_client -connect exemplo.com:443 -servername exemplo.com -state 2>&1 | head -20

E o servidor responde com o ServerHello, que escolhe a versao final e devolve a chave publica dele. As duas metades juntas produzem um segredo que nenhum dos dois conseguira sozinho calcular. Daqui em diante a conversa e cifrada com ele.

Sequencia do handshake TLS 1.3: ClientHello, ServerHello com chave X25519, Extensions cifradas, certificado P-256 assinado com ECDSA, Finished, e ticket de sessao entregue pelo servidor
Seis mensagens, uma ida e volta. A partir da terceira, o que trafega ja esta cifrado com o segredo que as duas chaves montaram.

O certificado e o que ele prova

Depois do ServerHello vem o certificado. No que medi, a chave do servidor e P-256 e a assinatura e ECDSA com SHA-256, num certificado de 607 bytes:

bash
openssl s_client -connect exemplo.com:443 -servername exemplo.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates

O certificado nao viaja cifrado, e nao viaja nem deveria. Ele e a parte da conversa que o cliente precisa ler em claro para conferir quem esta do outro lado. O que ele garante e o nome e a assinatura, nao o conteudo da pagina. Para o que um certificado resolve e o que nao resolve, o caso dos doze certificados falsos mostra o lado fraco.

Sozinho ele nao resolve nada sem a cadeia. Cada certificado aponta para quem o emitiu, e a corrente precisa terminar numa raiz em que o sistema confia. Acima dela vem o pin que impede um intermediario de emitir certificado para o seu nome.

O finished e o ticket de sessao

O handshake so esta completo quando os dois lados mandam um Finished, assinado com o segredo derivado das chaves. Isso fecha o ataque de intermediario: quem estiver no meio do caminho ve a chave publica, mas nao consegue produzir um Finished valido.

E entao vem a mensagem mais subestimada, que chega depois do handshake pronto: o ticket de sessao, uma chave que resume tudo o que foi negociado.

Medir o handshake inteiro na sua maquina

O script abaixo sobe um servidor TLS local com certificado autoassinado, conecta nele com o s_client e imprime as seis mensagens na ordem. Nada sai para a internet.

bash
#!/usr/bin/env bash
# handshake: sobe um TLS 1.3 local, mede o completo e o retomado.
set -u

if ! command -v openssl > /dev/null 2>&1; then
  echo "falta o openssl: instale com apt install openssl e rode de novo"
  exit 0
fi

DIR="$(mktemp -d)"
PORTA=14433

# certificado autoassinado, P-256, valido por 2 dias
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
  -nodes -keyout "$DIR/chave.pem" -out "$DIR/cert.pem" -days 2 \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost" 2>/dev/null

echo "== certificado =="
openssl x509 -in "$DIR/cert.pem" -noout -subject -dates

openssl s_server -accept $PORTA -cert "$DIR/cert.pem" -key "$DIR/chave.pem" \
  -www -tls1_3 > /dev/null 2>&1 &
SRV=$!
sleep 2

pedir() { printf 'GET / HTTP/1.0\r\n\r\n'; sleep 2; }

echo
echo "== handshake completo, com os estados =="
pedir | openssl s_client -connect 127.0.0.1:$PORTA -servername localhost \
  -sess_out "$DIR/sess.pem" -state 2>&1 | grep -E "SSL_connect|Server Temp Key|Cipher is"

echo
echo "== handshake retomado, com o ticket =="
pedir | openssl s_client -connect 127.0.0.1:$PORTA -servername localhost \
  -sess_in "$DIR/sess.pem" 2>&1 | grep -E "Reused|New,|handshake has read"

kill $SRV 2>/dev/null
rm -rf "$DIR"

Como verificar

O que interessa sao duas linhas, a do New, e a do Reused,. Na primeira execucao voce ve New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384, na segunda Reused, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384. O Reused diz que o ticket funcionou.

O certificado autoassinado faz a verificacao falhar, e tudo bem: aparecera Verification error: self-signed certificate, o aviso correto para um certificado que ninguem assinou. Com um site publico o mesmo comando mostra Verification: OK.

O tamanho do handshake

Aqui esta o numero que eu mais gosto de olhar. Um handshake completo le 778 bytes do servidor; um retomado le 241. A queda e de 69 por cento, e ela vem de uma coisa so: o certificado tem 607 bytes e nao precisa ser reenviado.

Comparacao de bytes lidos do servidor em dois handshakes TLS 1.3: o handshake completo le 778 bytes e o retomado com ticket le 241 bytes, uma queda de 69 por cento
778 bytes na primeira conversa, 241 na segunda. O certificado de 607 bytes e a diferenca entre as duas barras.

Em um site publico com cadeia de 4 certificados a conta muda: o handshake completo leu 3.727 bytes em TLS 1.3 e 3.878 em TLS 1.2. A cadeia pesa mais que a diferenca de versao, o que explica por que certificados de 6 dias fazem sentido: menos cadeia para carregar.

Se der errado

connect: Connection refused na hora de conectar. O s_server nao subiu. Verifique se a porta 14433 ja estava ocupada com ss -ltnp | grep 14433 e troque por outra no script.

falta o openssl. O script avisa e sai sem tentar rodar o resto. Instale o pacote e rode de novo.

Reused nunca aparece. O servidor pode estar sem -tls1_3, ou a sessao expirou entre as execucoes. Rode as duas linhas em sequencia, sem pausa longa.

As diferencas entre 1.2 e 1.3

TLS 1.2 TLS 1.3
mensagens 2 idas e voltas 1 ida e volta
assinatura do certificado sobre o handshake inteiro so sobre o certificado
cifra comum ECDHE-ECDSA-CHACHA20-POLY1305 TLS_AES_256_GCM_SHA384
bytes lidos no exemplo publico 3.878 3.727

O ganho do 1.3 nao esta so no tempo. Tirar a assinatura do handshake inteiro faz a assinatura do servidor valer para varias sessoes, o que deixa o handshake mais barato.

FAQ

O handshake e a mesma coisa que a conexao? Nao. Depois do Finished a conexao esta estabelecida, e a mesma chave continua cifrando os dados. O handshake acontece uma vez, a conexao dura.

Por que a chave publica vai em claro no ClientHello? Porque nao ha segredo nenhum ainda. Quem tentar ler o ClientHello descobre a chave publica de uma sessao que ele mesmo impede de decifrar.

TLS 1.3 tira o certificado? Nao. Ele sai do caminho critico e chega depois da chave estar estabelecida, mas continua sendo a unica prova de quem e o servidor. Para o que ele prova e o que nao prova, veja os certificados falsos e a lista CAA.

Vale a pena forcar TLS 1.3? Vale, e o custo e pequeno: a propria sessao retomada ja e o mecanismo que reduz o trabalho por conexao. O que nao vale e prometer 1.3 e deixar o cliente antigo sem caminho.

Fontes

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.