Aleatoriedade e segredos

TL;DR

Toda a criptografia depende de uma premissa tácita: que os números gerados como segredos são genuinamente imprevisíveis. Quando essa premissa falha — por um patch descuidado, por uma implementação preguiçosa ou por um padrão sabotado — a matemática mais robusta do mundo não salva ninguém. Aleatoriedade de qualidade não é um detalhe de implementação; é o alicerce invisível de qualquer sistema seguro.


Por que aleatoriedade importa antes de tudo

Imagine um cofre com 2¹²⁸ combinações possíveis. Se o fabricante, sem avisar ninguém, sempre entrega o cofre com uma das 300 mil combinações de um catálogo interno, não importa que a fechadura seja mecanicamente perfeita — o adversário não testa 2¹²⁸ possibilidades; testa 300 mil. Foi exatamente o que aconteceu com o Debian em 2008.

A criptografia moderna garante confidencialidade, integridade e autenticidade assumindo que chaves, nonces e IVs são uniformemente aleatórios e imprevisíveis. Quebrar essa suposição colapsa o sistema sem tocar nos algoritmos.

A regra de ouro

Uma chave de 128 bits só vale 128 bits de segurança se cada bit for independente e imprevisível. Se a semente que gerou a chave tem apenas 15 bits de entropia efetiva, a segurança real é 2¹⁵ — não 2¹²⁸.


Entropia: imprevisibilidade medida em bits

Entropia, no sentido criptográfico, é a quantidade de incerteza (imprevisibilidade) de um valor. Shannon formalizou isso em 1948:

Para um segredo criptográfico, o que interessa é a entropia de min-entropy — o logaritmo negativo da probabilidade do resultado mais provável. Um dado honesto de seis faces tem ≈ 2,58 bits de entropia de Shannon mas apenas log₂(1/6) ≈ 2,58 de min-entropy. Um dado viciado que cai em 6 com probabilidade 0,99 tem min-entropy ≈ 0,014 bits — é quase determinístico para o adversário.

A diferença entre Shannon entropy e min-entropy importa em cripto porque adversários não jogam na média; eles sempre escolhem o resultado mais provável. Por isso, algoritmos como AES e RSA são parametrizados em termos de min-entropy, não de Shannon entropy.

Para chaves criptográficas:

  • 128 bits de segurança → precisa de uma fonte com ≥ 128 bits de min-entropy.
  • Usar apenas o relógio do sistema como semente → tipicamente < 20 bits de entropia efetiva.
  • Usar PID + timestamp em microssegundos → ≈ 15–20 bits de espaço de busca real (Netscape, 1995).
  • Pool de entropia do Linux em boot limpo de IoT → pode ter < 32 bits antes do primeiro evento de hardware.

Quanto de entropia cada tipo de segredo precisa?

SegredoMin-entropy mínima recomendadaTamanho típico
Chave AES-128128 bits16 bytes
Chave AES-256 / ChaCha20256 bits32 bytes
Token de sessão web≥ 128 bits16–32 bytes
Salt bcrypt/Argon2≥ 128 bits (unicidade > sigilo)16 bytes
Nonce AES-GCM (96 bits)≥ 96 bits (unicidade > min-entropy)12 bytes
Chave privada Ed25519256 bits32 bytes seed
Parâmetro DH efêmero≥ 128 bits≥ 256 bytes (group size)

Heurística de entrevista

“Entropia é o que um atacante não sabe. Quanto maior a entropia, maior o espaço que ele precisa varrer. Aleatoriedade criptográfica é entropia empacotada em bits — e min-entropy é a medida que importa, não a média de Shannon.”


PRNG × CSPRNG × TRNG — a taxonomia que salva empregos

Os três tipos resolvem problemas distintos. Confundi-los é o erro mais comum de implementação.

