Chaves de API explicadas: tamanho, formato, armazenamento e rotação

Atualizado:

Uma chave de API é uma sequência longa e aleatória que um programa envia em cada requisição para mostrar qual aplicação está chamando. Ao contrário de uma senha, ninguém precisa decorá-la, então ela pode ser tão longa e aleatória quanto for preciso. Ao contrário de um token de login, ela costuma valer até alguém revogá-la. A documentação do Google resume o risco: chaves de API são credenciais ao portador, então quem tem uma pode usá-la.

Esta quarta parte da série sobre segurança percorre a vida inteira de uma chave: quantos bits aleatórios ela precisa ter, em que formato escrevê-la, por que existem prefixos como sk_live_, como enviá-la, o que o servidor deve guardar e como fazer a rotação e a revogação.

Chave de API, senha ou token de acesso?

Os três provam algo para um servidor, e por isso são confundidos com frequência. A diferença está em quem os cria, quanto tempo duram e o que o servidor guarda:

CredencialCriada porValidadeO servidor guarda
Senhauma pessoaaté a pessoa trocá-laum hash lento (Argon2id, scrypt, bcrypt)
Chave de APIum gerador aleatórioaté ser trocada ou revogadaum hash rápido, como SHA-256
Token de acesso (OAuth, JWT)o servidor de login, depois de um loginde minutos a horasuma assinatura para verificar, ou um registro do token

Uma chave de API normalmente identifica uma aplicação ou uma integração, não uma pessoa. Quando uma chave faria o papel de uma pessoa fazendo login, um token de acesso obtido num fluxo de login de verdade costuma ser a ferramenta melhor, como explica a parte anterior, sobre JSON Web Tokens.

Qual deve ser o tamanho de uma chave

O que protege uma chave é o número de bits aleatórios que ela tem, não o tamanho em caracteres. Cada bit dobra o número de chaves possíveis: 128 bits aleatórios permitem cerca de 3,4 × 10³⁸ chaves, então testar nem que seja um milionésimo delas a um bilhão de tentativas por segundo levaria cerca de 10¹⁶ anos, quase um milhão de vezes a idade do universo. Por isso 128 bits é um mínimo sólido, e 256 bits custam só alguns caracteres a mais.

Os bits precisam vir de um gerador aleatório criptográfico. No navegador, é o crypto.getRandomValues(); a MDN avisa que o Math.random() não fornece números criptograficamente seguros e não deve ser usado para nada relacionado a segurança. As linguagens de servidor têm seus equivalentes, como o módulo secrets do Python ou o crypto.randomBytes() do Node.js.

O GitHub aumentou seus tokens OAuth de 160 para 178 bits de entropia quando mudou o formato deles em 2021. Um caso precisa de ainda mais: um segredo que assina JSON Web Tokens com HMAC. O RFC 7518 exige uma chave pelo menos do tamanho da saída do hash: 256 bits para HS256, 384 para HS384 e 512 para HS512.

Formatos: base62, hexadecimal ou base64url

Os bits aleatórios precisam ser escritos como texto. O alfabeto decide quantos caracteres são necessários e onde a chave pode ser colada sem problema:

FormatoCaracteresBits por caractereTamanho para 256 bits
base62a–z, A–Z, 0–95,9543
hex0–9, a–f464
base64urla–z, A–Z, 0–9, - e _643

O base62 virou comum para chaves porque só tem letras e números: nada que uma URL, um terminal ou um arquivo de configuração tratem de forma especial, e um clique duplo seleciona a chave inteira. O hexadecimal é mais longo, mas funciona onde só 0–9 e a–f são aceitos. O base64url, do RFC 4648, é o mais curto, mas o - dele pode interromper a seleção com clique duplo. O Base64 padrão é uma escolha ruim, porque o + e a / precisam ser codificados em URLs.

Escrever base62 corretamente exige cuidado: um byte aleatório tem 256 valores, e 256 não é múltiplo de 62, então um simples byte % 62 deixa os oito primeiros caracteres um pouco mais prováveis. Um bom gerador descarta os bytes a partir de 248, como faz o gerador do nTools.

Prefixos e varredura de segredos

Muitos provedores começam suas chaves com um prefixo fixo e legível. As chaves secretas da Stripe começam com sk_live_ ou sk_test_, as chaves publicáveis com pk_ e as restritas com rk_. Desde 2021, os tokens do GitHub começam com ghp_ nos tokens de acesso pessoal, gho_ nos tokens OAuth e ghs_ nos de servidor para servidor.

Um prefixo não é secreto e não deixa a chave mais forte, mas cumpre três funções úteis. Dá para ver de relance para que serve uma chave e se ela é de teste. O GitHub escolheu o sublinhado porque ele não é um caractere do Base64, então sequências aleatórias como hashes não podem parecer um token por acaso, e porque um clique duplo seleciona o token inteiro. Acima de tudo, um prefixo permite que scanners encontrem chaves vazadas: a varredura de segredos do GitHub percorre todo o histórico do Git de todos os branches e, quando encontra o segredo de um provedor parceiro, avisa esse provedor, que pode revogar a chave.

O GitHub também termina seus tokens com um checksum CRC32 de 32 bits escrito em seis caracteres base62. Ele não acrescenta segurança, mas permite que um scanner descarte sequências aleatórias offline, sem perguntar ao servidor se o token existe.

