Pular para conteúdo

Protocolos de Troca de Chaves (Diffie-Hellman)

Protocolos de troca de chaves permitem que duas partes estabeleçam um segredo compartilhado em um canal inseguro sem terem trocado nenhuma informação secreta anteriormente. Isso é fundamental para comunicações seguras na internet.

Diffie-Hellman Key Exchange

O Diffie-Hellman (DH) foi um dos primeiros protocolos práticos de troca de chaves, publicado em 1976 por Whitfield Diffie e Martin Hellman.

Fundamento Matemático

Dado: - Um primo grande $p$ (tipicamente 2048 bits ou mais) - Um gerador $g$ de $\mathbb{Z}_p^*$ (tipicamente 2 ou 3)

Passos da Alice: 1. Escolha uma chave privada $a$ aleatoriamente de $[2, p-2]$ 2. Calcule valor público $A = g^a \mod p$ 3. Envie $A$ para o Bob pelo canal inseguro

Passos do Bob: 1. Escolha uma chave privada $b$ aleatoriamente de $[2, p-2]$ 2. Calcule valor público $B = g^b \mod p$ 3. Envie $B$ para a Alice pelo canal inseguro

Cálculo do segredo compartilhado: - Alice calcula: $s = B^a \mod p = (g^b)^a \mod p = g^{ab} \mod p$ - Bob calcula: $s = A^b \mod p = (g^a)^b \mod p = g^{ab} \mod p$

Ambos chegam ao mesmo segredo compartilhado $s$, que pode ser usado como uma chave de criptografia simétrica.

Análise de Segurança

A segurança do Diffie-Hellman depende do problema do logaritmo discreto: Dado $(p, g, y)$ onde $y = g^x \mod p$, encontrar $x$ é computacionalmente inviável para valores grandes de $p$.

Complexidade do ataque: - Força bruta: $O(\sqrt{p})$ usando Baby-step Giant-step ou algoritmo rho de Pollard - Para primos de 2048 bits, isso requer aproximadamente $2^{1024}$ operações - computacionalmente inviável com tecnologia atual

Implementação em Java

import java.math.BigInteger;
import java.security.SecureRandom;

public class TrocaDeChaveDiffieHellman {

    // Parâmetros padrão do RFC 7919 (modp1)
    private static final BigInteger P = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA2" +
            "2168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A087" +
            "98E3404DDEF9519B3CD3A431B302B026038C13ACFFFFFFFBCFC6EE1BBC7FF59" +
            "B88BB9BCB09CBECECDD4EBA3EDBD4547B9280CD73CDA250F164C406CBBA290" +
            "5F47E52BDF9D8EFF2A30E1F7BB4CC89B68F15D42A58ED30ABDDA62FFCF4F90" +
            "3EBC965FFC9BFD859AC479CA81E99A3ED9B6D1FE609731A", 16);

    private static final BigInteger G = new BigInteger("2");

    /**
     * Gera um par de chaves Diffie-Hellman.
     */
    public static KeyPair gerarParDeChaves() {
        SecureRandom random = new SecureRandom();

        // Gerar chave privada (aleatória no intervalo [3, P-2])
        byte[] bytesChavePrivada = new byte[128];
        do {
            random.nextBytes(bytesChavePrivada);
        } while (new BigInteger(1, bytesChavePrivada).compareTo(P.subtract(BigInteger.ONE)) >= 0 || 
                 new BigInteger(1, bytesChavePrivada).compareTo(BigInteger.valueOf(2)) <= 0);

        BigInteger x = new BigInteger(1, bytesChavePrivada);

        // Calcular chave pública: Y = g^x mod p
        BigInteger y = G.modPow(x, P);

        return new KeyPair(y, x);
    }

    /**
     * Calcula segredo compartilhado a partir da chave pública do parceiro.
     */
    public static byte[] calcularSegredoCompartilhado(BigInteger chavePublicaDoParceiro, BigInteger minhaChavePrivada) {
        // Segredo compartilhado: s = Y^x mod p
        BigInteger segredoCompartilhado = chavePublicaDoParceiro.modPow(minhaChavePrivada, P);

        // Hashar o resultado para obter uma chave utilizável (função de derivação de chaves)
        try {
            java.security.MessageDigest digest = java.security.MessageDigest.getInstance("SHA-256");
            return digest.digest(segredoCompartilhado.toByteArray());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Demonstração da troca de chaves Diffie-Hellman.
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=== Demonstração de Troca de Chaves Diffie-Hellman ===\n");

        // Alice gera seu par de chaves
        System.out.println("Alice gerando par de chaves...");
        KeyPair chavesAlice = gerarParDeChaves();
        BigInteger publicaAlice = (BigInteger) chavesAlice.getPublic();
        BigInteger privadaAlice = (BigInteger) chavesAlice.getPrivate();
        System.out.println("Valor público da Alice: " + publicaAlice.toString(16).substring(0, 64) + "...");

        // Bob gera seu par de chaves
        System.out.println("\nBob gerando par de chaves...");
        KeyPair chavesBob = gerarParDeChaves();
        BigInteger publicaBob = (BigInteger) chavesBob.getPublic();
        BigInteger privadaBob = (BigInteger) chavesBob.getPrivate();
        System.out.println("Valor público do Bob: " + publicaBob.toString(16).substring(0, 64) + "...");

        // Alice calcula segredo compartilhado usando chave pública do Bob
        System.out.println("\nAlice calculando segredo compartilhado...");
        byte[] segredoAlice = calcularSegredoCompartilhado(publicaBob, privadaAlice);

        // Bob calcula segredo compartilhado usando chave pública da Alice
        System.out.println("Bob calculando segredo compartilhado...");
        byte[] segredoBob = calcularSegredoCompartilhado(publicaAlice, privadaBob);

        // Verificar se correspondem
        boolean segredosCorrespondem = java.util.Arrays.equals(segredoAlice, segredoBob);
        System.out.println("\nA Alice e o Bob têm o mesmo segredo compartilhado: " + segredosCorrespondem);

        if (segredosCorrespondem) {
            System.out.println("Segredo compartilhado (primeiros 32 chars): " + 
                new String(segredoAlice, 0, Math.min(32, segredoAlice.length)));
        }
    }
}

Diffie-Hellman Efêmero (DHE) e ECDH de Curva Elíptica

Implementações modernas usam chaves efêmeras para sigilo futuro.

Sigilo Futuro (Forward Secrecy)

O sigilo futuro garante que mesmo se chaves privadas de longo prazo forem comprometidas, comunicações passadas permanecem seguras. Isso é alcançado usando pares de chaves temporárias (efêmeras) para cada sessão.

Diffie-Hellman Efêmero (DHE)

No DHE: 1. Cada parte gera um novo par de chaves DH efêmero para a sessão 2. As chaves privadas efêmeras são descartadas após a troca de chaves 3. Chaves RSA/ECC de longo prazo são usadas apenas para autenticação, não para troca de chaves

Diffie-Hellman de Curva Elíptica (ECDH)

O ECDH fornece segurança equivalente ao DH tradicional com tamanhos de chave muito menores:

Nível de Segurança Tamanho da Chave DH Tradicional Tamanho da Chave ECDH
128-bit 3072 bits 256 bits
192-bit 7680 bits 384 bits

Implementação Java com ECDH

import java.security.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class TrocaDeChaveEC {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * Gera um par de chaves ECDH.
     */
    public static KeyPair gerarParDeChavesECDH() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
        keyGen.initialize(256); // Curva P-256 (secp256r1)
        return keyGen.generateKeyPair();
    }

    /**
     * Calcula segredo compartilhado usando ECDH.
     */
    public static byte[] calcularSegredoCompartilhado(PublicKey chavePublicaDoParceiro, PrivateKey minhaChavePrivada) throws Exception {
        // Use KeyAgreement para derivar segredo compartilhado
        KeyAgreement agreement = new KeyAgreement(KeyAgreement.getInstance("ECDH", "BC"));

        // Inicializar com própria chave privada
        agreement.init(minhaChavePrivada);

        // Processar chave pública do parceiro
        agreement.doPhase(chavePublicaDoParceiro, true);

        // Gerar segredo compartilhado
        SecretKey segredoCompartilhado = agreement.generateSecret();

        return segredoCompartilhado.getEncoded();
    }

    /**
     * Demonstra troca de chaves ECDH com sigilo futuro.
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=== Demonstração de Troca de Chaves ECDH ===\n");

        // Alice gera chaves efêmeras para esta sessão
        System.out.println("Alice gerando chaves efêmeras...");
        KeyPair chavesAlice = gerarParDeChavesECDH();
        PublicKey publicaAlice = chavesAlice.getPublic();
        PrivateKey privadaAlice = chavesAlice.getPrivate();

        // Bob gera chaves efêmeras para esta sessão
        System.out.println("Bob gerando chaves efêmeras...");
        KeyPair chavesBob = gerarParDeChavesECDH();
        PublicKey publicaBob = chavesBob.getPublic();
        PrivateKey privadaBob = chavesBob.getPrivate();

        // Alice calcula segredo compartilhado usando chave pública do Bob
        byte[] segredoAlice = calcularSegredoCompartilhado(publicaBob, privadaAlice);

        // Bob calcula segredo compartilhado usando chave pública da Alice
        byte[] segredoBob = calcularSegredoCompartilhado(publicaAlice, privadaBob);

        boolean segredosCorrespondem = java.util.Arrays.equals(segredoAlice, segredoBob);
        System.out.println("\nA Alice e o Bob têm o mesmo segredo compartilhado: " + segredosCorrespondem);

        // Demonstrar sigilo futuro: mesmo se chaves efêmeras forem comprometidas posteriormente,
        // sessões passadas permanecem seguras porque cada sessão usa diferentes chaves efêmeras.
    }
}

Modos de Troca de Chave TLS

O TLS suporta múltiplos modos de troca de chaves:

Modo Descrição Sigilo Futuro
RSA Servidor cripta segredo pré-mestre com chave pública RSA Não
DHE Diffie-Hellman efêmero Sim
ECDHE Diffie-Hellman efêmero de curva elíptica Sim (recomendado)

Troca de Chave TLS 1.3

O TLS 1.3 só suporta modos com sigilo futuro: - ECDHE: Diffie-Hellman efêmero de curva elíptica - PSK: Chaves pré-compartilhadas com ECDHE para derivação de chaves

Considerações de Segurança

Ataques Comuns e Mitigações

Ataque Descrição Mitigação
Homem-no-meio (Man-in-the-middle) Atacante intercepta valores públicos Use DH autenticado (com certificados)
Subgrupo pequeno Atacante força uso de subgrupos pequenos Valide ordem do grupo, rejeite parâmetros inválidos
Ataque por tempo Canal lateral através de variações de tempo Use operações de tempo constante

Melhores Práticas

  1. Use parâmetros padrão: RFC 7919 para DH, curvas NIST para ECDH
  2. Ative sigilo futuro: Prefira DHE/ECDHE sobre troca RSA
  3. Valide certificados do parceiro: Prevenir ataques homem-no-meio
  4. Use TLS 1.3: Fornece melhor segurança e desempenho

Referências

  1. RFC 7919: Moduli for Diffie-Hellman Finite Fields
  2. RFC 5639: Elliptic Curve Cryptography (ECC) Cipher Suites
  3. RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3