String aleatória segura em Golang usando crypto/rand
Gerar strings aleatórias é extremamente útil. Esse recurso é usado em muitos lugares, como criar chaves para registros em bancos de dados distribuídos sem risco de colisão entre registros, criar IDs de controle de sessão, gerar senhas e muito mais.
Apesar de ser uma tarefa simples, há alguns truques interessantes. Na verdade, computadores não são bons para criar coisas verdadeiramente aleatórias. Eles criam coisas previsíveis, o que não é desejável quando o objetivo é criptografia e segurança.
Por exemplo, seria desastroso se alguém conseguisse adivinhar o ID de controle de sessão de um site. Seria fácil capturar sessões de outros usuários do sistema. Quanto mais imprevisível for a string da chave, melhor.
Felizmente, Go possui um package pronto para gerar chaves de alta qualidade. No exemplo abaixo, usamos o pacote crypto/rand para gerar uma string aleatória de 10 bytes. Em seguida, geramos um hash sha1 dessa string e convertemos o hash para hexadecimal para exibição.
package main
import (
"crypto/rand"
"crypto/sha1"
"fmt"
)
func hash(b []byte) string {
h := sha1.New()
h.Write(b)
sum := h.Sum(nil)
armored := fmt.Sprintf("%x", sum)
return armored
}
func randomString() (string, error) {
b := make([]byte, 10)
_, err := rand.Read(b)
if err != nil {
fmt.Printf("erro: %v", err)
return "", err
}
armored := hash(b)
return armored, err
}
func main() {
s, _ := randomString()
fmt.Printf("string aleatória: %s\n", armored)
}
Esse código atende à maioria das aplicações. O pacote crypto/rand
gera números aleatórios de alta qualidade, e 10 bytes são suficientes para quase tudo. Com alguns ajustes simples, você pode criar strings ainda mais fortes. Basta aumentar o número de bytes e alterar o tipo de hash para um mais adequado.
Por exemplo, eu uso muito sha256 com uma string de 16 bytes. Isso resulta em um número astronômico de combinações possíveis: 2^128
ou 340 trilhões de trilhões de trilhões de combinações.
A chance de uma colisão acidental, ou seja, de gerar a mesma string em um sistema, é minúscula.
Muitas vezes, preciso de strings mais longas. Nesses casos, eu uso o código abaixo.
func randomStringWithLength(length int) (string, error) {
const (
charset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
)
lenCharset := byte(len(charset))
b := make([]byte, length)
rand.Read(b)
for i := 0; i < length; i++ {
b[i] = charset[b[i]%lenCharset]
}
return string(b), nil
}
Alem de permitir passar o tamanho da string de saída você pode definir o charset que deseja usar. No exemplo acima, usei um charset com letras maiúsculas, minúsculas e números. Você pode adicionar ou remover caracteres e simbolos conforme a necessidade.
Outra forma que uso para gerar strings aleatórias é criar um UUID v4 usando o pacote github.com/google/uuid.
import "github.com/google/uuid"
.
.
.
ID := uuid.New().String()
fmt.Println(ID)
Usar UUID é especialmente útil se você utiliza PostgreSQL como base de dados. Você pode usar um campo do tipo uuid, e o PostgreSQL valida a entrada, impedindo que strings que não seguem o padrão UUID sejam inseridas. Além de ser uma camada extra de segurança contra erros de programação, isso também ajuda a evitar ataques de injeção de SQL.
Quase todos os sistemas que desenvolvi, em algum momento, se tornem sistemas distribuídos. Usar UUID como índice do banco de dados é praticamente obrigatório nesses casos.
Vejá o código fonte completo aqui.
Vídeo com a explicação do código: