1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
|
/*
* Copyright (c) 2019, Psiphon Inc.
* All rights reserved.
*
* Released under utls licence:
* https://github.com/refraction-networking/utls/blob/master/LICENSE
*/
// This code is a pared down version of:
// https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/158caea562287284cc3fa5fcd1b3c97b1addf659/psiphon/common/prng/prng.go
package tls
import (
crypto_rand "crypto/rand"
"encoding/binary"
"io"
"math"
"math/rand"
"sync"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/sha3"
)
const (
PRNGSeedLength = 32
)
// PRNGSeed is a PRNG seed.
type PRNGSeed [PRNGSeedLength]byte
// NewPRNGSeed creates a new PRNG seed using crypto/rand.Read.
func NewPRNGSeed() (*PRNGSeed, error) {
seed := new(PRNGSeed)
_, err := crypto_rand.Read(seed[:])
if err != nil {
return nil, err
}
return seed, nil
}
// newSaltedPRNGSeed creates a new seed derived from an existing seed and a
// salt. A HKDF is applied to the seed and salt.
//
// newSaltedPRNGSeed is intended for use cases where a single seed needs to be
// used in distinct contexts to produce independent random streams.
func newSaltedPRNGSeed(seed *PRNGSeed, salt string) (*PRNGSeed, error) {
saltedSeed := new(PRNGSeed)
_, err := io.ReadFull(
hkdf.New(sha3.New256, seed[:], []byte(salt), nil), saltedSeed[:])
if err != nil {
return nil, err
}
return saltedSeed, nil
}
// prng is a seeded, unbiased PRNG based on SHAKE256. that is suitable for use
// cases such as obfuscation. Seeding is based on crypto/rand.Read.
//
// This PRNG is _not_ for security use cases including production cryptographic
// key generation.
//
// It is safe to make concurrent calls to a PRNG instance.
//
// PRNG conforms to io.Reader and math/rand.Source, with additional helper
// functions.
type prng struct {
rand *rand.Rand
randomStreamMutex sync.Mutex
randomStream sha3.ShakeHash
}
// newPRNG generates a seed and creates a PRNG with that seed.
func newPRNG() (*prng, error) {
seed, err := NewPRNGSeed()
if err != nil {
return nil, err
}
return newPRNGWithSeed(seed)
}
// newPRNGWithSeed initializes a new PRNG using an existing seed.
func newPRNGWithSeed(seed *PRNGSeed) (*prng, error) {
shake := sha3.NewShake256()
_, err := shake.Write(seed[:])
if err != nil {
return nil, err
}
p := &prng{
randomStream: shake,
}
p.rand = rand.New(p)
return p, nil
}
// newPRNGWithSaltedSeed initializes a new PRNG using a seed derived from an
// existing seed and a salt with NewSaltedSeed.
func newPRNGWithSaltedSeed(seed *PRNGSeed, salt string) (*prng, error) {
saltedSeed, err := newSaltedPRNGSeed(seed, salt)
if err != nil {
return nil, err
}
return newPRNGWithSeed(saltedSeed)
}
// Read reads random bytes from the PRNG stream into b. Read conforms to
// io.Reader and always returns len(p), nil.
func (p *prng) Read(b []byte) (int, error) {
p.randomStreamMutex.Lock()
defer p.randomStreamMutex.Unlock()
// ShakeHash.Read never returns an error:
// https://godoc.org/golang.org/x/crypto/sha3#ShakeHash
_, _ = io.ReadFull(p.randomStream, b)
return len(b), nil
}
// Int63 is equivilent to math/read.Int63.
func (p *prng) Int63() int64 {
i := p.Uint64()
return int64(i & (1<<63 - 1))
}
// Int63 is equivilent to math/read.Uint64.
func (p *prng) Uint64() uint64 {
var b [8]byte
p.Read(b[:])
return binary.BigEndian.Uint64(b[:])
}
// Seed must exist in order to use a PRNG as a math/rand.Source. This call is
// not supported and ignored.
func (p *prng) Seed(_ int64) {
}
// FlipWeightedCoin returns the result of a weighted
// random coin flip. If the weight is 0.5, the outcome
// is equally likely to be true or false. If the weight
// is 1.0, the outcome is always true, and if the
// weight is 0.0, the outcome is always false.
//
// Input weights > 1.0 are treated as 1.0.
func (p *prng) FlipWeightedCoin(weight float64) bool {
if weight > 1.0 {
weight = 1.0
}
f := float64(p.Int63()) / float64(math.MaxInt64)
return f > 1.0-weight
}
// Intn is equivilent to math/read.Intn, except it returns 0 if n <= 0
// instead of panicking.
func (p *prng) Intn(n int) int {
if n <= 0 {
return 0
}
return p.rand.Intn(n)
}
// Int63n is equivilent to math/read.Int63n, except it returns 0 if n <= 0
// instead of panicking.
func (p *prng) Int63n(n int64) int64 {
if n <= 0 {
return 0
}
return p.rand.Int63n(n)
}
// Intn is equivilent to math/read.Perm.
func (p *prng) Perm(n int) []int {
return p.rand.Perm(n)
}
// Range selects a random integer in [min, max].
// If min < 0, min is set to 0. If max < min, min is returned.
func (p *prng) Range(min, max int) int {
if min < 0 {
min = 0
}
if max < min {
return min
}
n := p.Intn(max - min + 1)
n += min
return n
}
|