Генерация случайных чисел и строк в Go

Покаполностью случайный, на самом деле невозможно, мы все еще можем иметь псевдослучайные числа на компьютерах.

Мы можем иметьобычныйпсевдослучайные числа икриптографически безопасныйпсевдослучайные числа.

Посмотрим, как это сделать в Go.

Псевдослучайные числа

Вmath/randупаковкапредоставленный стандартной библиотекой Go дает намгенераторы псевдослучайных чисел (PRNG), также называемыйдетерминированные генераторы случайных битов.

Как и во всех генераторах псевдо-чисел, любое число, сгенерированное с помощьюmath/randявляетсяне совсем случайнопо умолчанию как детерминированныйон всегда будет печатать одно и то же значение каждый раз.

В качестве примера попробуйте запустить этот код, который вводитrand.Intn(n), который возвращает случайное число между0иn - 1.

package main

import ( “fmt” “math/rand” )

func main() { fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) }

детская площадка

Вы всегда будете видеть одну и ту же последовательность при каждом запуске программы. Случайное число меняется внутри программы, но каждый раз, когда вы ее запускаете, вы получаете один и тот же результат:

11
27
17
29
1

Это потому, что по умолчанию семя всегда одно и то же, число1. Чтобы действительно получить случайное число, вам нужно предоставить уникальноесемядля вашей программы. Вы действительно хотите не забыть о заполнении, а вместо этого правильно заполнить наш генератор псевдочумов. Как?

Использоватьrand.Seed()перед звонком в любойmath/randметод, передаваяint64ценить. Вам просто нужно один раз засеять свою программу, а не каждый раз, когда вам нужно случайное число. Наиболее часто используемым начальным значением является текущее время, преобразованное вint64кUnixNanoсrand.Seed(time.Now().UnixNano()):

package main

import ( “fmt” “math/rand” “time” )

func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) fmt.Println(rand.Intn(30)) }

Помните, что из-за изолированности Go Playground всегда начинается с одного и того же времени, поэтому этот код не будет работать должным образом. Попробуйте это в реальной среде, и числа, которые раньше не менялись, теперь будут печататься по-разному при каждом запуске программы.

Некоторые распространенные примеры перечислены ниже для удобства повторного использования:

Сгенерировать случайное целое число

package main

import ( “fmt” “math/rand” “time” )

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomInt(1, 11)) //get an int in the 1…10 range }

Сгенерировать случайную строку

package main

import ( “fmt” “math/rand” “time” )

// Returns an int >= min, < max func randomInt(min, max int) int { return min + rand.Intn(max-min) }

// Generate a random string of A-Z chars with len = l func randomString(len int) string { bytes := make([]byte, len) for i := 0; i < len; i++ { bytes[i] = byte(randomInt(65, 90)) } return string(bytes) }

func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomString(10)) // print 10 chars }

Вернет 10 символов в верхнем регистре. Изменять

bytes[i] = byte(randomInt(65, 90))

к

bytes[i] = byte(randomInt(97, 122))

только для строчных букв.

Если вместо этого вы хотите иметь пул определенных символов для выбора, используйте

package main

import ( “fmt” “math/rand” “time” )

var pool = “_:$%&/()”

// Generate a random string of A-Z chars with len = l func randomString(l int) string { bytes := make([]byte, l) for i := 0; i < l; i++ { bytes[i] = pool[rand.Intn(len(pool))] } return string(bytes) }

func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomString(1000)) }

Изменятьlen(pool)кutf8.RuneCountInString(pool)если вы используете строки, отличные от ascii, какlen()считает байты, но не все символы занимают только один байт в Юникоде - см.https://stackoverflow.com/questions/12668681/how-to-get-the-number-of-characters-in-a-string.

Сгенерируйте случайный массив целых чисел

package main

import ( “fmt” “math/rand” “time” )

func randomArray(len int) []int { a := make([]int, len) for i := 0; i <= len-1; i++ { a[i] = rand.Intn(len) } return a }

func main() { rand.Seed(time.Now().UnixNano()) fmt.Println(randomArray(10)) }

Случайные числа крипто-уровня

Go также предоставляетКриптографически безопасный генератор псевдослучайных чисел (CSPRNG)в стандартном пакете библиотекиcrypto.rand

Так что вы можете спросить, зачем мне вообще использовать библиотеку генератора случайных псевдо-чисел, предоставленнуюmath/randвместо? Ну, это зависит от варианта использования.math/randявляетсянамного быстреедля приложений, которым не требуется генерация случайных данных на уровне криптографии или безопасности.crypto.randподходит для безопасного и криптографического использования, но работает медленнее.

Для чего его использовать? Например, создание паролей, токенов CSRF, ключей сеанса или чего-либо, удаленно связанного с безопасностью.

Он не зависит от текущего времени, как мы делали в предыдущих примерах вmath/rand, но вместо этого он использует API-интерфейсы CSPRNG операционной системы: API-интерфейс CryptGenRandom в Windows и/dev/urandom/на всех остальных (Linux, OSX, * nix)

Вы получаете 256 случайных байтов напрямую с

package main

import ( “crypto/rand” “fmt” )

func main() { key := [256]byte{} _, err := rand.Read(key[:]) if err != nil { panic(err) } fmt.Println(key) }

Я беру образец кода изМэтт Сильверлок: вы можете сделать его более общим и создать функцию генерации случайных байтов

// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
    b := make([]byte, n)
    _, err := rand.Read(b)
    // Note that err == nil only if we read len(b) bytes.
    if err != nil {
        return nil, err
    }
<span style="color:#66d9ef">return</span> <span style="color:#a6e22e">b</span>, <span style="color:#66d9ef">nil</span>

}

и используя это, функцию генерации случайной строки,

// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomString(s int) (string, error) {
    b, err := GenerateRandomBytes(s)
    return base64.URLEncoding.EncodeToString(b), err
}

Больше руководств по go: