Criptografia simétrica: AES na prática

1. Fundamentos do AES para Desenvolvedores

AES (Advanced Encryption Standard) é o algoritmo de criptografia simétrica mais utilizado no mundo. "Simétrica" significa que a mesma chave é usada tanto para cifrar quanto para decifrar os dados. Isso difere da criptografia assimétrica (como RSA), onde existem chaves pública e privada distintas.

O AES opera em blocos de 128 bits (16 bytes) e oferece três modos de operação principais que todo desenvolvedor precisa conhecer:

  • ECB (Electronic Codebook): o modo mais simples e perigoso. Cada bloco é cifrado independentemente, o que faz com que padrões iguais nos dados gerem blocos cifrados idênticos — um vazamento grave de informação.
  • CBC (Cipher Block Chaining): cada bloco é combinado com o bloco cifrado anterior antes de ser cifrado. Exige um vetor de inicialização (IV) e padding (geralmente PKCS#7).
  • GCM (Galois/Counter Mode): modo autenticado que combina cifragem e verificação de integridade em um único passo. É o mais recomendado para a maioria dos casos.

Por que AES é o padrão atual? Segurança comprovada por décadas de análise criptográfica, desempenho excelente em hardware e software, e suporte nativo em praticamente todas as linguagens modernas.

2. Tamanhos de Chave e Implicações de Segurança

AES suporta três tamanhos de chave:

  • AES-128: 128 bits (16 bytes). Seguro para uso geral, com desempenho ligeiramente superior.
  • AES-192: 192 bits (24 bytes). Raramente usado na prática.
  • AES-256: 256 bits (32 bytes). Oferece margem de segurança contra ataques quânticos futuros. Recomendado para dados altamente sensíveis.

Recomendação prática: use AES-256-GCM como padrão. Se o desempenho for crítico e os dados não forem extremamente sensíveis, AES-128-GCM é aceitável.

A geração de chaves deve usar fontes criptograficamente seguras. Exemplo em Python:

import os

# Gerar chave AES-256 (32 bytes)
chave = os.urandom(32)

# Gerar IV para GCM (12 bytes é o tamanho recomendado)
iv = os.urandom(12)

Nunca derive chaves de senhas sem um KDF (Key Derivation Function) como PBKDF2 ou Argon2.

3. Modos de Operação e Seus Riscos

ECB — O Modo Proibido

O ECB cifra blocos idênticos em cifras idênticas. Isso permite que um atacante identifique padrões no texto cifrado.

# EXEMPLO DO PROBLEMA — NÃO USE ECB
# Se cifrarmos "AAAA" e "BBBB" com ECB:
# "AAAA" -> cifra X
# "BBBB" -> cifra Y
# Mas se o texto tiver "AAAA" em duas posições, ambas geram cifra X

CBC — Exige Cuidados

O CBC requer um IV aleatório e único para cada operação. O IV não precisa ser secreto, mas nunca deve ser reutilizado com a mesma chave.

# CBC exige padding PKCS#7
# Se o último bloco tiver 5 bytes, adiciona-se 11 bytes de valor 0x0B
# Isso torna o tamanho do texto cifrado sempre múltiplo de 16

GCM — O Mais Seguro

GCM fornece autenticação integrada. Você obtém o texto cifrado e um tag de autenticação que verifica se os dados não foram adulterados.

# GCM produz:
# - texto cifrado (mesmo tamanho do original)
# - tag de autenticação (16 bytes)
# - usa nonce (outro nome para IV) de 12 bytes

4. Gerenciamento de Chaves e IVs na Prática

Armazenamento Seguro de Chaves

Nunca hardcode chaves no código-fonte. Use:

  • Variáveis de ambiente: para ambientes de desenvolvimento e produção simples.
  • Cofres de segredos: HashiCorp Vault, AWS Secrets Manager, Azure Key Vault.
  • HSM (Hardware Security Module): para requisitos de segurança máxima.

IVs — Regra de Ouro

Nunca reutilize o mesmo IV com a mesma chave. Gere um novo IV aleatório para cada operação de cifragem. O IV pode ser armazenado junto com o texto cifrado (em texto claro).

# Estrutura de armazenamento recomendada:
# [IV (12 bytes)] + [texto cifrado] + [tag (16 bytes)]

Rotação de Chaves

Implemente rotação periódica de chaves. Estratégia comum: manter uma chave ativa para novas cifragens e uma ou mais chaves antigas para decifragem de dados existentes.

5. Implementação Segura com Bibliotecas Comuns

Exemplo em Python com cryptography

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os

def cifrar(dados: bytes, chave: bytes) -> bytes:
    aesgcm = AESGCM(chave)
    iv = os.urandom(12)
    # GCM cifra e autentica em um único passo
    texto_cifrado = aesgcm.encrypt(iv, dados, None)
    # Retorna IV + texto_cifrado (que inclui o tag)
    return iv + texto_cifrado

def decifrar(dados_cifrados: bytes, chave: bytes) -> bytes:
    aesgcm = AESGCM(chave)
    iv = dados_cifrados[:12]
    texto_cifrado = dados_cifrados[12:]
    return aesgcm.decrypt(iv, texto_cifrado, None)

# Uso
chave = os.urandom(32)
dados = b"Mensagem secreta"
cifrado = cifrar(dados, chave)
decifrado = decifrar(cifrado, chave)
print(decifrado.decode())  # Mensagem secreta

Exemplo em Node.js com crypto

const crypto = require('crypto');

function cifrar(texto, chave) {
    const iv = crypto.randomBytes(12);
    const cipher = crypto.createCipheriv('aes-256-gcm', chave, iv);
    let cifrado = cipher.update(texto, 'utf8', 'hex');
    cifrado += cipher.final('hex');
    const tag = cipher.getAuthTag().toString('hex');
    return iv.toString('hex') + ':' + cifrado + ':' + tag;
}

function decifrar(pacote, chave) {
    const partes = pacote.split(':');
    const iv = Buffer.from(partes[0], 'hex');
    const cifrado = partes[1];
    const tag = Buffer.from(partes[2], 'hex');
    const decipher = crypto.createDecipheriv('aes-256-gcm', chave, iv);
    decipher.setAuthTag(tag);
    let texto = decipher.update(cifrado, 'hex', 'utf8');
    texto += decipher.final('utf8');
    return texto;
}

// Uso
const chave = crypto.randomBytes(32);
const cifrado = cifrar("Mensagem secreta", chave);
const decifrado = decifrar(cifrado, chave);
console.log(decifrado);  // Mensagem secreta

Cuidado fundamental: nunca implemente AES manualmente. Sempre use bibliotecas testadas e auditadas.

6. Armadilhas Comuns e Como Evitá-las

Chaves Fixas ou Derivadas de Senhas Fracas

# ERRADO: chave derivada diretamente de senha
senha = "minha_senha"
chave = senha.encode()[:32]  # Péssima ideia!

# CERTO: usar KDF com salt
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
salt = os.urandom(16)
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=salt, iterations=600000)
chave = kdf.derive(senha.encode())

Esquecer a Autenticação

Cifrar sem autenticação permite que um atacante modifique o texto cifrado. CBC + HMAC funciona, mas GCM já resolve isso.

Vazamento de Metadados

O tamanho do texto cifrado revela o tamanho aproximado do original. Para dados muito sensíveis, considere padding para tamanhos fixos.

7. Casos de Uso no Desenvolvimento Web

Criptografia de Dados em Repouso

Cifre tokens de sessão, dados pessoais e informações financeiras antes de armazenar no banco de dados ou Redis:

# Exemplo: cifrar payload JSON antes de salvar no Redis
import json

payload = {"user_id": 123, "email": "usuario@exemplo.com"}
payload_bytes = json.dumps(payload).encode()
payload_cifrado = cifrar(payload_bytes, chave)
redis.set("sessao:123", payload_cifrado)

Criptografia em Trânsito (Camada de Aplicação)

O TLS protege os dados durante o transporte, mas a criptografia em nível de aplicação oferece proteção adicional caso o TLS seja comprometido ou os logs sejam expostos.

Exemplo Prático: API com Dados Cifrados

# POST /api/dados-sensiveis
# 1. Recebe dados do cliente via HTTPS
# 2. Cifra com AES-256-GCM
# 3. Armazena no banco (IV + cifrado + tag)
# 4. Decifra apenas quando necessário, em ambiente controlado

Referências