🧠FFVAcademy
🔐

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, chain

Diffie-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 outra

Certificados 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 URL

SNI, ALPN, OCSP Stapling e Certificate Transparency

Extensão TLSFunçãoEnviada por
SNIInforma hostname para multi-tenant TLSCliente (ClientHello)
ALPNNegocia protocolo de aplicação (h2, http/1.1)Cliente + Servidor
OCSP StaplingProva de não-revogação incluída pelo servidorServidor
Session TicketResumption rápida de sessão (TLS 1.2)Servidor
ECHCriptografa o SNI (privacidade de hostname)Cliente
Certificate StatusSolicita status OCSP ao servidorCliente
# 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