graph TD
    A["Fonte fisica<br/>(ruido termico, timing de hardware)"] --> B["TRNG<br/>True Random Number Generator"]
    B --> C["Pool de entropia do SO"]
    C --> D["CSPRNG<br/>Criptograficamente Seguro"]
    D --> E["Chaves, nonces, IVs, salts, tokens"]
    F["Seed deterministico"] --> G["PRNG<br/>ex.: Mersenne Twister, LCG"]
    G --> H["Simulacao, jogos, testes A/B"]
    G -.->|"NUNCA"| E

Leitura do diagrama

O fluxo superior (TRNG → pool → CSPRNG) é o caminho correto para material criptográfico. O PRNG convencional (fluxo inferior) só é aceitável para fins não-criptográficos. A seta tracejada com “NUNCA” marca o erro clássico: usar Math.random() ou rand() para gerar tokens de sessão.

PRNG — deterministico e previsível

  • LCG (Linear Congruential Generator): Xₙ₊₁ = (a·Xₙ + c) mod m. Ciclo curto, retrodicável.
  • Mersenne Twister (MT19937): excelente para simulação e jogos; porém, com 624 saídas consecutivas de 32 bits, o estado interno inteiro é recuperável — e a partir daí todas as saídas futuras são previsíveis.
  • Math.random() (JavaScript V8), rand() (C), java.util.Random: proibidos para cripto. Nenhum oferece garantias de forward secrecy ou resistência à recuperação de estado.

CSPRNG — o mínimo aceitável para cripto

Um CSPRNG precisa satisfazer duas propriedades:

  1. Next-bit unpredictability: conhecer os primeiros k bits não ajuda a prever o bit k+1 com probabilidade > 50% + ε negligível.
  2. State compromise resilience: mesmo que o estado interno vaze, não deve ser possível reconstruir saídas anteriores (backward secrecy) nem prever facilmente as futuras (forward secrecy, alcançada por rerandomização periódica).

Exemplos corretos por linguagem/SO:

ContextoFunção correta
Linux/macOSgetrandom(2), /dev/urandom
Pythonsecrets.token_bytes()
Javajava.security.SecureRandom
Node.jscrypto.randomBytes()
Gocrypto/rand.Read()
C/C++getrandom() ou RAND_bytes() (OpenSSL)

Armadilha de semeadura manual

SecureRandom em Java não deve ser semeado manualmente em produção. Chamar new SecureRandom(seed) com uma seed fraca desfaz todas as garantias do CSPRNG. Deixe o SO fornecer a entropia.

TRNG — entropia física

  • Hardware Security Module (HSM), TPM, RDRAND (Intel), RDSEED: leem ruído físico (ruído térmico, jitter de oscilador, decaimento radioativo).
  • O SO coleta entropia de eventos de hardware (timings de interrupções, movimentação de mouse, I/O de disco) para alimentar o pool de entropia — que por sua vez semeia o CSPRNG.

RDRAND não é suficiente sozinho

A instrução RDRAND da Intel extrai bits diretamente do RNG de hardware embutido no processador — é conveniente e rápida. Porém, usá-la como única fonte de entropia exige confiar integralmente na Intel. O kernel Linux a usa como uma das fontes do pool, nunca como única. Para aplicações paranóicas (HSMs, carteiras de criptomoedas de alta segurança), misture fontes independentes: RDRAND ⊕ ruído de disco ⊕ dados de rede.

Comparando as três classes

CaracterísticaPRNGCSPRNGTRNG
Determinístico?SimSim (dado estado)Não
Previsível conhecendo estado?SimNãoNão
VelocidadeMuito altaAltaBaixa
Boa para cripto?NãoSimSim (como semente)
Exemplo Linuxrand(), drand48()getrandom(2), /dev/urandom/dev/hwrng, RDRAND

Fontes de entropia no SO — desmistificando /dev/urandom

flowchart LR
    subgraph HW ["Eventos de hardware"]
        A["IRQs / timings"]
        B["RDRAND / RDSEED"]
        C["Ruido de disco / rede"]
    end
    HW --> D["Input Pool<br/>(kernel entropy pool)"]
    D --> E["ChaCha20 DRBG<br/>(Linux >= 5.17)"]
    E --> F["/dev/urandom"]
    E --> G["/dev/random"]
    E --> H["getrandom(2)"]
    G -.->|"bloqueia se pool nao inicializado<br/>(comportamento legado)"| I["Aplicacao"]
    F --> I
    H --> I

Leitura do diagrama

Desde o Linux 5.17, /dev/random e /dev/urandom usam o mesmo CSPRNG (ChaCha20 DRBG). A diferença prática é que /dev/random ainda pode bloquear antes de o pool ser inicializado pela primeira vez — mas não bloqueia em sistemas em execução há mais de alguns segundos. getrandom(2) (disponível desde Linux 3.17) bloqueia somente até a inicialização, depois retorna sem bloqueio — é a API recomendada para código novo.

O mito do /dev/urandom perigoso

Por anos, a documentação sugeria que /dev/urandom era “menos seguro” por não bloquear. Isso era verdade apenas em um cenário específico: imediatamente após o boot, antes de o pool de entropia ser inicializado. Em qualquer outro momento, /dev/urandom e /dev/random são equivalentes em segurança prática. Theodore Ts’o (mantenedor do RNG do Linux) confirmou isso em vários posts técnicos.

O problema real é o boot em ambientes com pouca entropia:

  • VMs que clonadas com estado idêntico podem gerar as mesmas chaves.
  • Dispositivos IoT que nunca veem eventos de hardware aleatórios (disco, rede, mouse) antes de gerar sua primeira chave SSH.
  • Contêineres mínimos sem acesso a /dev/hwrng ou equivalente.

O paper “Mining Your Ps and Qs” (Heninger et al., USENIX Security 2012) documentou isso em escala: 0,5% dos hosts TLS tinham chaves RSA com fatores primos compartilhados — resultado direto de entropia insuficiente no momento da geração da chave. O ataque é elegante: se dois hosts independentes geraram chaves RSA fracas compartilhando um dos primos, gcd(N₁, N₂) revela imediatamente o fator primo comum, quebrando ambas as chaves sem nenhuma força bruta.

Por que RSA é tão sensível à entropia no boot?

Gerar uma chave RSA exige dois primos p e q grandes e aleatórios. Se o pool de entropia está vazio no boot, o CSPRNG pode repetir a mesma sequência em máquinas diferentes — gerando o mesmo p em duas chaves distintas. gcd(N₁, N₂) = p é computável em milissegundos mesmo para chaves de 2048 bits. É por isso que chaves RSA geradas em dispositivos IoT sem fonte de entropia adicional são estruturalmente fracas.


Nonce, IV, salt — o trio confundido em toda entrevista

Três conceitos distintos que partilham a palavra “aleatório” mas têm requisitos radicalmente diferentes.

graph LR
    subgraph NONCE ["Nonce"]
        N1["Obrigatorio: UNICO por operacao"]
        N2["Nem sempre precisa ser imprevisivel<br/>(contador serve em CTR/GCM)"]
        N3["Reuso => catastrofe<br/>(ex.: two-time pad, GCM key recovery)"]
    end
    subgraph IV ["IV (Initialization Vector)"]
        I1["Obrigatorio: IMPREVISIVEL<br/>(CBC, CFB, OFB)"]
        I2["Publico: pode ir no ciphertext"]
        I3["Reuso em CBC => revela XOR dos plaintexts"]
    end
    subgraph SALT ["Salt"]
        S1["Obrigatorio: UNICO por usuario/senha"]
        S2["NAO precisa ser secreto"]
        S3["Derrota rainbow tables<br/>e ataques de dicionario paralelos"]
    end

Leitura do diagrama

Os três blocos mostram os requisitos mínimos de cada primitivo. Note que salt não precisa de sigilo — só de unicidade. IV precisa de imprevisibilidade. Nonce precisa de unicidade mas nem sempre de imprevisibilidade (depende do modo de operação).

Consequências do reuso

PrimitivoO que quebra com reuso
Nonce em AES-GCMRecuperação da chave de autenticação GHASH → forja mensagens
IV em AES-CBCC₁ ⊕ C₂ = P₁ ⊕ P₂ — relação entre plaintexts vaza
Nonce ECDSA (k)Chave privada recuperável com álgebra simples (caso PS3)
Salt em bcryptDois usuários com mesma senha têm mesmo hash → lookup table volta a funcionar

Salt e hashing de senha

O salt não é segredo — ele vai armazenado junto com o hash (ex.: $2b$12$<22-chars-salt><31-chars-hash> em bcrypt). O objetivo é forçar o atacante a executar a função de derivação cara separadamente para cada usuário, tornando ataques em paralelo com tabelas pré-computadas inviáveis. Detalhes em 06 - Hashing criptográfico.

Nonce em DH e troca de chaves

Em protocolos de troca de chaves como TLS 1.3, tanto cliente quanto servidor contribuem com nonces para o handshake. Esses nonces garantem que cada sessão derive material de chave único — mesmo que as chaves de longo prazo sejam idênticas. O nonce do servidor, em particular, é o principal mecanismo de defesa contra ataques de replay: um adversário que grava o handshake e o reenvia recebe um nonce diferente do servidor e falha na derivação da chave de sessão. A aleatoriedade aqui não é sobre confidencialidade direta, mas sobre freshness — garantir que a conversa está acontecendo agora, não reproduzida de um momento anterior.


Casos canônicos — quando a aleatoriedade falhou

sequenceDiagram
    participant Dev as Desenvolvedor
    participant Sys as Sistema
    participant Att as Atacante

    Note over Dev,Sys: Sony PS3 (2010) - ECDSA com nonce k fixo
    Dev->>Sys: Assina firmware A com k=constante
    Dev->>Sys: Assina firmware B com k=constante (mesmo k!)
    Att->>Sys: Observa (r1,s1) e (r2,s2) com r1 = r2
    Note over Att: r igual => k identico<br/>Resolve sistema linear => extrai chave privada
    Att->>Sys: Assina qualquer codigo com chave recuperada

Leitura do diagrama

Quando k é fixo em ECDSA, r = (k·G).x é igual em todas as assinaturas. Com dois pares (r, s₁) e (r, s₂) do mesmo r, a chave privada d é recuperável por álgebra linear simples: d = (s₁·h₂ - s₂·h₁) / (s₂ - s₁) mod n. Isso exigiu exatamente zero força bruta.

Linha do tempo dos desastres

AnoIncidenteRaiz da falhaImpacto
1995Netscape SSLSeed = PID + timestamp (< 20 bits de entropia)Sessões SSL quebráveis em segundos
2008Debian OpenSSL (CVE-2008-0166)Patch removeu duas linhas de seed; só PID restou; 294.912 chaves possíveisChaves RSA/DSA/EC previsíveis; reemissão em massa
2010Sony PS3 ECDSA (27C3, fail0verflow)Nonce k fixo em todas as assinaturasChave privada da Sony recuperada; homebrew irrestrito
2012Chaves RSA em IoT (Mining Your Ps and Qs)Entropia insuficiente no boot; chaves com fator primo comum0,5% de hosts TLS com chave privada RSA recuperável via GCD
2013Dual_EC_DRBG (NIST SP 800-90A)Backdoor suspeito: relação entre pontos P e Q conhecida pela NSACSPRNG “padrão” comprometido; RSA BSAFE afetado

Dual_EC_DRBG — o CSPRNG sabotado

O Dual Elliptic Curve DRBG foi padronizado pela NIST em SP 800-90A em 2006. Em 2007, Dan Shumow e Niels Ferguson (da Microsoft) mostraram que se alguém conhecesse o logaritmo discreto e tal que Q = e·P (onde P e Q são os pontos da curva fixados no padrão), esse alguém poderia prever todas as saídas futuras com apenas 32 bytes de observação. Os documentos Snowden em 2013 confirmaram que a NSA inseriu deliberadamente os parâmetros P e Q. O algoritmo foi retirado do NIST SP 800-90A em abril de 2014.


