Pular para conteúdo

Criptografia Assimétrica (RSA, ECC)

A criptografia assimétrica, também conhecida como criptografia de chave pública, usa um par de chaves matematicamente relacionadas: uma chave pública que pode ser compartilhada abertamente e uma chave privada que deve permanecer secreta. Isso resolve o problema fundamental da comunicação segura sem segredos pré-existentes.

Fundamentos Matemáticos

O Problema do Logaritmo Discreto

Dado um primo $p$, um gerador $g$ e o valor $y = g^x \mod p$, encontrar $x$ dado $(p, g, y)$ é computacionalmente difícil para valores grandes de $p$. Isso forma a base de: - Troca de Chaves Diffie-Hellman - Criptografia ElGamal

O Problema da Fatoração de Inteiros

Dado um número composto grande $n = p \times q$ (onde $p, q$ são primos), encontrar $p$ e $q$ é difícil. Isso forma a base de: - Criptografia RSA

O Problema do Logaritmo Discreto em Curvas Elípticas

Encontrar $k$ dado os pontos $P$ e $Q = kP$ em uma curva elíptica é computacionalmente difícil, mesmo para tamanhos de chave relativamente pequenos. Isso permite: - ECC (Criptografia de Curva Elíptica) - chaves menores com segurança equivalente ao RSA

Criptografia RSA

O RSA é o algoritmo assimétrico mais amplamente utilizado, baseado na dificuldade de fatorar inteiros grandes.

Fundamento Matemático

Dado: 1. Dois números primos grandes $p$ e $q$ (tipicamente 1024 bits cada para uma chave de 2048 bits) 2. Módulo $n = p \times q$ 3. Função totiente de Euler $\phi(n) = (p-1)(q-1)$ 4. Expoente público $e$ (tipicamente 65537, coprimo com $\phi(n)$) 5. Expoente privado $d$ tal que $ed \equiv 1 \mod \phi(n)$

Criptografia: $C = M^e \mod n$
Descriptografia: $M = C^d \mod n$

Onde: - $M$ é a mensagem em texto claro (deve ser menor que $n$) - $C$ é o texto cifrado - Todas as operações são aritmética modular

Processo de Geração de Chaves RSA

def gerar_chave_rsa(bits=2048):
    # Gerar dois números primos grandes
    p = obter_primo_grande(bits // 2)
    q = obter_primo_grande(bits // 2)

    # Calcular módulo e totiente
    n = p * q
    phi_n = (p - 1) * (q - 1)

    # Escolher expoente público e (tipicamente 65537)
    e = 65537

    # Verificar gcd(e, phi(n)) == 1
    assert math.gcd(e, phi_n) == 1

    # Calcular expoente privado d
    d = modInverse(e, phi_n)

    return {
        'n': n,
        'e': e,
        'd': d,
        'p': p,
        'q': q
    }

Implementação em Java

import java.math.BigInteger;
import java.security.*;
import java.util.Base64;

public class CriptografiaRSA {

    private static final int TAMANHO_CHAVE = 2048;

    /**
     * Gera um novo par de chaves RSA.
     */
    public static KeyPair gerarParDeChaves() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(TAMANHO_CHAVE);
        return keyGen.generateKeyPair();
    }

    /**
     * Cripta dados usando a chave pública do destinatário.
     */
    public static String criptografar(String textoPlano, PublicKey chavePublica) throws Exception {
        // Para RSA de mensagens grandes, use abordagem híbrida:
        // 1. Gerar chave simétrica aleatória
        // 2. Criptografar mensagem com chave simétrica (AES)
        // 3. Criptografar chave simétrica com chave pública do destinatário (RSA)

        // Exemplo simples apenas para mensagens pequenas:
        javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, chavePublica);

        byte[] bytesCifrados = cipher.doFinal(textoPlano.getBytes());
        return Base64.getEncoder().encodeToString(bytesCifrados);
    }

    /**
     * Descriptografa dados usando a chave privada.
     */
    public static String descriptografar(String textoCodificado, PrivateKey chavePrivada) throws Exception {
        byte[] textoCifrado = Base64.getDecoder().decode(textoCodificado);

        javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(javax.crypto.Cipher.DECRYPT_MODE, chavePrivada);

        byte[] bytesDescriptografados = cipher.doFinal(textoCifrado);
        return new String(bytesDescriptografados);
    }

    public static void main(String[] args) throws Exception {
        // Gerar par de chaves
        KeyPair parDeChaves = gerarParDeChaves();
        PublicKey chavePublica = parDeChaves.getPublic();
        PrivateKey chavePrivada = parDeChaves.getPrivate();

        String mensagem = "Mensagem secreta para criptografia RSA";

        System.out.println("Mensagem original: " + mensagem);

        // Criptografar com chave pública
        String criptografado = criptografar(mensagem, chavePublica);
        System.out.println("Criptografado (Base64): " + criptografado);

        // Descriptografar com chave privada
        String descriptografado = descriptografar(criptografado, chavePrivada);
        System.out.println("Descriptografado: " + descriptografado);
    }
}