Envie num cabeçalho, nunca na URL

O RFC 6750, que define os tokens ao portador do OAuth 2.0, diz que os clientes devem enviá-los no cabeçalho Authorization e não colocá-los nas URLs das páginas, porque os endereços acabam no histórico do navegador e nos logs dos servidores. O Google dá o mesmo conselho para as próprias chaves de API e oferece um cabeçalho para elas. O mesmo RFC exige TLS: uma chave enviada por http simples pode ser lida por qualquer um no caminho.

O que o servidor deve guardar

O servidor não precisa guardar a chave em si, só uma forma de reconhecê-la. Guarde um hash da chave, como o SHA-256 dela; quando chegar uma requisição, calcule o hash da chave recebida e procure esse hash. Se o banco de dados vazar, os hashes não servem para chamar a API, e não dá para recuperar as chaves a partir deles.

Senhas precisam de um hash lento porque, como explica o Password Storage Cheat Sheet da OWASP, um hash rápido como o SHA-256 permite que atacantes testem uma quantidade enorme de tentativas em pouco tempo. Esse raciocínio depende de as senhas poderem ser adivinhadas. Uma chave com 256 bits aleatórios não pode, então um único hash rápido basta, e cada requisição continua rápida.

Como só o hash é guardado, a chave completa só pode ser mostrada uma vez, na criação. A Stripe funciona assim com as chaves secretas de produção que você mesmo cria: o painel mostra a chave uma única vez e não consegue revelá-la de novo. Guarde em claro o prefixo e os últimos caracteres se as pessoas precisarem distinguir suas chaves.

Manter as chaves fora do código

Chaves vazadas aparecem principalmente onde os desenvolvedores as colocaram por comodidade. A Stripe avisa que pessoas mal-intencionadas varrem o tempo todo os repositórios públicos em busca de chaves de API, e que mesmo repositórios privados podem ficar expostos. Os conselhos dela, repetidos pelo Google e pela OWASP:

Rotação e revogação

Toda chave deve poder ser trocada em pouco tempo. Fazer a rotação é criar uma chave nova, passar todas as aplicações para ela e depois apagar a antiga. Feita periodicamente, ela limita por quanto tempo uma chave roubada continua funcionando e, como lembra a Stripe, prova que você sabe onde cada chave é usada. O painel da Stripe mantém a chave antiga e a nova funcionando juntas por até sete dias para a troca ser tranquila.

Quando uma chave fica exposta — enviada para um repositório, colada num chat, impressa num log —, revogue-a na hora, mesmo sem ter certeza de que alguém a viu. Apagar o commit não basta: a chave pode já ter sido copiada e continua no histórico do Git e em cada clone. Quando o sistema permitir, dê também uma data de validade às chaves, para que uma chave esquecida pare de funcionar sozinha.

Gerar uma chave

O gerador de chaves de API do nTools cria chaves em base62, hexadecimal ou base64url, de 128 a 512 bits, com prefixo opcional e, se você quiser, o hash SHA-256 para guardar no servidor. Ele usa o crypto.getRandomValues() e funciona inteiramente no seu navegador: as chaves não são enviadas para lugar nenhum.

Gerador de chaves de API

Perguntas frequentes

Posso usar um UUID como chave de API?

Um UUID versão 4 tem só 122 bits aleatórios e um formato fixo e reconhecível, e foi criado como identificador, não como segredo. É melhor gerar uma chave própria com 128 bits ou mais.

Uma chave gerada no navegador é segura?

Sim, se vier do crypto.getRandomValues(), o gerador criptográfico que o navegador usa na própria segurança, e se a página não a enviar para lugar nenhum. O gerador do nTools funciona assim: nenhuma requisição sai da página quando você clica em “Gerar”.

Chaves de API devem expirar?

Quando o sistema permite, sim: uma data de validade limita o estrago de uma chave esquecida ou vazada sem ninguém perceber. Chaves de longa duração são comuns, o que torna a rotação e a revogação rápida ainda mais importantes.

Enviei uma chave para o Git. E agora?

Primeiro revogue ou troque a chave, depois limpe o repositório. Reescrever o histórico depois não deixa a chave segura de novo, porque ela pode já ter sido copiada.

É melhor criptografar a chave em vez de guardar o hash?

Só se o próprio servidor precisar usar a chave depois, por exemplo para chamar outro serviço com ela. Uma chave que só precisa ser verificada deve ser guardada como hash: assim não há nada para descriptografar se o banco vazar.

Anterior da série ← JSON Web Tokens explicados: claims, assinaturas e validação segura

Referências

  1. GitHub — Behind GitHub’s new authentication token formats (April 2021)
  2. GitHub Docs — About secret scanning
  3. GitHub Docs — About secret scanning for partners
  4. Stripe — API keys
  5. Stripe — Best practices for managing secret API keys
  6. Google Cloud — Best practices for managing API keys
  7. RFC 6750 — The OAuth 2.0 Authorization Framework: Bearer Token Usage (October 2012)
  8. RFC 7518 — JSON Web Algorithms (JWA) (May 2015)
  9. RFC 4648 — The Base16, Base32, and Base64 Data Encodings (October 2006)
  10. OWASP — Secrets Management Cheat Sheet
  11. OWASP — Password Storage Cheat Sheet
  12. MDN — Crypto: getRandomValues()
  13. MDN — Math.random()

Guias relacionados