Как сгенерировать случайную строку заданной длины в Go?
Понимание проблемы
Ваш код почти правильный, но есть несколько нюансов. Во-первых, math/rand не является криптостойким генератором — он детерминирован и предсказуем, что не подходит для паролей или токенов. Во-вторых, до Go 1.20 требовался явный вызов rand.Seed(), иначе seed был фиксирован (0). Начиная с Go 1.20 seed устанавливается автоматически, но это всё ещё псевдослучайность. Для безопасных строк используйте crypto/rand.
Решение 1: Базовое с использованием math/rand (для небезопасных целей)
Если вам не нужна криптостойкость (например, для тестов или генерации ID, которые не должны быть секретными), используйте math/rand с явным seed из текущего времени. Вот улучшенная версия вашего кода:
package main
import (
"fmt"
"math/rand"
"time"
)
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func init() {
rand.Seed(time.Now().UnixNano())
}
func RandString(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
func main() {
fmt.Println(RandString(10))
}
- charset — строка со всеми допустимыми символами (буквы + цифры).
- init() — инициализация seed при старте программы.
- rand.Intn() — возвращает случайное число от 0 до len(charset)-1.
Недостаток: math/rand небезопасен для криптографии.
Решение 2: Криптостойкое с использованием crypto/rand (рекомендуется)
Для паролей, токенов или любых данных, где важна безопасность, используйте crypto/rand. Он использует системный энтропийный пул и недетерминирован:
package main
import (
"crypto/rand"
"fmt"
"math/big"
)
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func CryptoRandString(length int) (string, error) {
b := make([]byte, length)
for i := range b {
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
b[i] = charset[idx.Int64()]
}
return string(b), nil
}
func main() {
str, err := CryptoRandString(10)
if err != nil {
panic(err)
}
fmt.Println(str)
}
- crypto/rand.Reader — глобальный источник криптостойкой случайности.
- rand.Int() — безопасно генерирует число в диапазоне [0, n).
- Возвращает ошибку, если системный источник недоступен (крайне маловероятно).
Решение 3: Оптимизированное с пакетом math/rand/v2 (Go 1.22+)
Начиная с Go 1.22 появился пакет math/rand/v2, который быстрее и использует более качественный генератор. Пример:
package main
import (
"fmt"
"math/rand/v2"
)
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func RandStringV2(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.IntN(len(charset))]
}
return string(b)
}
func main() {
fmt.Println(RandStringV2(10))
}
Обратите внимание: rand.IntN() — новый синтаксис, seed не требуется.
Рекомендации
- Для большинства случаев используйте
crypto/rand— это правильно и безопасно. - Если производительность критична (миллионы генераций в секунду) и безопасность не нужна —
math/rand/v2. - Не используйте
math/randбез seed в старых версиях Go. - Добавьте в charset специальные символы (!@#$%^&*), если требуется сложность пароля.
Альтернативный подход: Использование готовых библиотек
Если вы не хотите писать свою реализацию, можно воспользоваться популярными сторонними пакетами, которые уже решают эту задачу с учётом всех нюансов. Например, github.com/thanhpk/randstr или github.com/gofrs/uuid (для UUID).
Пример с randstr:
package main
import (
"fmt"
"github.com/thanhpk/randstr"
)
func main() {
// Строка из букв и цифр длиной 12
fmt.Println(randstr.String(12))
// Только цифры
fmt.Println(randstr.Digit(6))
// Только буквы нижнего регистра
fmt.Println(randstr.Lower(8))
}
Библиотека использует crypto/rand внутри, поэтому безопасна. Это удобно, если в проекте уже есть зависимости или вам нужно больше опций (например, исключить похожие символы, как "l" и "1").
Минус: Добавляет внешнюю зависимость. Для простых проектов лучше написать свою функцию.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги