Escolher um identificador: UUID, IDs curtos e os que deixam escapar informação
Use um UUID v4 para tudo o que seja interno, um v7 quando o identificador for a chave primária de uma tabela grande, e um código curto com um alfabeto restrito quando alguém tiver de o ler em voz alta. Os inteiros sequenciais são a opção a evitar em tudo o que seja público.
As versões de UUID que interessam
Um UUID tem 128 bits. Quatro transportam a versão e dois a variante, por isso nenhuma versão tem os 128 todos para gastar.
| Versão | O que contém | Usar quando |
|---|---|---|
| v1 | Timestamp de 60 bits, sequência de relógio de 14 bits, ID de nó de 48 bits (o endereço MAC) | Só para dados legados |
| v3 / v5 | Um hash MD5 ou SHA-1 de um espaço de nomes e de um nome | São precisos IDs determinísticos |
| v4 | 122 bits aleatórios, mais nada | Por omissão, quando nada deve poder ser inferido |
| v7 | Timestamp de 48 bits em milissegundos, depois 74 bits aleatórios | Chaves em que a ordem de inserção conta |
O v1 é o aviso à navegação. Incorpora a hora de criação e, na maioria das implementações, o endereço MAC da máquina que o gerou, e foi assim que identificadores destes ajudaram a chegar ao autor do vírus Melissa em 1999. O v7, normalizado no RFC 9562 em 2024, publica a hora de criação de propósito: é a sua funcionalidade e o seu custo.
A questão das colisões
Um UUID v4 tem 122 bits aleatórios, logo existem 2^122 deles, aproximadamente 5,3 x 10^36. O que conta é o limite do aniversário, não o tamanho do espaço: a probabilidade de dois quaisquer de n valores coincidirem é cerca de n ao quadrado a dividir por 2^123. Um bilião de UUID gerados traz uma probabilidade de duplicado de cerca de uma em dez biliões, e para chegar à probabilidade de sair cara ou coroa são precisos cerca de 2,7 x 10^18 valores, uns 85 anos a mil milhões por segundo.
"Não é uma preocupação prática" continua a não ser "impossível", e a diferença nunca está na aritmética. Os duplicados vêm do gerador: uma semente fixa, uma máquina virtual clonada com a sua reserva de entropia, uma imagem de contentor que traz consigo o estado guardado do PRNG. A unicidade pertence à fonte aleatória, não ao formato, por isso mantenha a restrição de unicidade na coluna.
Ordenação, localidade no índice e fuga de informação
Os identificadores aleatórios espalham-se. Num índice B-tree cada inserção cai numa página folha aleatória, por isso o conjunto de trabalho passa a ser o índice inteiro em vez da sua extremidade direita, as taxas de acerto da cache descem e as páginas dividem-se. Uma chave que cresce ao longo do tempo acrescenta sempre no mesmo sítio, mantendo quentes as mesmas poucas páginas. É para isso que existem o v7 e o ULID; o ULID são 48 bits de timestamp em milissegundos mais 80 bits aleatórios, escritos como 26 carateres ordenáveis em base32 de Crockford.
O custo é a previsibilidade: um ID ordenado no tempo publica quando o seu registo foi criado, por isso meia dúzia deles revela taxas de inscrição e horas mortas. Se isso importar, use v4 e pague o custo no índice.
Os inteiros sequenciais vão mais longe. /invoices/1041 diz que emitiu cerca de mil faturas, e dois ID separados por uma semana dão a sua taxa de crescimento. Pior, um atacante pode pedir o 1040. Se o servidor devolver esse registo por ele existir e por quem o pede ter sessão iniciada, sem verificar de quem é, isso é uma falha de controlo de acesso, catalogada como referência direta insegura a objetos. Um ID impossível de adivinhar não é uma verificação de autorização, mas impede que alguém percorra a tabela toda.
Os slugs também são identificadores e não deixam escapar nada sobre volume quando bem construídos: acentos reduzidos, tudo em minúsculas, pontuação colapsada em hífenes, como faz o Slug Generator. Mantenha o slug estável depois da publicação; alterá-lo parte as ligações que apontam para lá.
IDs curtos, alfabetos e comprimento
Cada caráter de um código aleatório vale o log2 do tamanho do alfabeto: 5,95 bits em base62, 5 em base32, 4 em hexadecimal. O comprimento e o alfabeto determinam a probabilidade de colisão para um dado volume.
| Comprimento (base62) | Valores distintos | Bits | Probabilidade de colisão em mil milhões de ID |
|---|---|---|---|
| 8 | 2,2 x 10^14 | 43,7 | Certa |
| 10 | 8,4 x 10^17 | 59,5 | Cerca de 45% |
| 12 | 3,2 x 10^21 | 71,5 | Cerca de 1 em 6500 |
| 16 | 4,8 x 10^28 | 95,3 | Cerca de 1 em 100 mil milhões |
Isto pressupõe que cada caráter é aleatório de forma independente; um prefixo como inv_ acrescenta zero bits.
Para tudo o que uma pessoa tenha de ler, base62 é o alfabeto errado. 0 e O, 1, l e I são erros de transcrição à espera de acontecer, e um código que distingue maiúsculas de minúsculas não sobrevive a um telefonema. A base32 de Crockford elimina I, L, O e U e aceita qualquer caixa na entrada, o que compensa o caráter a mais.
De onde vêm os bits aleatórios
Math.random() não é uma fonte criptográfica. O V8 implementa-o com xorshift128+, cujo estado interno se recupera a partir de uma curta série de saídas, e a partir daí todos os valores futuros são previsíveis. Serve para baralhar uma demonstração ou para um valor de preenchimento. Não serve para um identificador de sessão, uma chave de API, uma ligação de reposição ou um token.
// Browsers, Node 19+, Deno and Bun
const id = crypto.randomUUID();
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
Noutros sítios: crypto.randomBytes em Node, secrets.token_urlsafe em Python, crypto/rand em Go, SecureRandom em Java, random_bytes em PHP. O UUID Generator produz valores v4 criptograficamente aleatórios e o Password Generator usa a mesma API do navegador; o Random Numbers serve para amostragem, não para segredos.
Uma armadilha para quem constrói o seu próprio gerador: mapear um byte aleatório para um alfabeto de 62 carateres com % 62 enviesa o resultado a favor dos primeiros oito carateres; use amostragem por rejeição.
Dados de preenchimento e de exemplo
O lorem ipsum é Cícero desfigurado, e sobrevive precisamente por ser ilegível: um texto que parece linguagem sem ser linguagem permite julgar um layout pela forma e pelo comprimento das linhas em vez de o julgar pelas frases, e é isso que o Lorem Ipsum Generator produz. Troque-o por texto realista antes da aprovação final, porque o latim de preenchimento esconde o transbordo que um título verdadeiro vai provocar.
Os dados fabricados obedecem a uma regra: nunca podem ser confundidos com os verdadeiros.
- Os números de cartão vêm da gama de teste publicada por um fornecedor de pagamentos, ou então falham de imediato a verificação de Luhn. Um número aleatório de 16 dígitos que passe no Luhn pertence a alguém.
- Os identificadores nacionais usam gamas reservadas. Os números da Segurança Social dos Estados Unidos começados por 000, 666 ou 900 a 999 nunca são atribuídos.
- Use
example.come os domínios equivalentes, reservados pelo RFC 2606, e o bloco de documentação 192.0.2.0/24.
Os dados de teste chegam à produção mais vezes do que se espera: um script de povoamento apontado à base de dados errada, um marcador esquecido num modelo de email enviado a toda a lista. Os registos falsos devem parecer obviamente falsos, para que alguém repare num deles antes de o apoio ao cliente agir com base nele.
Códigos QR em resumo
Um código QR é uma grelha de módulos, com versões de 1 a 40, de 21x21 até 177x177. O comprimento da carga determina a versão e, para um tamanho de impressão fixo, uma versão mais alta significa módulos mais pequenos, o que exige melhor impressão e uma leitura mais próxima. Encurte o URL antes de o dar ao QR Code Generator e note que o modo alfanumérico só aceita maiúsculas, pelo que HTTPS://EXAMPLE.COM codifica mais pequeno do que a forma em minúsculas.
A correção de erros tem quatro níveis, que recuperam cerca de 7% (L), 15% (M), 25% (Q) e 30% (H) de um código danificado. A redundância custa capacidade, por isso um nível mais alto empurra a mesma carga para uma versão mais densa. O M é a predefinição habitual; escolha Q ou H para impressão pequena, superfícies curvas ou um logótipo ao centro, que funciona porque a redundância o absorve.
Há dois requisitos físicos que decidem se um código é lido. A zona de silêncio é uma margem livre de quatro módulos de cada lado, usada pelo descodificador para encontrar o limite, por isso um código encostado a uma moldura ou a uma fotografia falha muitas vezes. O contraste tem de ser genuinamente escuro sobre genuinamente claro: azul pálido sobre branco é uma falha comum, e muitos descodificadores recusam códigos invertidos.