| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- /*
- * 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 equivalent to math/read.Int63.
- func (p *prng) Int63() int64 {
- i := p.Uint64()
- return int64(i & (1<<63 - 1))
- }
- // Int63 is equivalent 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 equivalent 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 equivalent 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 equivalent 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
- }
|