Tamanhos de Chave RSA e Segurança

Tamanho da Chave Segurança Estimada Tempo para Quebrar (estimado) Recomendação
1024 bits ~80 bits Anos com cluster Obsoleto
2048 bits ~112 bits Séculos Mínimo
3072 bits ~128 bits Milênios Recomendado
4096 bits ~152 bits Extremamente longo Alta segurança

Criptografia de Curva Elíptica (ECC)

A Criptografia de Curva Elíptica fornece segurança equivalente ao RSA com tamanhos de chave muito menores, tornando-a ideal para ambientes com limitação de largura de banda.

Fundamento Matemático

Uma curva elíptica sobre um campo finito é definida pela equação: $$y^2 = x^3 + ax + b \mod p$$

O problema do logaritmo discreto em curvas elípticas (ECDLP): Dados pontos $P$ e $Q = kP$, encontrar $k$ é computacionalmente inviável.

Comparação de Tamanhos de Chave

Nível de Segurança Tamanho da Chave RSA Tamanho da Chave ECC
128-bit 3072 bits 256 bits
192-bit 7680 bits 384 bits
256-bit 15360 bits 512 bits

ECDSA (Elliptic Curve Digital Signature Algorithm)

O ECDSA é usado para assinaturas digitais e fornece a mesma segurança do RSA com chaves menores.

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

public class AssinaturaECDSA {

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

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

    /**
     * Assina dados usando ECDSA.
     */
    public static byte[] assinarDados(byte[] mensagem, PrivateKey chavePrivada) throws Exception {
        Signature assinatura = Signature.getInstance("SHA256withECDSA", "BC");
        assinatura.initSign(chavePrivada);
        assinatura.update(mensagem);
        return assinatura.sign();
    }

    /**
     * Verifica uma assinatura ECDSA.
     */
    public static boolean verificarAssinatura(byte[] mensagem, byte[] assinaturaBytes, PublicKey chavePublica) throws Exception {
        Signature assinatura = Signature.getInstance("SHA256withECDSA", "BC");
        assinatura.initVerify(chavePublica);
        assinatura.update(mensagem);
        return assinatura.verify(assinaturaBytes);
    }

    public static void main(String[] args) throws Exception {
        KeyPair parDeChaves = gerarParDeChaves();
        PublicKey chavePublica = parDeChaves.getPublic();
        PrivateKey chavePrivada = parDeChaves.getPrivate();

        byte[] mensagem = "Mensagem para assinar".getBytes();

        // Assinar a mensagem
        byte[] assinatura = assinarDados(mensagem, chavePrivada);
        System.out.println("Comprimento da assinatura: " + assinatura.length + " bytes");

        // Verificar a assinatura
        boolean isValida = verificarAssinatura(mensagem, assinatura, chavePublica);
        System.out.println("Assinatura válida: " + isValida);
    }
}

ECDH (Elliptic Curve Diffie-Hellman) - Troca de Chaves

O ECDH permite que duas partes estabeleçam um segredo compartilhado usando criptografia de curva elíptica.

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);
        return keyGen.generateKeyPair();
    }

    /**
     * Calcula segredo compartilhado usando a chave pública do parceiro.
     */
    public static byte[] calcularSegredoCompartilhado(PublicKey chavePublicaDoParceiro, PrivateKey minhaChavePrivada) throws Exception {
        // Use ECDH para derivar segredo compartilhado
        KeyAgreement agreement = new KeyAgreement(KeyAgreement.getInstance("ECDH", "BC"));
        agreement.init(minhaChavePrivada);
        agreement.doPhase(chavePublicaDoParceiro, true);

        SecretKey segredoCompartilhado = agreement.generateSecret();
        return segredoCompartilhado.getEncoded();
    }

    public static void main(String[] args) throws Exception {
        // Alice gera par de chaves
        KeyPair chavesAlice = gerarParDeChavesECDH();
        PublicKey publicaAlice = chavesAlice.getPublic();
        PrivatePrivadaAlice = chavesAlice.getPrivate();

        // Bob gera par de chaves
        KeyPair chavesBob = gerarParDeChavesECDH();
        PublicKey publicaBob = chavesBob.getPublic();
        PrivatePrivadaBob = 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);

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

Criptografia ElGamal

O ElGamal é baseado no problema do logaritmo discreto e fornece criptografia probabilística.

Fundamento Matemático

Dado: - Um primo grande $p$ - Um gerador $g$ de $\mathbb{Z}_p^*$ - Chave pública $(p, g, y)$ onde $y = g^x \mod p$ (chave privada é $x$)

Criptografia: Para criptografar mensagem $m$: 1. Escolha aleatória $k$ 2. Calcule $c_1 = g^k \mod p$ 3. Calcule $c_2 = m \cdot y^k \mod p$ 4. Texto cifrado: $(c_1, c_2)$

Descriptografia: $$m = c_2 \cdot (c_1^x)^{-1} \mod p$$

Implementação em Java

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

public class CriptografiaElGamal {

    // Parâmetros padrão (RFC 3526)
    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 ElGamal.
     */
    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);
    }
}

Considerações de Segurança

Melhores Práticas para Criptografia Assimétrica

  1. Use tamanhos de chave adequados: RSA 2048+ ou ECC P-256 mínimo
  2. Prefira criptografia autenticada: Use padding OAEP para RSA, não PKCS#1 v1.5
  3. Evite algoritmos crus: Sempre use modos apropriados (RSA-OAEP, ECDSA)
  4. Use criptografia híbrida: Cripte dados com AES, cripte chave com RSA/ECC
  5. Implemente validação de certificados: Verifique certificados em conexões TLS

Vulnerabilidades Comuns

Vulnerabilidade Descrição Mitigação
Ataques a subgrupos pequenos Atacante força uso de subgrupos pequenos Valide ordem do grupo
Ataques por tempo Canal lateral através de variações de tempo Use operações de tempo constante
Ataques oracle de padding Explora erros de validação de padding Use OAEP, não PKCS#1 v1.5

Quando Usar Criptografia Assimétrica vs Simétrica

Cenário Abordagem Recomendada
Criptografar arquivos grandes Híbrida (AES + RSA/ECC para chave)
Mensagens seguras ECDH para troca de chaves, AES para dados
Assinaturas digitais ECDSA ou RSA-PSS
Distribuição de chaves Variantes Diffie-Hellman

Referências

  1. RFC 8017: PKCS #1: RSA Cryptography Specifications Version 2.2
  2. RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA)
  3. RFC 5480: Elliptic Curve Cryptography Subject Public Key Info
  4. Menezes, A. J., van Oorschot, P. C., & Vanstone, S. A. (1996). Handbook of Applied Cryptography. CRC Press.