Pseudo-Random Generators
In OWASP Secure Coding Practices you'll find what seems to be a really complex guideline: "All random numbers, random file names, random GUIDs, and random strings should be generated using the cryptographic module’s approved random number generator when these random values are intended to be un-guessable", so let's talk about "random numbers".
Cryptography relies on some randomness, but for the sake of correctness what most programming languages provide out-of-the-box is a pseudo-random number generator: Go's math/rand is not an exception.
You should carefully read the documentation when it states that "Top-level functions, such as Float64 and Int, use a default shared Source that produces a deterministic sequence of values each time a program is run." (source)
What exactly does it mean? Let's see
package main
import "fmt"
import "math/rand"
func main() {
fmt.Println("Random Number: ", rand.Intn(1984))
}
Running this program several times will lead exactly to the same number/sequence, but why?
$ for i in {1..5}; do go run rand.go; done
Random Number: 1825
Random Number: 1825
Random Number: 1825
Random Number: 1825
Random Number: 1825
Because Go's math/rand is a deterministic pseudo-random number generator like many others they use a source, called a Seed. This Seed is solely responsible for the randomness of the deterministic pseudo-random number generator -- if it is known or predictable, the same will happen to generated number sequence.
We could "fix" this example quite easily by using the math/rand Seed function getting the expected five different values for each program execution, but because we're on Cryptographic Practices section we should follow to Go's crypto/rand package.
package main
import "fmt"
import "math/big"
import "crypto/rand"
func main() {
rand, err := rand.Int(rand.Reader, big.NewInt(1984))
if err != nil {
panic(err)
}
fmt.Printf("Random Number: %d\n", rand)
}
You may notice that running crypto/rand is slower than math/rand but this is expected: the fastest algorithm isn't always the safest. Crypto's rand is also safer to implement; an example of this, is the fact that you CANNOT seed crypto/rand, the library uses OS-randomness for this, preventing developer misuse.
$ for i in {1..5}; do go run rand-safe.go; done
Random Number: 277
Random Number: 1572
Random Number: 1793
Random Number: 1328
Random Number: 1378
If you're curious about how this can be exploited just think what happens if your application creates a default password on user signup, by computing the hash of a pseudo-random number generated with Go's math/rand as shown in the first example?
Yes, you guessed it, you would be able to predict the user's password!