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

更多教程: