The math/rand package contains lots of convenient functions, for example to get an integer in a specified range without running into issues caused by just truncating a number from a different distribution and so on. But it's insecure, and we use if for things that benefit from being more secure like session IDs, CSRF tokens and API keys. This implements a math/rand.Source that reads from crypto/rand.Reader, this bridging the gap between them. It also updates our RandomString to use the new source, thus giving us secure session IDs and CSRF tokens. Some future work remains: - Fix API keys by making the generation in the UI use this code as well - Refactor out these things into an actual random package, and audit our use of randomness everywhere I'll leave both of those for the future in order to not muddy the waters on this diff... GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3180
65 lines
1.9 KiB
Go
65 lines
1.9 KiB
Go
// Copyright (C) 2014 The Syncthing Authors.
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package util
|
|
|
|
import (
|
|
"crypto/md5"
|
|
cryptoRand "crypto/rand"
|
|
"encoding/binary"
|
|
"io"
|
|
mathRand "math/rand"
|
|
)
|
|
|
|
// randomCharset contains the characters that can make up a randomString().
|
|
const randomCharset = "2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ"
|
|
|
|
var (
|
|
// defaultSecureSource is a concurrency safe math/rand.Source with a
|
|
// cryptographically sound base.
|
|
defaltSecureSource = newSecureSource()
|
|
|
|
// defaultSecureRand is a math/rand.Rand based on the secure source.
|
|
defaultSecureRand = mathRand.New(defaltSecureSource)
|
|
)
|
|
|
|
func init() {
|
|
// The default RNG should be seeded with something good.
|
|
mathRand.Seed(RandomInt64())
|
|
}
|
|
|
|
// RandomString returns a strongly random string of characters (taken from
|
|
// randomCharset) of the specified length. The returned string contains ~5.8
|
|
// bits of entropy per character, due to the character set used.
|
|
func RandomString(l int) string {
|
|
bs := make([]byte, l)
|
|
for i := range bs {
|
|
bs[i] = randomCharset[defaultSecureRand.Intn(len(randomCharset))]
|
|
}
|
|
return string(bs)
|
|
}
|
|
|
|
// RandomInt64 returns a strongly random int64, slowly
|
|
func RandomInt64() int64 {
|
|
var bs [8]byte
|
|
_, err := io.ReadFull(cryptoRand.Reader, bs[:])
|
|
if err != nil {
|
|
panic("randomness failure: " + err.Error())
|
|
}
|
|
return SeedFromBytes(bs[:])
|
|
}
|
|
|
|
// SeedFromBytes calculates a weak 64 bit hash from the given byte slice,
|
|
// suitable for use a predictable random seed.
|
|
func SeedFromBytes(bs []byte) int64 {
|
|
h := md5.New()
|
|
h.Write(bs)
|
|
s := h.Sum(nil)
|
|
// The MD5 hash of the byte slice is 16 bytes long. We interpret it as two
|
|
// uint64s and XOR them together.
|
|
return int64(binary.BigEndian.Uint64(s[0:]) ^ binary.BigEndian.Uint64(s[8:]))
|
|
}
|