All Tools / Blog / Como Gerar uma Senha Aleatória Segura em JavaScript e Python

Como Gerar uma Senha Aleatória Segura em JavaScript e Python

4 min read

A maioria dos geradores de senha que as pessoas escrevem com Math.random() não são seguros. Para uma senha que resiste a ataques de força bruta, você precisa de um gerador de números aleatórios criptograficamente seguro. Este guia mostra como gerar senhas fortes do jeito certo.

O que torna uma senha segura?

Dois fatores determinam a força de uma senha: comprimento e tamanho do conjunto de caracteres. A entropia (em bits) captura os dois:

entropia = log2(tamanho_do_conjunto ^ comprimento)
          = comprimento × log2(tamanho_do_conjunto)

Metas práticas:

  • 128 bits — seguro para a maioria dos usos
  • 80 bits — mínimo para qualquer coisa importante
  • < 40 bits — quebrável com hardware doméstico
Tipo de senha Bits por caractere Comprimento para 128 bits de entropia
Só minúsculas (26 chars) 4,7 28 caracteres
Maiúsculas + minúsculas + dígitos (62 chars) 5,95 22 caracteres
ASCII imprimível completo (94 chars) 6,55 20 caracteres
Palavra Diceware (7.776 palavras) 12,9 10 palavras

Uma senha de 20 caracteres com maiúsculas, minúsculas, dígitos e símbolos tem ~128 bits de entropia.

JavaScript — navegador

Use crypto.getRandomValues() — a única API correta para isso. Nunca use Math.random().

function generatePassword(length = 20, options = {}) {
    const {
        uppercase = true,
        lowercase = true,
        digits = true,
        symbols = true,
    } = options;

    let chars = '';
    if (uppercase) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
    if (lowercase) chars += 'abcdefghijklmnopqrstuvwxyz';
    if (digits)    chars += '0123456789';
    if (symbols)   chars += '!@#$%^&*()-_=+[]{}|;:,.<>?';

    if (!chars) throw new Error('Pelo menos um conjunto de caracteres deve estar habilitado');

    const array = new Uint32Array(length);
    crypto.getRandomValues(array);

    return Array.from(array, x => chars[x % chars.length]).join('');
}

console.log(generatePassword(20));
// "kT9!mP2#xQ7@rL5$nW8^"

Se precisar de uniformidade perfeita (por exemplo, para material de chave criptográfica), use amostragem por rejeição:

function secureSample(chars, length) {
    const result = [];
    while (result.length < length) {
        const bytes = new Uint8Array(length * 2);
        crypto.getRandomValues(bytes);
        for (const b of bytes) {
            if (result.length >= length) break;
            if (b < Math.floor(256 / chars.length) * chars.length) {
                result.push(chars[b % chars.length]);
            }
        }
    }
    return result.join('');
}

JavaScript — Node.js

const crypto = require('crypto');

function generatePassword(length = 20) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
    const bytes = crypto.randomBytes(length);
    return Array.from(bytes, b => chars[b % chars.length]).join('');
}

console.log(generatePassword(24));
// "Xm9kQ!rL2nP@jT5vW8sZ#hK"

Python

O módulo secrets do Python (Python 3.6+) é a ferramenta certa — usa o RNG criptograficamente seguro do sistema operacional.

import secrets
import string

def generate_password(length: int = 20) -> str:
    alphabet = string.ascii_letters + string.digits + string.punctuation
    return ''.join(secrets.choice(alphabet) for _ in range(length))

print(generate_password(20))
# "k!T9mP2#xQ7@rL5$nW8^"

secrets.choice() seleciona usando o CSPRNG do SO. Não use random.choice() — o módulo random não é criptograficamente seguro.

Conjuntos de caracteres personalizados:

import secrets
import string

def generate_password(
    length: int = 20,
    uppercase: bool = True,
    lowercase: bool = True,
    digits: bool = True,
    symbols: bool = True,
) -> str:
    chars = ''
    required = []

    if uppercase:
        chars += string.ascii_uppercase
        required.append(secrets.choice(string.ascii_uppercase))
    if lowercase:
        chars += string.ascii_lowercase
        required.append(secrets.choice(string.ascii_lowercase))
    if digits:
        chars += string.digits
        required.append(secrets.choice(string.digits))
    if symbols:
        sym = '!@#$%^&*()-_=+[]{}|;:,.<>?'
        chars += sym
        required.append(secrets.choice(sym))

    if not chars:
        raise ValueError('Pelo menos um conjunto de caracteres deve estar habilitado')

    remaining = length - len(required)
    pool = required + [secrets.choice(chars) for _ in range(remaining)]

    secrets.SystemRandom().shuffle(pool)
    return ''.join(pool)

print(generate_password(24))

Isso garante pelo menos um caractere de cada conjunto habilitado e depois embaralha — o primeiro caractere não será previsível.

Linha de comando

macOS/Linux — openssl:

# Senha base64 de 20 caracteres
openssl rand -base64 20

# Senha hexadecimal de 20 caracteres
openssl rand -hex 20

# Somente alfanumérico
openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 20

Linux — /dev/urandom:

# Caracteres ASCII imprimíveis
cat /dev/urandom | tr -dc 'a-zA-Z0-9!@#$%^&*' | head -c 20; echo

Python em uma linha:

python3 -c "import secrets, string; print(''.join(secrets.choice(string.ascii_letters + string.digits + '!@#$%^&*') for _ in range(20)))"

Frases-senha (Diceware)

Uma frase-senha de 4–6 palavras aleatórias de uma lista grande pode ser mais segura que uma senha aleatória curta — e muito mais fácil de lembrar.

import secrets

# A lista de palavras EFF grande tem 7776 palavras (6 rolagens de dado)
# Download: https://www.eff.org/files/2016/07/18/eff_large_wordlist.txt

def generate_passphrase(word_count: int = 5, wordlist_path: str = 'eff_large_wordlist.txt') -> str:
    with open(wordlist_path) as f:
        words = [line.split('\t')[1].strip() for line in f if '\t' in line]
    return '-'.join(secrets.choice(words) for _ in range(word_count))

print(generate_passphrase(5))
# "crumpet-waffle-harbor-slime-donkey"

5 palavras da lista EFF dão ~64,6 bits de entropia. 6 palavras dão ~77,5 bits.

O que não usar

  • Math.random() (JavaScript) — usa um PRNG, não um CSPRNG.
  • random.random() / random.choice() (Python) — o mesmo problema.
  • Qualquer sistema que faz hash de um timestamp ou nome de usuário.
  • Senhas com menos de 12 caracteres.

Pontos principais

  • Use crypto.getRandomValues() no JavaScript do navegador, crypto.randomBytes() no Node.js.
  • Use secrets.choice() no Python — não random.choice().
  • Use openssl rand na linha de comando.
  • Mire em 20+ caracteres com conjuntos de caracteres mistos para ~128 bits de entropia.
  • Frases-senha Diceware são uma excelente alternativa — mais longas e mais memoráveis.