在Go中生成隨機數和字符串

儘管完全隨機是不可能的,我們仍然可以在計算機上使用偽隨機數。

我們可以有常規的偽隨機數,以及圖像安全偽隨機數。

讓我們看看如何在Go中做到這一點。

偽隨機數

math/rand包裹Go標準庫提供的內容使我們偽隨機數生成器(PRNG), 也被稱為確定性隨機位發生器

與所有偽數字生成器一樣,通過math/rand不是很隨機默認情況下是確定性的每次都會打印相同的值

例如,嘗試運行此代碼,其中介紹了rand.Intn(n),返回介於之間的隨機數0n - 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經過Unix納米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()計算字節數,但並非所有字符都只佔用Unicode中的一個字節-請參見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)) }

加密級隨機數

去還提供了加密安全的偽隨機數生成器(CSPRNG)在標準庫包中crypto.rand

所以您可能會問,為什麼我還要使用由提供的偽數隨機生成器庫?math/rand反而?好吧,這取決於用例。math/rand快多了適用於不需要加密級別或與安全性相關的隨機數據生成的應用程序。crypto.rand適用於安全且可加密的用途,但速度較慢。

它應該用於什麼?例如,生成密碼,CSRF令牌,會話密鑰或與安全性遠程相關的任何內容。

它不依賴於當前時間,就像我們在前面的示例中所做的那樣math/rand,而是使用操作系統CSPRNG API:Windows上的CryptGenRandom API和/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) }

我正在從中獲取代碼示例馬特·西爾弗洛克(Matt Silverlock):您可以使其更通用,並創建一個隨機字節生成函數

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

更多教程: