Guia Web Crypto API: encriptação nativa do browser para programadores
Domine a Web Crypto API para aplicações de transferência encriptada. Guia completo de AES-GCM, RSA-OAEP e gestão de chaves no browser.
A Web Crypto API (especificada na recomendação W3C Web Cryptography API, exposta via window.crypto.subtle) é a forma nativa do browser de executar criptografia sem enviar uma biblioteca criptográfica pela rede. Suporta AES-GCM, AES-CBC, AES-CTR, AES-KW, HMAC, RSA-OAEP, RSA-PSS, RSASSA-PKCS1-v1_5, ECDH, ECDSA, HKDF e PBKDF2 em todos os browsers modernos (Chrome 37+, Firefox 34+, Safari 10.1+, Edge 79+). Para aplicações de transferência de ficheiros, isto importa porque cada byte de texto cifrado pode ser gerado do lado do cliente antes do carregamento, com o browser a fornecer uma implementação auditada e em tempo constante. Este guia percorre os primitivos que interessam para a transferência encriptada de ficheiros e os erros que afetam todas as primeiras implementações.
SubtleCrypto é baseado em Promise e assíncrono
Cada método em crypto.subtle retorna uma Promise. Isso é deliberado: as operações criptográficas podem ser descarregadas para hardware ou threads em segundo plano, pelo que forçar o comportamento assíncrono evita que a API seja mal utilizada de formas que bloqueiem a thread principal. Forma do código:
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
true, // extraível
["encrypt", "decrypt"]
);
O segundo argumento (true) marca a chave como extraível, o que significa que pode ser exportada posteriormente via crypto.subtle.exportKey(). Para chaves de longa duração, defina-o como false para manter os bytes brutos inacessíveis a partir do JavaScript. Para chaves que precisem de ser serializadas num fragmento de URL (o padrão HexaTransfer), defina-o como true.
O terceiro argumento é um array de utilizações da chave. Uma chave gerada com ["encrypt"] não pode ser utilizada para decifrar, mesmo que o AES-GCM seja simétrico. Esta separação evita que um fluxo de encriptação comprometido seja abusado para decifrar dados históricos.
AES-GCM para encriptação simétrica de ficheiros
O AES-GCM é o motor de trabalho para o conteúdo de ficheiros. Fornece encriptação autenticada com dados associados (AEAD): texto cifrado mais etiqueta de autenticação mais dados associados opcionais que são autenticados mas não encriptados. Para transferência de ficheiros, use uma chave de 256 bits e um nonce de 96 bits conforme as recomendações NIST SP 800-38D.
const iv = crypto.getRandomValues(new Uint8Array(12)); // nonce de 96 bits
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
key,
plaintext
);
O output inclui uma etiqueta de autenticação GCM de 128 bits anexada ao texto cifrado. A decifração verifica automaticamente a etiqueta e lança um erro se não coincidir. Nunca reutilize um nonce com a mesma chave; as propriedades de segurança do GCM colapsam catastroficamente com a reutilização de nonce (os atacantes conseguem recuperar a chave de autenticação). Para transferência de ficheiros onde cada ficheiro recebe uma chave nova, os nonces aleatórios são seguros; para chaves de longa duração, use um contador.
PBKDF2 para chaves derivadas de palavras-passe
Quando os utilizadores escrevem uma palavra-passe para proteger um ficheiro, não pode usar a palavra-passe diretamente como chave AES. Execute-a primeiro através do PBKDF2:
const passwordKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
"PBKDF2",
false,
["deriveKey"]
);
const aesKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 600000,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
As orientações de hashing de palavras-passe OWASP de 2023 recomendam 600 000 iterações para PBKDF2-SHA-256. Qualquer valor abaixo de 310 000 está abaixo das boas práticas atuais. O salt tem de ser aleatório e armazenado junto ao texto cifrado (não é segredo, apenas tem de ser único).
Para código novo em 2026, considere o Argon2id em vez do PBKDF2. O Argon2 ainda não está na Web Crypto API, mas bibliotecas como argon2-browser ou @noble/hashes fornecem implementações JavaScript/WASM. O Argon2id resiste muito melhor a ataques GPU do que o PBKDF2.
RSA-OAEP para encapsulamento de chaves
Para cenários em que pretende encriptar a chave AES de um ficheiro com a chave pública de um destinatário, use RSA-OAEP. Gere chaves:
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]), // 65537
hash: "SHA-256",
},
true,
["encrypt", "decrypt"]
);
Use modulusLength 4096 para chaves novas; 2048 é aceitável mas começará a ser descontinuado à medida que os prazos quânticos se concretizem. O RSA-OAEP encripta apenas payloads pequenos (no máximo modulusLength/8 - 2*hashLength - 2 bytes), pelo que encapsule uma chave AES de 256 bits em vez de encriptar o conteúdo do ficheiro diretamente.
Para aplicações sensíveis ao desempenho, o ECDH com P-256 ou P-384 é uma alternativa melhor ao RSA. A geração de chaves é uma ordem de grandeza mais rápida e os tamanhos das chaves são muito menores.
Streaming para ficheiros grandes
Um ficheiro de 2 GB não cabe confortavelmente num ArrayBuffer do browser. O Chrome, Firefox e Safari permitem todos ler Ficheiros com File.stream() retornando um ReadableStream, processando em fragmentos. A própria Web Crypto API ainda não tem métodos de encriptação/decifração em streaming (isso é uma lacuna na especificação), pelo que existem duas alternativas:
- Dividir em fragmentos (64 KB ou 1 MB) e encriptar cada um com um nonce único. O destinatário concatena por ordem. Isto perde a verdadeira AEAD sobre o ficheiro completo mas funciona para a maioria dos casos.
- Usar uma biblioteca criptográfica WASM (libsodium.js, @noble/ciphers com backend WASM) que suporta modos AEAD em streaming como XChaCha20-Poly1305 ou AES-GCM-SIV.
Para transferências abaixo de algumas centenas de megabytes, o AES-GCM em buffer funciona bem e é muito mais simples. Acima disso, o streaming torna-se necessário para evitar pressão de memória.
Exportação, importação de chaves e fragmentos de URL
Para fluxos ao estilo HexaTransfer onde a chave viaja no fragmento do URL:
const rawKey = await crypto.subtle.exportKey("raw", aesKey);
const keyBase64 = btoa(String.fromCharCode(...new Uint8Array(rawKey)));
// URL de partilha como https://example.com/f/abc123#key=keyBase64
Os fragmentos de URL nunca são enviados aos servidores nos pedidos HTTP (o browser remove-os). Isto mantém a chave do lado do cliente mesmo que o utilizador partilhe uma ligação. No lado do destinatário:
const keyBase64 = window.location.hash.slice(5); // remover "#key="
const rawKey = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
const key = await crypto.subtle.importKey(
"raw", rawKey, "AES-GCM", false, ["decrypt"]
);
Use codificação base64url (substituir + por -, / por _, remover o preenchimento) para evitar problemas de codificação de URL.
Erros comuns
Usar Math.random() para salts ou IVs. Math.random() não é criptograficamente seguro. Use sempre crypto.getRandomValues().
Reutilizar IVs com a mesma chave. As propriedades de segurança do GCM colapsam completamente com a reutilização de nonce. Os nonces aleatórios de 96 bits colidem após cerca de 2^48 encriptações sob a mesma chave (limite de aniversário). Para transferência de ficheiros onde cada ficheiro tem a sua própria chave, é seguro; para chaves de longa duração, use um contador.
Esquecer HTTPS. crypto.subtle só está disponível em contextos seguros (HTTPS ou localhost). Numa origem insegura, crypto.subtle é undefined.
Armazenar chaves extraíveis no IndexedDB sem proteção. Se precisar de persistir chaves, encapsule-as (por exemplo, com uma chave derivada de uma frase-passe) antes de armazenar. Nunca armazene chaves AES brutas em localStorage, que é acessível a qualquer script na origem.
Confiar em palavras-passe fornecidas pelo utilizador sem PBKDF2. Uma palavra-passe bruta convertida em bytes UTF-8 não é uma chave de 256 bits. Derive sempre.
Não verificar as etiquetas de autenticação. crypto.subtle.decrypt() faz isto automaticamente para AES-GCM, mas se implementar protocolos personalizados por cima, não ignore a verificação.
Nuances de suporte nos browsers
Todos os browsers principais suportam Web Crypto em HTTPS. Algumas particularidades:
- O PBKDF2 do Safari era mais lento do que o Chrome/Firefox durante anos; a diferença foi eliminada no Safari 15.
- O Firefox impõe validação de input mais rigorosa; código que corre no Chrome pode lançar
OperationErrorno Firefox. Teste em ambos. - A Web Crypto em service workers funciona mas exige que o âmbito de registo seja HTTPS.
- O Node.js fornece
require("crypto").webcryptocom uma API compatível desde o Node 15, útil para código criptográfico isomórfico.
Quando usar uma biblioteca em vez da API nativa
A Web Crypto cobre bem os fundamentos mas carece de primitivos modernos como ChaCha20-Poly1305, Argon2, X25519 e Ed25519 (embora o Ed25519 esteja a chegar). Para isso, o libsodium.js (via WASM) ou @noble/ciphers / @noble/curves (JavaScript puro, auditado) são as opções de referência. O HexaTransfer usa os primitivos da Web Crypto diretamente para AES-GCM e PBKDF2 visto que estes cobrem o caminho de transferência de ficheiros sem dependências.
Para um fluxo de transferência encriptada completo, a Web Crypto consegue-o em menos de 100 linhas de código: gerar chave AES, derivar ou aleatória, encriptar ficheiro, carregar texto cifrado, partilhar ligação com chave no fragmento, o destinatário importa a chave e decifra. É tudo.
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