Golang密码学洗牌算法

huangapple go评论72阅读模式
英文:

Golang Cryptographic Shuffle

问题

我正在尝试在Go语言中实现一个使用crypto/rand而不是math/rand的字符串洗牌函数。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)
}
英文:

I'm trying to implement a string shuffle function in Go that uses crypto/rand instead of math/rand. The Fisher-Yates Shuffle requires random integers so I've tried to implement that functionality, without having to use crypto/rand Int which relies on math/big. Below is the best I've come up with so far but is there a better method? The fact that I can't find existing examples leads me to wonder if there's a good reason why nobody does this!

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

2: https://golang.org/pkg/crypto/rand/#Int "rand Int"
3: https://golang.org/pkg/math/big/

答案1

得分: 9

Go的math/rand库提供了很好的功能,可以从Source生成随机数值原语。

// Source表示在范围[0, 1<<63)内生成均匀分布的伪随机int64值的源。

type Source interface {
Int63() int64
Seed(seed int64)
}

NewSource(seed int64)返回内置的确定性伪随机数生成器,但是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[:])
// 掩码掉符号位以确保为正数
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是2的幂,可以进行掩码
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是2的幂,可以进行掩码
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,以实现替代的随机性手段。

英文:

Go's math/rand library has good facilities for producing random numerical primitives from a Source.

// A Source represents a source of uniformly-distributed 
// pseudo-random int64 values in the range [0, 1&lt;&lt;63).

type Source interface {
    Int63() int64
    Seed(seed int64)
}

NewSource(seed int64) returns the builtin, deterministic PRNG, but New(source Source) will allow anything that satisfies the Source interface.

Here is an example of a Source that is backed by crypto/rand.

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[:]) &amp; (1&lt;&lt;63 - 1))
}

func (_ CryptoRandSource) Seed(_ int64) {}

You can use it like this:

r := rand.New(NewCryptoRandSource())

for i := 0; i &lt; 10; i++ {
	fmt.Println(r.Int())
}

The math/rand library has a properly implemented Intn() method which ensures a uniform distribution.

func (r *Rand) Intn(n int) int {
    if n &lt;= 0 {
        panic(&quot;invalid argument to Intn&quot;)
    }
    if n &lt;= 1&lt;&lt;31-1 {
        return int(r.Int31n(int32(n)))
    }
    return int(r.Int63n(int64(n)))
}

func (r *Rand) Int31n(n int32) int32 {
    if n &lt;= 0 {
        panic(&quot;invalid argument to Int31n&quot;)
    }
    if n&amp;(n-1) == 0 { // n is power of two, can mask
        return r.Int31() &amp; (n - 1)
    }
    max := int32((1 &lt;&lt; 31) - 1 - (1&lt;&lt;31)%uint32(n))
    v := r.Int31()
    for v &gt; max {
        v = r.Int31()
    }
    return v % n
}

func (r *Rand) Int63n(n int64) int64 {
	if n &lt;= 0 {
		panic(&quot;invalid argument to Int63n&quot;)
	}
	if n&amp;(n-1) == 0 { // n is power of two, can mask
		return r.Int63() &amp; (n - 1)
	}
	max := int64((1 &lt;&lt; 63) - 1 - (1&lt;&lt;63)%uint64(n))
	v := r.Int63()
	for v &gt; max {
		v = r.Int63()
	}
	return v % n
}

Cryptographic hash functions also can be wrapped as a Source for alternate means of randomness.

答案2

得分: 0

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]
英文:

The numbers from n % max are not distributed uniformly. For example,

package main

import (
	&quot;fmt&quot;
	&quot;math&quot;
)

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

Output:

[37 37 37 36 36 36 36]

答案3

得分: -1

根据收到的评论,我认为我可以改进我问题中的示例,通过添加一个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)
}
英文:

Based on the comments received, I think I can improve on the example in my question by adding a uniformInt function, populating a uint32 instead of a uint16 and removing the pointer to the slice.

package main

import &quot;crypto/rand&quot;
import &quot;fmt&quot;
import &quot;encoding/binary&quot;

func randomInt() int {
        var n uint32
        binary.Read(rand.Reader, binary.LittleEndian, &amp;n)
        return int(n)
}

func uniformInt(max int) (r int) {
        divisor := 4294967295 / max // Max Uint32
        for {
                r = randomInt() / divisor
                if r &lt;= 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{&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;, &quot;f&quot;, &quot;h&quot;, &quot;i&quot;, &quot;j&quot;, &quot;k&quot;}
        shuffle(slice)
        fmt.Println(slice)
}

huangapple
  • 本文由 发表于 2016年2月4日 22:28:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/35203635.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定