Привет, Хабр!
В версии Go 1.22 пакет math/rand/v2
претерпел значительные изменения, а в частности - переход на ChaCha8Rand. Этот новый генератор представляет собой модификацию широко известного и проверенного временем шифра ChaCha8, который используется в протоколах TLS и SSH.
Немного про сам генератор
ChaCha8Rand основывается на алгоритме ChaCha8, который сам по себе является облегченной версией шифра ChaCha20. ChaCha8 выполняет восемь раундов перестановок и замен ключа и блока данных, что гарантирует высокий уровень энтропии и защиту от различных криптографических атак.
ChaCha8Rand принимает 32-байтный ключ, его называют семенем, который используется в качестве начальной точки для генерации случайных чисел. Ключ устанавливает начальное состояние алгоритма, которое затем преобразуется в псевдослучайное состояние для генерации последовательности чисел.
ChaCha8Rand генерирует 64-битные псевдослучайные числа путем прямого вызова алгоритма ChaCha8. Внутри он инициализирует ChaCha8 с заданным ключом и использует блоки данных, которые алгоритм создает при каждом запросе нового случайного значения. Процесс итеративный: каждый вызов генератора ChaCha8Rand изменяет внутреннее состояние и формирует новое случайное число.
ChaCha8Rand поддерживает функции сериализации и десериализации состояния, что позволяет сохранять и восстанавливать текущее состояние генератора.
Реализация ChaCha8Rand в Go
В пакете math/rand/v2
, ChaCha8 представлен структурой ChaCha8
, которая хранит внутреннее состояние генератора и предоставляет методы для управления им. Состояние инициализируется 32-байтным семенем, передаваемым в функцию NewChaCha8
, а сам процесс генерации числа осуществляется методом Uint64()
, который возвращает следующее 64-битное псевдослучайное число.
Основные функции и методы, которые используют ChaCha8Rand, включают:
IntN и Int64N: функции генерируют псевдослучайное целое число в заданном диапазоне. Например,
IntN(n int) int
возвращает случайное число от 0 до n-1. Они обеспечивают равномерное распределение значений в указанном диапазоне.Float64: генерирует псевдослучайное число с плавающей точкой между 0.0 и 1.0, не включая 1.0.
Perm: создаёт случайную перестановку чисел от 0 до n-1.
Кроме того, в math/rand/v2
реализованы новые методы MarshalBinary и UnmarshalBinary для объектов, которые реализуют интерфейс Source, включая ChaCha8.
Генератор создается путем вызова NewChaCha8
с 32-байтным массивом начальных значений. Если массив не заполнен случайными данными, генератор не сможет обеспечить криптографическую стойкость.
После инициализации генератора метод Uint64()
возвращает следующее псевдослучайное 64-битное число на основе внутреннего состояния. Этот метод использует ChaCha8 для выполнения восемь раундов перестановок и замен.
Примеры
Инициализация генератора и генерация чисел:
package main
import (
"fmt"
"math/rand/v2"
)
func main() {
// создаем 32-байтное семя для инициализации генератора
seed := [32]byte{'s', 'o', 'm', 'e', 'k', 'e', 'y', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f'}
// инициализируем новый генератор ChaCha8
generator := rand.NewChaCha8(seed)
// получаем несколько случайных чисел
fmt.Println(generator.Uint64())
fmt.Println(generator.Uint64())
}
Генерация криптографически безопасного случайного числа:
package main
import (
"fmt"
"math/rand/v2"
)
func main() {
seed := [32]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v'}
generator := rand.NewChaCha8(seed)
fmt.Println("Secure random number:", generator.Uint64())
}
Генерация случайной строки для токенов или сессий:
package main
import (
"fmt"
"math/rand/v2"
)
func randomBytes(n int) []byte {
seed := [32]byte{'s', 'e', 'e', 'd', 'p', 'h', 'r', 'a', 's', 'e', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm'}
generator := rand.NewChaCha8(seed)
b := make([]byte, n)
for i := range b {
b[i] = byte(generator.Uint32() % 256)
}
return b
}
func main() {
fmt.Println("Random session token:", randomBytes(16))
}
Использование в шифровании:
package main
import (
"crypto/cipher"
"fmt"
"golang.org/x/crypto/chacha20"
"math/rand/v2"
)
func main() {
seed := [32]byte{'k', 'e', 'y', 'p', 'h', 'r', 'a', 's', 'e', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'}
generator := rand.NewChaCha8(seed)
nonce := [12]byte{} // нормально было бы использовать случайные значения
key := [32]byte{}
for i := range key {
key[i] = byte(generator.Uint32() % 256)
}
cipher, err := chacha20.NewUnauthenticatedCipher(key[:], nonce[:])
if err != nil {
panic(err)
}
plaintext := []byte("This is a secret message.")
ciphertext := make([]byte, len(plaintext))
cipher.XORKeyStream(ciphertext, plaintext)
fmt.Println("Encrypted:", ciphertext)
fmt.Println("Decrypted message:")
cipher.XORKeyStream(ciphertext, ciphertext) // расшифровка производится тем же способом
fmt.Println(string(ciphertext))
}
Сравнение ChaCha8Rand с другими генераторами
Составили табличку:
Критерий | ChaCha8Rand | PCG | Rand (Go) |
---|---|---|---|
Криптографическая безопасность | Высокая (256 бит) | Подходит не для всех задач и в целом низкая | Низкая |
Производительность | Высокая на | Высокая, особенно в микробенчмарках | Средняя |
Универсальность | Подходит для криптографических приложений | Ограниченное использование в криптографии | Не рекомендуется для криптографических приложений |
Методы ускорения | Не требуется | Доступны методы "jump ahead" для быстрого пропуска значений | Отсутствуют |
Размер состояния | 512 бит | 64 или 128 бит | Зависит от реализации |
Ожидается, что сообщество Golang предложит дополнительные стратегии использования этого инструмента, будем следить за новостями!
Больше про языки программирования эксперты OTUS рассказывают в рамках практических онлайн-курсов. С полным каталогом курсов можно ознакомиться по ссылке.