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)
}

Testar no Golang Playground

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:

Cesar Gimenes

Última modificação