Geração segura de números aleatórios: base da criptografia forte
A geração aleatória segura é crítica para a encriptação. Saiba como crypto.getRandomValues funciona e por que aleatoriedade fraca quebra todo o seu modelo de segurança.
A geração segura de números aleatórios é a base sobre a qual assenta todo o resto da criptografia. Em JavaScript, crypto.getRandomValues(buffer) preenche um TypedArray com bytes aleatórios criptograficamente seguros provenientes do CSPRNG do sistema operativo (/dev/urandom em Linux/macOS, BCryptGenRandom no Windows, SecRandomCopyBytes em iOS/macOS). Não use Math.random() para nada que envolva segurança — é um PRNG do tipo Mulberry32 ou xorshift concebido para velocidade, não para imprevisibilidade, e o seu output é previsível após observar algumas amostras. Um RNG fraco compromete chaves AES, handshakes TLS, unicidade de nonces em GCM, tokens impossíveis de adivinhar, e todos os outros primitivos de segurança que dependem de bits imprevisíveis.
A diferença entre aleatório e aleatório criptográfico
Um PRNG (gerador de números pseudo-aleatórios) produz um fluxo determinístico a partir de uma semente. Dada a semente e o algoritmo, pode reproduzir todos os outputs. Adequado para jogos, simulações e métodos de Monte Carlo. Catastrófico para criptografia.
Um CSPRNG (PRNG criptograficamente seguro) é alimentado a partir de uma fonte de entropia verdadeira (ruído térmico, temporização de interrupções, instruções de hardware RNG como o RDSEED da Intel) e o seu design garante que o output é computacionalmente indistinguível de aleatoriedade verdadeira, e a observação de outputs passados não ajuda a prever outputs futuros.
O JavaScript fornece os dois. Math.random() é um PRNG. crypto.getRandomValues() é um wrapper sobre o CSPRNG do sistema operativo. Uma linha de diferença no código, uma diferença enorme em segurança.
O uso correcto canónico
// Gerar bytes aleatórios suficientes para uma chave AES de 256 bits
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
// Gerar um nonce GCM de 96 bits
const nonce = crypto.getRandomValues(new Uint8Array(12));
// Gerar um salt de 128 bits
const salt = crypto.getRandomValues(new Uint8Array(16));
// Gerar um token aleatório seguro para URL
const tokenBytes = crypto.getRandomValues(new Uint8Array(32));
const token = btoa(String.fromCharCode(...tokenBytes))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
crypto.getRandomValues() é síncrono, preenche o buffer no local e retorna o buffer. O tamanho máximo de pedido é 65.536 bytes numa única chamada (quota imposta pela especificação para prevenir bloqueios). Para material aleatório maior, chame repetidamente.
Equivalentes em Node.js
const { randomBytes, randomFillSync, webcrypto } = require('crypto');
const keyBytes = randomBytes(32); // Retorna um Buffer
// Ou compatível com Web Crypto
const nonce = webcrypto.getRandomValues(new Uint8Array(12));
O randomBytes do Node extrai do mesmo CSPRNG subjacente que a Web Crypto. Use qualquer estilo de API que se adapte ao seu código. Para código isomórfico que corre em ambos os ambientes, webcrypto.getRandomValues corresponde exactamente ao navegador.
Por que o Math.random falha
O V8 (Chrome/Node), SpiderMonkey (Firefox) e JavaScriptCore (Safari) implementam todos Math.random() como um PRNG rápido sem garantias criptográficas. O V8 usa uma variante xorshift128+. Investigadores demonstraram que após observar ~5 outputs, um atacante pode recuperar o estado interno e prever todos os outputs futuros. Em 2015, Mike Pound e colegas reverteram o estado Math.random do V8 em programas de bug bounty reais.
Se usar Math.random() para gerar tokens de sessão, links de reposição de palavra-passe, nonces de encriptação ou IDs de partilha, atacantes que observem alguns deles podem prever os restantes. Não é teórico — é uma classe de erros comum em auditorias.
Usos incorrectos comuns
Alimentar uma biblioteca PRNG com Math.random():
// MAU
const seed = Math.floor(Math.random() * 2**32);
Nada a jusante pode ser mais aleatório do que a semente. Use crypto.getRandomValues(new Uint32Array(1))[0] em vez disso.
Usar Date.now() como entropia: O tempo é adivinhável dentro de janelas apertadas. Mesmo combinado com um pequeno factor aleatório, os timestamps vazam bits suficientes para atacantes.
Criar o seu próprio mixer por XOR de fontes: Não o faça. Os CSPRNGs do sistema operativo já misturam todas as fontes de entropia úteis. Adicionar a sua própria mistura tipicamente reduz a entropia em vez de a aumentar.
Viés de módulo ao gerar intervalos: randomBytes[0] % 10 não está uniformemente distribuído em 0-9 porque 256 não é múltiplo de 10. Para inteiros aleatórios uniformes num intervalo, use rejeição de amostras:
function randomInt(max) {
const range = new Uint32Array(1);
const threshold = 2**32 - (2**32 % max);
do {
crypto.getRandomValues(range);
} while (range[0] >= threshold);
return range[0] % max;
}
Fontes de entropia e preocupações no arranque
Em Linux, /dev/urandom é sempre seguro após o arranque inicial. Durante os primeiros segundos de arranque em sistemas sem hardware RNG, o pool do kernel pode estar com pouca semente. Isto foi explorado no bug Debian OpenSSL de 2008, onde um patch removeu a mistura de entropia e deixou apenas o ID de processo como semente. As chaves geradas nessa janela tinham apenas 2^15 valores possíveis — enumerados em segundos.
Os sistemas modernos alimentam o CSPRNG do kernel a partir de: RDSEED em x86-64 (quando disponível), instruções RNG ARMv8.5-A, ruído térmico de vários periféricos, temporização de interrupções, teclado/rato se interactivo. Em servidores que usam hardware como Intel Ice Lake ou AMD Zen 3+, o CSPRNG é alimentado em microssegundos após o arranque.
Para contentores Docker: o /dev/urandom do anfitrião é passado por defeito. Não é necessária nenhuma acção. Para serverless (AWS Lambda, Cloudflare Workers), o runtime trata da semente de entropia por invocação.
Tokens de sessão e IDs de partilha
Para um serviço de transferência de ficheiros, gera identificadores aleatórios para:
- IDs de ficheiros em URLs (os atacantes não devem conseguir adivinhar IDs válidos)
- Tokens de partilha para links protegidos por palavra-passe
- Tokens CSRF
- Chaves de encriptação (chaves AES por ficheiro)
- Nonces para GCM
Comprimento mínimo: 128 bits (16 bytes) para colisão e impossibilidade de adivinhar, 256 bits (32 bytes) para chaves. A codificação base64url segura para URL adiciona ~33% de comprimento; hex adiciona 100%.
Um token de 32 bytes codificado em base64url tem 43 caracteres e é essencialmente livre de colisões em 2^256.
Testar para RNG fraco
Sinais de que o seu RNG está comprometido ou é fraco:
- Tokens idênticos gerados por diferentes pedidos (colisão no que devia ser um espaço enorme)
- O output passa em testes visuais mas falha nas baterias estatísticas
dieharderouPractRand - Reutilização de semente após reinício do processo — cada deploy usa o mesmo estado inicial
- Chaves geradas caem em padrões (por exemplo, os primeiros 4 bytes variam mas os últimos 28 são idênticos)
Em produção, provavelmente não verá isto a não ser que algo esteja catastroficamente errado. O modo de falha é normalmente silencioso: os ataques apenas se tornam práticos sobre o que devia ser um espaço de busca de 2^256.
Auditoria: cada chamada a Math.random() num código-base deve ser revista. Um grep por Math.random sobre a árvore de código é uma boa verificação de higiene semanal. Converter qualquer chamada relevante para segurança a crypto.getRandomValues demora minutos e previne vulnerabilidades reais.
Strings aleatórias e UUIDs
Para identificadores voltados para o utilizador, crypto.randomUUID() retorna um UUID v4 (122 bits de aleatoriedade) num formato padrão:
const id = crypto.randomUUID();
// "f47ac10b-58cc-4372-a567-0e02b2c3d479"
Suportado no Chrome 92+, Firefox 95+, Safari 15.4+, Node 14.17+. Bom para chaves primárias de base de dados, IDs de pedidos de API e identificadores únicos não críticos para segurança. Use getRandomValues explícito para qualquer coisa que requeira formatos personalizados ou maior entropia.
Em servidores: evite pools de RNG personalizados
Alguns frameworks de servidor oferecem os seus próprios pools aleatórios que afirmam "misturar" o CSPRNG do sistema com entropia ao nível da aplicação. Trate isto com suspeita. A mistura personalizada raramente melhora o output do kernel e pode reduzir silenciosamente a entropia se houver um bug.
Se estiver em Node ou num runtime principal, o crypto.randomBytes integrado está correcto e é rápido. Não o substitua por misturadores de terceiros.
A conclusão
Cada peça de criptografia numa aplicação de transferência de ficheiros depende de bytes aleatórios imprevisíveis. Chaves AES, nonces GCM, salts PBKDF2, tokens de partilha, tokens anti-CSRF, IDs de sessão — todos precisam do mesmo primitivo: crypto.getRandomValues() nos navegadores, crypto.randomBytes() ou webcrypto.getRandomValues em Node. Use esses. Nunca Math.random(). Nunca timestamps. Nunca o seu próprio mixer.
O HexaTransfer deriva cada chave AES por ficheiro, nonce e identificador de URL a partir de crypto.getRandomValues() no cliente. Os IDs de partilha do lado do servidor vêm de crypto.randomBytes. Uma API, comportamento consistente, sem forma de vazar acidentalmente bits previsíveis no sistema.
O primitivo é aborrecido precisamente porque precisa de ser. Aborrecido, correcto e disponível em todo o lado — exactamente o que as bases criptográficas devem ser.
Experimente em hexatransfer.com — gratuito, sem conta, até 10 GB.
Envie arquivos grandes com segurança e criptografia de ponta a ponta
Transfira arquivos de até 10 GB gratuitamente com criptografia de ponta a ponta. Sem necessidade de conta. Seus arquivos são criptografados no navegador antes do envio — ninguém mais pode lê-los.
Enviar um arquivo