🔐
TLS 1.3: handshake, chaves, certificados, SNI, ALPN
⏱ 18 min de leitura·+90 XP
TLS é a camada que torna o HTTPS seguro — sem ele, qualquer roteador no caminho leria seus dados. Entender o handshake revela por que HTTPS é 1 RTT mais lento que HTTP e por que Perfect Forward Secrecy mudou a equação de vigilância em massa.
TLS 1.3 handshake: 1 RTT
# TLS 1.3 handshake (1 RTT — metade do TLS 1.2):
# CLIENTE → SERVIDOR: ClientHello
# TLS version: 1.3
# Supported cipher suites: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256
# Key share: chave pública ECDHE gerada para esta sessão
# SNI: "example.com" (extensão: qual hostname o cliente quer)
# ALPN: ["h2", "http/1.1"] (qual protocolo prefere)
# Supported groups: x25519, secp256r1 (curvas elípticas)
# SERVIDOR → CLIENTE: ServerHello + dados criptografados
# ServerHello:
# TLS version: 1.3
# Cipher suite: TLS_AES_256_GCM_SHA384
# Key share: chave pública ECDHE do servidor para esta sessão
# [a partir daqui TUDO É CRIPTOGRAFADO]
# EncryptedExtensions: ALPN selecionado ("h2"), etc.
# Certificate: certificado X.509 do servidor
# CertificateVerify: prova que servidor tem a chave privada
# Finished: MAC de toda a transcrição do handshake
# CLIENTE → SERVIDOR: Finished + dados da aplicação
# Finished: MAC de toda a transcrição
# [dados da aplicação já podem ser enviados aqui!]
# Total: 1 RTT até começar a receber dados.
# TLS 1.2 com ECDHE: 2 RTTs. Com RSA: 2 RTTs.
# TLS 1.2 com session resumption: 1 RTT.
# TLS 1.3 com 0-RTT: 0 RTTs adicionais (apenas para reconexão).
# Verificar handshake TLS na prática:
# openssl s_client -connect example.com:443 -tls1_3 2>&1 | head -30
# Mostra: protocolo, cipher suite, certificado, chainDiffie-Hellman efêmero e Perfect Forward Secrecy
# ECDHE: Elliptic Curve Diffie-Hellman Ephemeral
# Matemática: gerar segredo compartilhado sem transmiti-lo
# Conceito DH simples (com números reais, não seguros):
g = 2 # gerador público
p = 23 # primo público
# Servidor e cliente geram segredos privados efêmeros:
privado_servidor = 6 # gerado aleatoriamente por sessão
privado_cliente = 15 # gerado aleatoriamente por sessão
# Trocam chaves públicas:
pub_servidor = pow(g, privado_servidor, p) # 2^6 mod 23 = 18
pub_cliente = pow(g, privado_cliente, p) # 2^15 mod 23 = 19
# Calculam o segredo compartilhado (sem transmiti-lo!):
segredo_no_servidor = pow(pub_cliente, privado_servidor, p) # 19^6 mod 23 = 2
segredo_no_cliente = pow(pub_servidor, privado_cliente, p) # 18^15 mod 23 = 2
assert segredo_no_servidor == segredo_no_cliente # sempre iguais por matemática
print(f"Segredo compartilhado: {segredo_no_servidor}") # nunca trafegou pela rede!
# Em produção: curvas elípticas (x25519) em vez de DH inteiros
# x25519: chaves de 256 bits, ~128 bits de segurança, muito mais rápido que RSA-2048
# Por que "efêmero" é chave para PFS:
# Chaves privadas ECDHE são geradas POR SESSÃO e descartadas após o handshake.
# Se atacante grava tráfego hoje e obtém chave PRIVADA DO SERVIDOR no futuro:
# - TLS 1.2 RSA: consegue decriptar (chave privada decripta a chave de sessão)
# - TLS 1.3 ECDHE: NÃO consegue (chave efêmera já foi descartada)
# Derivação das chaves de sessão (HKDF):
# master_secret = ECDHE_shared_secret
# client_key = HKDF-Expand(master_secret, "client write key", key_length)
# server_key = HKDF-Expand(master_secret, "server write key", key_length)
# Cada direção usa chave diferente — comprometer uma não compromete a outraCertificados X.509 e PKI
import ssl
import socket
import datetime
def inspecionar_certificado(host: str, port: int = 443) -> dict:
"""Inspeciona certificado TLS de um host."""
context = ssl.create_default_context()
with socket.create_connection((host, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
cert = ssock.getpeercert()
cipher = ssock.cipher()
version = ssock.version()
# Extrair campos relevantes:
subject = dict(x[0] for x in cert.get("subject", []))
issuer = dict(x[0] for x in cert.get("issuer", []))
san = [v for (t, v) in cert.get("subjectAltName", []) if t == "DNS"]
not_after = datetime.datetime.strptime(cert["notAfter"], "%b %d %H:%M:%S %Y %Z")
dias_restantes = (not_after - datetime.datetime.utcnow()).days
return {
"cn": subject.get("commonName"),
"san": san,
"issuer": issuer.get("organizationName"),
"not_after": not_after.isoformat(),
"dias_restantes": dias_restantes,
"tls_version": version,
"cipher": cipher[0],
}
info = inspecionar_certificado("example.com")
print(f"CN: {info['cn']}")
print(f"SANs: {info['san']}")
print(f"Emissor: {info['issuer']}")
print(f"Expira em: {info['dias_restantes']} dias")
print(f"TLS: {info['tls_version']}, Cipher: {info['cipher']}")
# Estrutura de um certificado X.509:
# - Subject: para quem foi emitido (CN=example.com)
# - SAN (Subject Alt Names): domínios cobertos (prefere SAN ao CN)
# - Issuer: quem assinou (Let's Encrypt, DigiCert, etc.)
# - Not Before / Not After: validade
# - Serial Number: único por CA
# - Public Key: RSA 2048+ ou EC P-256/P-384
# - Signature Algorithm: SHA256withRSAEncryption ou similar
# - Extensions: Key Usage, Extended Key Usage, OCSP URL, CRL URLSNI, ALPN, OCSP Stapling e Certificate Transparency
| Extensão TLS | Função | Enviada por |
|---|---|---|
| SNI | Informa hostname para multi-tenant TLS | Cliente (ClientHello) |
| ALPN | Negocia protocolo de aplicação (h2, http/1.1) | Cliente + Servidor |
| OCSP Stapling | Prova de não-revogação incluída pelo servidor | Servidor |
| Session Ticket | Resumption rápida de sessão (TLS 1.2) | Servidor |
| ECH | Criptografa o SNI (privacidade de hostname) | Cliente |
| Certificate Status | Solicita status OCSP ao servidor | Cliente |
# SNI: permite múltiplos domínios HTTPS no mesmo IP # Sem SNI: servidor teria que usar IP diferente por domínio (IPv4 escasso) # Com SNI: 1 IP → 10.000 domínios HTTPS diferentes # Verificar SNI em uso: # openssl s_client -servername example.com -connect example.com:443 2>/dev/null | grep "subject=" # ALPN: negocia protocolo de aplicação no handshake TLS # ClientHello extension: ["h2", "http/1.1"] (order = preference) # ServerHello extension: "h2" (servidor aceita HTTP/2) # Sem ALPN: servidor responde HTTP/1.1 mesmo que suporte HTTP/2 # OCSP Stapling: evita round-trip extra de verificação de revogação # Sem stapling: cliente faz request OCSP → CA (latência extra) # Com stapling: servidor inclui resposta OCSP pré-assinada pela CA no handshake # Nginx: ssl_stapling on; ssl_stapling_verify on; # Apache: SSLUseStapling on # Certificate Transparency (CT): # Toda CA deve registrar certificados emitidos em CT logs públicos # Browser verifica que o cert está em ≥2 CT logs antes de aceitar # Permite detectar: cert fraudulento emitido sem autorização do dono do domínio # Exemplo real: Symantec emitiu certs inválidos → Chrome exigiu CT → detectado # Let's Encrypt: CA gratuita e automatizada # ACME protocol: valida domínio e emite cert em ~30s # Certbot ou acme.sh automatizam renovação (certs expiram em 90 dias) # Wildcard certs: *.example.com via DNS-01 challenge (cria record TXT) # Verificar chain completa: # openssl s_client -connect example.com:443 -showcerts 2>/dev/null | openssl x509 -text -noout | grep -A2 "Issuer"
✅
Modelo mental: TLS 1.3 = 1 RTT de handshake (vs 2 do TLS 1.2). ECDHE garante Perfect Forward Secrecy — sessões passadas não podem ser decriptadas mesmo com a chave privada do servidor. SNI permite HTTPS multi-tenant no mesmo IP. ALPN negocia protocolo (h2/http1.1) no handshake. Certificados X.509 = confiança delegada: CA raiz → CA intermediária → seu domínio. Let's Encrypt automatiza emissão e renovação gratuitamente.
💡
Próximo: DNS — o sistema que converte "example.com" em IP e as vulnerabilidades que existem nessa tradução.
🧩
Quiz rápido
3 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito
Continue lendo