Gerando segredos corretamente na prática

Exemplos canônicos por linguagem

Python — tokens de sessão e chaves:

import secrets
 
# Token de sessão URL-safe (256 bits de entropia)
token = secrets.token_urlsafe(32)   # 32 bytes = 256 bits
 
# Chave AES-256
key = secrets.token_bytes(32)
 
# NUNCA: random.randint(0, 2**256) — PRNG, não CSPRNG

JavaSecureRandom:

import java.security.SecureRandom;
 
// Não seede manualmente; o SO provê entropia
SecureRandom rng = new SecureRandom();
byte[] key = new byte[32];          // 256 bits
rng.nextBytes(key);
 
// NUNCA: new Random().nextBytes(key)

Node.js:

const { randomBytes } = require('crypto');
 
// 32 bytes = 256 bits de entropia
const sessionToken = randomBytes(32).toString('hex');
 
// NUNCA: Math.random()

Go:

import "crypto/rand"
 
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
    panic(err)
}
// math/rand é PROIBIDO para cripto

Linux/C (sem libssl):

#include <sys/random.h>
 
unsigned char key[32];
// getrandom bloqueia até o pool ser inicializado
ssize_t n = getrandom(key, sizeof(key), 0);
if (n != sizeof(key)) { /* erro */ }

Cuidado com fork()

Em ambientes que fazem fork() após inicializar um CSPRNG, o processo filho pode herdar o mesmo estado do gerador. CSPRNGs modernos de SO (como o do Linux) detectam fork via getentropy()/getrandom() e rerandomizam automaticamente — mas bibliotecas de espaço de usuário podem não fazer isso. Verifique o comportamento da sua biblioteca antes de usar em servidores multi-processo.


Entropia em ambientes problemáticos

flowchart TD
    A["Ambiente de geracao de chave"] --> B{"Tem entropia suficiente?"}
    B -->|"Sistema desktop/server em execucao"| C["OK — /dev/urandom / getrandom"]
    B -->|"VM recem clonada"| D["RISCO — estado identico entre clones"]
    B -->|"IoT no boot sem eventos"| E["RISCO — pool vazio, bloqueia ou gera fraco"]
    B -->|"Conteiner minimo"| F["RISCO — sem /dev/hwrng, poucos eventos"]
    D --> G["Mitigacao: misturar UUID da VM, MAC, timestamp de criacao"]
    E --> H["Mitigacao: haveged, rng-tools, TPM, ATECC608"]
    F --> I["Mitigacao: --device /dev/hwrng ou virtio-rng no host"]

Leitura do diagrama

O caminho da esquerda (sistema desktop/servidor em operação normal) é seguro — o pool de entropia está cheio de eventos de hardware acumulados. Os três caminhos do lado direito são cenários reais de produção onde chaves fracas foram geradas: VMs clonadas (problema documentado em infraestrutura de cloud), dispositivos IoT no primeiro boot (Mining Your Ps and Qs) e contêineres mínimos sem acesso a hardware de entropia.


Como auditar código que gera segredos

A maioria das vulnerabilidades de aleatoriedade fraca é detectável em revisão de código com um checklist simples. Em entrevistas de segurança, demonstrar essa visão de auditoria é um diferencial.

Sinais de alerta em revisão de código

1. Uso de PRNG não-criptográfico para material sensível:

# RUIM — detecção em grep
import random
token = random.randint(0, 2**128)  # PRNG; previsível
 
# BOM
import secrets
token = secrets.token_bytes(16)

2. Semeadura manual com valores previsíveis:

// RUIM — seed fixo torna o CSPRNG determinístico
SecureRandom rng = new SecureRandom(new byte[]{42});
 
// RUIM — seed baseado em tempo; espaço ≈ 2³² se PID + ms
long seed = System.currentTimeMillis() ^ pid;
Random rng = new Random(seed);

3. Reuso de nonce ou IV:

