【问题标题】:Golang Cryptographic ShuffleGolang 加密洗牌
【发布时间】:2016-05-14 05:15:46
【问题描述】:

我正在尝试在 Go 中实现一个使用 crypto/rand 而不是 math/rand 的字符串 shuffle 函数。 Fisher-Yates Shuffle 需要随机整数,所以我尝试实现该功能,而不必使用依赖于math/bigcrypto/rand Int。以下是我迄今为止提出的最好的方法,但有更好的方法吗?我找不到现有示例这一事实让我想知道是否有充分的理由为什么没有人这样做!

package main

import "crypto/rand"
import "fmt"
import "encoding/binary"

func randomInt(max int) int {
    var n uint16
    binary.Read(rand.Reader, binary.LittleEndian, &n)
    return int(n) % max
}

func shuffle(s *[]string) {
        slice := *s
        for i := range slice {
                j := randomInt(i + 1)
                slice[i], slice[j] = slice[j], slice[i]
        }
        *s = slice
    }

func main() {
        slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
        shuffle(&slice)
        fmt.Println(slice)
}

【问题讨论】:

    标签: go cryptography shuffle


    【解决方案1】:

    n % max 中的数字分布不均。例如,

    package main
    
    import (
        "fmt"
        "math"
    )
    
    func main() {
        max := 7
        size := math.MaxUint8
        count := make([]int, size)
        for i := 0; i < size; i++ {
            count[i%max]++
        }
        fmt.Println(count[:max])
    }
    

    输出:

    [37 37 37 36 36 36 36]
    

    【讨论】:

      【解决方案2】:

      根据收到的 cmets,我认为我可以通过添加一个 uniformInt 函数、填充一个 uint32 而不是 uint16 并删除指向切片的指针来改进我的问题中的示例。

      package main
      
      import "crypto/rand"
      import "fmt"
      import "encoding/binary"
      
      func randomInt() int {
              var n uint32
              binary.Read(rand.Reader, binary.LittleEndian, &n)
              return int(n)
      }
      
      func uniformInt(max int) (r int) {
              divisor := 4294967295 / max // Max Uint32
              for {
                      r = randomInt() / divisor
                      if r <= max {
                              break
                      }
              }
              return
      }
      
      func shuffle(slice []string) {
              for i := range slice {
                      j := uniformInt(i + 1)
                      slice[i], slice[j] = slice[j], slice[i]
              }
      }
      
      func main() {
              slice := []string{"a", "b", "c", "d", "e", "f", "h", "i", "j", "k"}
              shuffle(slice)
              fmt.Println(slice)
      }
      

      【讨论】:

      • 或者你可以只使用Rand.IntnRand.Int31nRand.Int63n中的算法(或者使用math/randcrypto/rand源)。您的 uniformInt 可以在具有 32 位 int 的系统上返回负值,我相当肯定您的整数除法会给输出增加一些偏差,但如果我采用已知的方法,我不需要担心正确地进行数学运算很好的算法。
      【解决方案3】:

      Go 的 math/rand 库有很好的设施,可以从 Source 生成随机数字基元。

      // A Source represents a source of uniformly-distributed 
      // pseudo-random int64 values in the range [0, 1<<63).
      
      type Source interface {
          Int63() int64
          Seed(seed int64)
      }
      

      NewSource(seed int64) 返回内置的确定性 PRNG,但 New(source Source) 将允许满足 Source 接口的任何内容。

      这是一个由crypto/rand 支持的Source 示例。

      type CryptoRandSource struct{}
      
      func NewCryptoRandSource() CryptoRandSource {
          return CryptoRandSource{}
      }
      
      func (_ CryptoRandSource) Int63() int64 {
          var b [8]byte
          rand.Read(b[:])
          // mask off sign bit to ensure positive number
          return int64(binary.LittleEndian.Uint64(b[:]) & (1<<63 - 1))
      }
      
      func (_ CryptoRandSource) Seed(_ int64) {}
      

      你可以这样使用它:

      r := rand.New(NewCryptoRandSource())
      
      for i := 0; i < 10; i++ {
          fmt.Println(r.Int())
      }
      

      math/rand 库有一个正确实现的 Intn() 方法,可确保均匀分布。

      func (r *Rand) Intn(n int) int {
          if n <= 0 {
              panic("invalid argument to Intn")
          }
          if n <= 1<<31-1 {
              return int(r.Int31n(int32(n)))
          }
          return int(r.Int63n(int64(n)))
      }
      
      func (r *Rand) Int31n(n int32) int32 {
          if n <= 0 {
              panic("invalid argument to Int31n")
          }
          if n&(n-1) == 0 { // n is power of two, can mask
              return r.Int31() & (n - 1)
          }
          max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
          v := r.Int31()
          for v > max {
              v = r.Int31()
          }
          return v % n
      }
      
      func (r *Rand) Int63n(n int64) int64 {
          if n <= 0 {
              panic("invalid argument to Int63n")
          }
          if n&(n-1) == 0 { // n is power of two, can mask
              return r.Int63() & (n - 1)
          }
          max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
          v := r.Int63()
          for v > max {
              v = r.Int63()
          }
          return v % n
      }
      

      加密哈希函数也可以包装为 Source 以作为随机的替代方式。

      【讨论】:

      • 我想你希望int64(binary.LittleEndian.Uint64(b[:]) &amp; (1&lt;&lt;63 - 1)) 确保你有一个正数
      • 好消息@JimB。更新了示例。谢谢。
      • 很好,谢谢。值得补充的是,stackoverflow.com/questions/10408646/… 解释了如何导入 crypt/rand 和 math/rand。
      • 而不是&amp; (1&lt;&lt;63 - 1)),您也只需右移一位:int64(binary.LittleEndian.Uint64(b[:]) &gt;&gt; 1)
      猜你喜欢
      • 2021-06-14
      • 2018-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-26
      • 1970-01-01
      • 1970-01-01
      • 2020-09-22
      相关资源
      最近更新 更多