4

What I want to do:

Based on the contents of a string (like a1b2c3d4e5, for example), I want to generate a bunch of "repeatable" random events. Generating repeatable random numbers in Golang is easy - you just seed the random number generator with a specific number using the rand.Seed() function, and then you are done.

However, the rand.Seed() function takes an int64 as an argument. So I will need to convert a string to a int64 somehow.

What I tried already:

My first thought was to encode the string using base64, and then convert the sequence of bytes to a int64. However, through some basic testing that only seems to support strings of around 7 length. After I add an 8th character, the number just stays the same.

I guess the fundamental problem here is that there are more possible string values than there are possible int64 values.

So what about some kind of hash function? Most hash functions that I know of return a sequence of bytes; I would need some kind of hash function that returns an int64. Perhaps this approach is also misguided?

James
  • 1,394
  • 2
  • 21
  • 31
  • Is there any requirement for the seed to not be easily manipulable/detectable from the input string, for security reasons? Because if not, I can't think of any reason why just taking any hash of the string and applying `mod MAXINT` wouldn't be sufficient. – Adrian Petrescu Jan 17 '18 at 17:51

3 Answers3

8

A hash of the string will do the trick.

You can use:

  • md5 sum of the string (returns 16 bytes)
  • convert the first 8 bytes into a uint64 (binary.BigEndian.Uint64 ignores the rest. It will crash if your input is less than 8 bytes though).

Sample code (playground link):

package main

import (
    "crypto/md5"
    "encoding/binary"
    "fmt"
    "io"
    "math/rand"
)

func main() {
    h := md5.New()
    io.WriteString(h, "And Leon's getting larger!")
    var seed uint64 = binary.BigEndian.Uint64(h.Sum(nil))
    fmt.Println(seed)
    rand.Seed(int64(seed))
    fmt.Println(rand.Int())
}

Prints:

2458341265858962012
792667671

Note: md5 is just an example. You can use any hash that generates at least 8 bytes. eg: sha256. Simply replace md5.New() with sha256.New() (and the import). You can find a nice list of hash examples here.

And a big word of warning: this does not talk about crypto applications at all. I'm assuming this is for something like a user-provided seed (eg: a game seed) for non-crypto purposes.

Marc
  • 19,394
  • 6
  • 47
  • 51
  • Wouldn't CRC64 be a better hashing algorithm than MD5 (or SHA256), since it outputs exactly 64 bits and thus nothing would have to be ignored/truncated? – James Jan 17 '18 at 18:02
  • That would definitely work. If this is for something like a game's random number generator, this would be perfect. However, if this is to seed a random-number generator for crypto-purposes, sha256 would probably be the best of the three mentioned so far. – Marc Jan 17 '18 at 18:03
  • It is for a game. Can you edit in an example for CRC64? – James Jan 17 '18 at 18:07
  • CRC64 is a bit more annoying to use. You can see examples in the link I added to my answer (along with many other hashes in Go). – Marc Jan 17 '18 at 18:10
3

Using a hash function is a good direction. And to produce a value that fits into an int64 simply take the first 8 bytes of the hash value.

For a good hash function, each byte (or rather each bit) of the hash result depends on all of the input bytes, so the first 8 bytes won't get "stuck" after a certain input length.

To convert a slice of 8 bytes to an int64 value, you may use the encoding/binary package.

For example:

digest := ... // calculate digest
seed := int64(binary.BigEndian.Uint64(digest[:8]))

Slicing the digest value digest[:8] can also be omitted, as a BigEndian.Uint64() call will only read the first 8 bytes.

icza
  • 389,944
  • 63
  • 907
  • 827
0

You can try this. You can give a different sequence for generating random events by generating a different seed by using time.

package main

import (
    "bytes"
    "fmt"
    "math/rand"
    "time"
)

var r = rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
func main() {
    fmt.Println(randomString(10))
}

func randomString(l int) string {

    var result bytes.Buffer
    var temp string
    for i := 0; i < l; {
        if string(randInt(65, 90)) != temp {
            temp = string(randInt(65, 90))
            result.WriteString(temp)
            i++
        }
    }
    return result.String()
}

func randInt(min int, max int) int {
    return min + r.Intn(max-min)
}
0example.com
  • 317
  • 2
  • 4