# RUIM — IV fixo em CBC
IV = b'\x00' * 16
cipher = AES.new(key, AES.MODE_CBC, IV)  # todos os plaintexts com o mesmo IV
 
# BOM — IV gerado por mensagem
IV = secrets.token_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, IV)

4. Salt ausente ou constante em hashing de senha:

# RUIM — sem salt; rainbow table funciona
hashlib.sha256(password.encode()).hexdigest()
 
# RUIM — salt constante; ainda permite ataques em paralelo
hashlib.sha256(b"static_salt" + password.encode()).hexdigest()
 
# BOM — bcrypt com salt automático por usuário
bcrypt.hashpw(password.encode(), bcrypt.gensalt())

Ferramentas de análise estática

Ferramentas como Semgrep e Bandit (Python) têm regras específicas para detectar uso de random em contextos sensíveis. Em pipelines de CI, executar:

bandit -r . -t B311  # Detecta uso de random não-criptográfico
semgrep --config=p/secrets  # Regras para detecção de segredos fracos

Não substituem revisão humana, mas capturam os erros mais óbvios.


Requisitos distintos em cada primitivo — tabela de referência rápida

PrimitivoÚnico?Secreto?Imprevisível?Onde vai?Falha se…
Chave simétricaSimSimCofre / KMSGerada com PRNG → recuperável
Nonce (CTR/GCM)SimNão (geralmente)Depende do modoJunto ao ciphertextReusado → two-time pad / key recovery
IV (CBC)SimNãoSimJunto ao ciphertextPrevisível → ataques BEAST / CRIME
Salt (hashing)SimNãoNão obrigatórioJunto ao hashOmitido → rainbow tables voltam
Nonce ECDSA (k)SimSimSimNunca expostoFixo ou repetido → chave privada vaza
Token de sessãoSimSimSimCookie / headerCurto → força bruta; PRNG → previsível

Conexões

Resumo em uma linha

Aleatoriedade criptográfica é entropia empacotada em bits: use sempre um CSPRNG alimentado pelo SO (getrandom, SecureRandom, crypto.randomBytes), entenda os requisitos distintos de nonces, IVs e salts, e nunca confie em fontes de entropia fracas — a história mostra que quem errou aqui perdeu tudo.


Em entrevista

Quando o tema “aleatoriedade” aparece em entrevista de segurança, o sinal que diferencia o candidato senior é ir além de “use uma biblioteca segura” e explicar por quê e o que quebra quando não se faz isso.

Frases de alto impacto (use em inglês em entrevistas internacionais):

  • “Cryptographic security is only as strong as the randomness used to generate keys and nonces — weak entropy collapses the effective key space from 2¹²⁸ to something brute-forceable.”
  • “A CSPRNG must satisfy next-bit unpredictability and state compromise resilience — properties that Mersenne Twister and LCG explicitly do not provide.”
  • “The Debian OpenSSL bug shows that removing two lines of seeding code reduced 294,912 possible RSA keys — math was fine, entropy wasn’t.”
  • “Nonce, IV, and salt sound interchangeable but have distinct requirements: a nonce just needs to be unique, a CBC IV needs to be unpredictable, and a salt just needs to be per-user — confusing them leads to broken systems.”
  • “The PS3 ECDSA break required zero brute force — a fixed nonce k in two signatures lets you solve for the private key with high-school algebra.”

Vocabulário PT → EN:

PTEN
EntropiaEntropy
AleatoriedadeRandomness
Gerador pseudoaleatórioPseudo-random number generator (PRNG)
Gerador criptograficamente seguroCryptographically secure PRNG (CSPRNG)
Número usado uma vezNonce
Vetor de inicializaçãoInitialization vector (IV)
Sal (hashing de senhas)Salt
Fonte de entropiaEntropy source
Pool de entropiaEntropy pool
Rerandomização de estadoState reseed / state rerandomization
SementeSeed
Previsibilidade de próximo bitNext-bit unpredictability
Segredo por encaminhamentoForward secrecy

Lastro