Como gerar senha segura em código (sem Math.random)
Uma senha segura depende de duas coisas: um gerador aleatório criptográfico e um sorteio sem viés entre os caracteres. Este guia explica por que Math.random não serve, calcula a força de uma senha em bits e mostra como gerar senhas em JavaScript, Python e PHP com as funções seguras de cada linguagem.
O gerador certo
Math.random no JavaScript, random no Python e rand no PHP são rápidos, mas previsíveis: quem observa alguns valores pode deduzir os próximos. Para senhas, tokens e códigos de recuperação, use o gerador criptográfico: crypto.getRandomValues no JavaScript, o módulo secrets no Python e random_int no PHP.
Para gerar uma senha agora, sem código, use o gerador de senha, que usa o gerador criptográfico do navegador.
Quão forte é uma senha
A força se mede em bits: tamanho vezes o logaritmo na base 2 do número de caracteres possíveis. Com o alfabeto dos exemplos (71 caracteres), cada caractere vale cerca de 6,15 bits.
- 12 caracteres: cerca de 74 bits.
- 16 caracteres: cerca de 98 bits, um bom padrão para senhas guardadas em gerenciador.
- Aumentar o tamanho rende mais que aumentar o alfabeto: cada caractere a mais soma 6 bits, e dobrar o alfabeto soma só 1 bit por caractere.
O viés do módulo
O erro clássico é pegar um byte aleatório (0 a 255) e fazer byte % tamanhoDoAlfabeto. Com 71 caracteres, 256 não divide por 71: sobram 43 valores, e os 43 primeiros caracteres saem 4 vezes em 256 enquanto os outros saem 3. A senha continua parecendo aleatória, mas fica mais fácil de adivinhar.
A solução é descartar os bytes da sobra, como faz o exemplo em JavaScript. Python e PHP já sorteiam sem viés.
Em JavaScript
const ALFABETO = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789!@#$%&*-_';
function gerarSenha(tamanho = 16, alfabeto = ALFABETO) {
// Descarta bytes acima do maior múltiplo do tamanho do alfabeto: sem isso, os
// primeiros caracteres sairiam mais vezes (o viés do módulo)
const limite = 256 - (256 % alfabeto.length);
let senha = '';
while (senha.length < tamanho) {
// getRandomValues aceita no máximo 65536 bytes por chamada
const bytes = crypto.getRandomValues(new Uint8Array(Math.min(tamanho * 2, 65536)));
for (const b of bytes) {
if (b < limite && senha.length < tamanho) {
senha += alfabeto[b % alfabeto.length];
}
}
}
return senha;
} Funciona no navegador e no Node.js. Os bytes descartados são repostos na próxima volta.
Em Python e PHP
import secrets
ALFABETO = (
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789!@#$%&*-_"
)
def gerar_senha(tamanho: int = 16, alfabeto: str = ALFABETO) -> str:
# secrets.choice usa o gerador seguro do sistema e sorteia sem viés
return "".join(secrets.choice(alfabeto) for _ in range(tamanho)) <?php
const ALFABETO = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' .
'abcdefghijklmnopqrstuvwxyz' .
'0123456789!@#$%&*-_';
function gerarSenha(int $tamanho = 16, string $alfabeto = ALFABETO): string
{
$senha = '';
$max = strlen($alfabeto) - 1;
for ($i = 0; $i < $tamanho; $i++) {
$senha .= $alfabeto[random_int(0, $max)]; // random_int é seguro e sem viés
}
return $senha;
} Cuidados
- Nunca registre a senha gerada em logs, nem a envie por e-mail em texto. Se precisar entregar uma senha, prefira um link de definição de senha com validade curta.
- Exigir "pelo menos um número e um símbolo" pouco acrescenta a uma senha aleatória longa, e pode forçar um novo sorteio. O tamanho importa mais.
- Para guardar a senha no banco, nunca use o texto nem um hash simples. Veja como guardar senhas com hash.
- Se a senha vai ser digitada à mão, tire caracteres parecidos (0 e O, 1, l e I) do alfabeto e compense com um caractere a mais.
Achou um erro neste guia? Avise por e-mail.
Ferramentas para usar junto
Perguntas frequentes
Math.random serve para gerar senha?
Não. O Math.random não é criptográfico: a sequência pode ser prevista a partir de alguns valores. Use crypto.getRandomValues no navegador e no Node.js.
Qual o tamanho ideal de uma senha?
Para senhas aleatórias guardadas em um gerenciador, 16 caracteres ou mais. Com letras, números e alguns símbolos, isso passa de 90 bits, fora do alcance de tentativas por força bruta.
O que é o viés do módulo?
É a diferença de frequência que aparece quando um número aleatório é reduzido com o resto da divisão por um valor que não divide o intervalo por igual. Alguns caracteres passam a sair mais que outros.
Como gerar senha em Python?
Use o módulo secrets: "".join(secrets.choice(alfabeto) for _ in range(16)). Ele usa o gerador seguro do sistema operacional e sorteia sem viés.