Показаны сообщения с ярлыком случайные последовательности в Go. Показать все сообщения
Показаны сообщения с ярлыком случайные последовательности в Go. Показать все сообщения

воскресенье, 26 апреля 2020 г.

Удобный доступ к crypto/rand для создания случайных чисел в Golang

Go имеет два пакета для случайных чисел:

  • math/rand реализует большой выбор генераторов псевдослучайных чисел.
  • crypto/rand реализует криптографически безопасный генератор псевдослучайных чисел с ограниченным интерфейсом.

Два пакета можно объединить, вызвав rand.New в пакете math/rand с источником, который получает данные из crypto/rand.

import (
    crand "crypto/rand"
    rand "math/rand"

    "encoding/binary"
    "fmt"
    "log"
)

func main() {
    var src cryptoSource
    rnd := rand.New(src)

    // действительно случайное число от 0 до 999
    fmt.Println(rnd.Intn(1000)) 
}

type cryptoSource struct{}

func (s cryptoSource) Seed(seed int64) {}

func (s cryptoSource) Int63() int64 {
    return int64(s.Uint64() & ^uint64(1<<63))
}

func (s cryptoSource) Uint64() (v uint64) {
    err := binary.Read(crand.Reader, binary.BigEndian, &v)
    if err != nil {
        log.Fatal(err)
    }
    return v
}

Предупреждение: crand.Reader возвращает ошибку в случае сбоя основного системного вызова. Например, если он не может прочитать /dev/urandom в системе Unix, или если CryptAcquireContext не работает в системе Windows.


Читайте также:


Создать случайную строку (пароль) в Golang

Случайная строка

Этот код генерирует случайную строку цифр и символов из шведского алфавита (которая включает символы не ASCII å, ä и ö).

rand.Seed(time.Now().UnixNano())
chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ" +
    "abcdefghijklmnopqrstuvwxyzåäö" +
    "0123456789")
length := 8
var b strings.Builder
for i := 0; i < length; i++ {
    b.WriteRune(chars[rand.Intn(len(chars))])
}
str := b.String() // Например "ExcbsVQs"

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

Случайная строка с ограничениями

Этот код генерирует случайную строку ASCII по крайней мере с одной цифрой и одним специальным символом.

rand.Seed(time.Now().UnixNano())
digits := "0123456789"
specials := "~=+%^*/()[]{}/!@#$?|"
all := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
    "abcdefghijklmnopqrstuvwxyz" +
    digits + specials
length := 8
buf := make([]byte, length)
buf[0] = digits[rand.Intn(len(digits))]
buf[1] = specials[rand.Intn(len(specials))]
for i := 2; i < length; i++ {
    buf[i] = all[rand.Intn(len(all))]
}
rand.Shuffle(len(buf), func(i, j int) {
    buf[i], buf[j] = buf[j], buf[i]
})
str := string(buf) // Например "3i[g0|)z"

До версии Go 1.10

В коде до версии Go 1.10 замените вызов rand.Shuffle следующим кодом:

// Тасование Фишера — Йетса
for i := len(buf) - 1; i > 0; i-- { 
    j := rand.Intn(i + 1)
    buf[i], buf[j] = buf[j], buf[i]
}


Читайте также:


Что такое seed (начальное число, семя) в генераторе случайных чисел?

В действительности псевдослучайные числа вовсе не случайны. Они вычисляются с использованием фиксированного детерминированного алгоритма.

Начальное число является отправной точкой для последовательности псевдослучайных чисел. Если вы начинаете с того же семени, вы получаете ту же последовательность. Это может быть очень полезно для отладки.

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

Пример

Этот генератор производит последовательность из 97 различных чисел, затем он запускается снова. seed решает, с какого числа начнется последовательность.

// New возвращает генератор псевдослучайных чисел Rand 
// с заданным начальным числом.
// Каждый раз, когда вы вызываете Rand, 
// вы получаете новое "случайное" число.
func New(seed int) (Rand func() int) {
    current := seed
    return func() int {
        next := (17 * current) % 97
        current = next
        return next
    }
}

func main() {
    rand1 := New(1)
    fmt.Println(rand1(), rand1(), rand1())

    rand2 := New(2)
    fmt.Println(rand2(), rand2(), rand2())
}

Вывод:

17 95 63
34 93 29

Генераторы случайных чисел, которые вы найдете в большинстве языков программирования, работают точно так же, но, конечно, они используют более умную функцию. В идеале вам нужна длинная последовательность с хорошими случайными свойствами, вычисляемая функцией, которая использует только дешевые арифметические операции. Например, вы обычно хотели бы избежать % оператор модуля.


Читайте также:


Генерация случайных чисел, символов и элементов срезов в Golang

Используйте функции rand.Seed и rand.Int63 в пакете math/rand для генерации неотрицательного псевдослучайного числа типа int64:

rand.Seed(time.Now().UnixNano())
n := rand.Int63() // например 4601851300195147788

Аналогично, rand.Float64 генерирует псевдослучайное число с плавающей точкой x, где 0 <= x < 1:

x := rand.Float64() // например 0.49893371771268225

Предупреждение: без первоначального вызова rand.Seed вы будете получать одинаковую последовательность чисел при каждом запуске программы.

Несколько случайных источников

Все функции в пакете math/rand используют один случайный источник.

При необходимости вы можете создать новый генератор случайных чисел типа Rand со своим собственным источником, а затем использовать его методы для генерации случайных чисел:

generator := rand.New(rand.NewSource(time.Now().UnixNano()))
n := generator.Int63()
x := generator.Float64()

Целые числа и символы в заданном диапазоне

Число между a и b

Используйте rand.Intn(m), который возвращает псевдослучайное число n, где 0 <= n < m.

n := a + rand.Intn(b-a+1) // a ≤ n ≤ b

Символ между 'a' и 'z'

c := 'a' + rune(rand.Intn('z'-'a'+1)) // 'a' ≤ c ≤ 'z'

Случайный элемент из среза

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

chars := []rune("AB⌘")
c := chars[rand.Intn(len(chars))] // например '⌘'


Читайте также:


среда, 27 февраля 2019 г.

Go Code Review Comments: Пакет Crypto Rand

Не используйте пакет math/rand для генерации ключей, даже одноразовых. Без заданного семени, генератор полностью предсказуем. Заданный с семенем time.Nanoseconds(), присутствует всего несколько бит энтропии. Вместо этого используйте crypto/rand Reader, и если вам нужен текст, напечатайте в шестнадцатеричной или base64 кодировке:

import (
    "crypto/rand"
    // "encoding/base64"
    // "encoding/hex"
    "fmt"
)

func Key() string {
    buf := make([]byte, 16)
    _, err := rand.Read(buf)
    if err != nil {
        // потеря случайности, никогда не должна произойти
        panic(err)  
    }
    return fmt.Sprintf("%x", buf)
    // or hex.EncodeToString(buf)
    // or base64.StdEncoding.EncodeToString(buf)
}


Читайте также: