github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/psiphon/common/prng/prng.go (about)

     1  /*
     2   * Copyright (c) 2018, Psiphon Inc.
     3   * All rights reserved.
     4   *
     5   * This program is free software: you can redistribute it and/or modify
     6   * it under the terms of the GNU General Public License as published by
     7   * the Free Software Foundation, either version 3 of the License, or
     8   * (at your option) any later version.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  /*
    21  Package prng implements a seeded, unbiased PRNG that is suitable for use
    22  cases including obfuscation, network jitter, load balancing.
    23  
    24  Seeding is based on crypto/rand.Read and the PRNG stream is provided by
    25  chacha20. As such, this PRNG is suitable for high volume cases such as
    26  generating random bytes per IP packet as it avoids the syscall overhead
    27  (context switch/spinlock) of crypto/rand.Read.
    28  
    29  This PRNG also supports replay use cases, where its intended to reproduce the
    30  same traffic shape or protocol attributes there were previously produced.
    31  
    32  This PRNG is _not_ for security use cases including production cryptographic
    33  key generation.
    34  
    35  Limitations: there is a cycle in the PRNG stream, after roughly 2^64 * 2^38-64
    36  bytes; and the global instance initialized in init() ignores seeding errors.
    37  
    38  It is safe to make concurrent calls to a PRNG instance, including the global
    39  instance, but the caller is responsible for serializing multiple calls as
    40  required for replay.
    41  
    42  PRNG conforms to io.Reader and math/rand.Source, with additional helper
    43  functions.
    44  */
    45  package prng
    46  
    47  import (
    48  	crypto_rand "crypto/rand"
    49  	"crypto/sha256"
    50  	"encoding/base64"
    51  	"encoding/binary"
    52  	"encoding/hex"
    53  	"io"
    54  	"math"
    55  	"math/rand"
    56  	"sync"
    57  	"time"
    58  
    59  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/crypto/Yawning/chacha20"
    60  	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
    61  	"golang.org/x/crypto/hkdf"
    62  )
    63  
    64  const (
    65  	SEED_LENGTH = 32
    66  )
    67  
    68  // Seed is a PRNG seed.
    69  type Seed [SEED_LENGTH]byte
    70  
    71  // NewSeed creates a new PRNG seed using crypto/rand.Read.
    72  func NewSeed() (*Seed, error) {
    73  	seed := new(Seed)
    74  	_, err := crypto_rand.Read(seed[:])
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  	return seed, nil
    79  }
    80  
    81  // NewSaltedSeed creates a new seed derived from an existing seed and a salt.
    82  // A HKDF is applied to the seed and salt.
    83  //
    84  // NewSaltedSeed is intended for use cases where a single seed needs to be
    85  // used in distinct contexts to produce independent random streams.
    86  func NewSaltedSeed(seed *Seed, salt string) (*Seed, error) {
    87  	saltedSeed := new(Seed)
    88  	_, err := io.ReadFull(
    89  		hkdf.New(sha256.New, seed[:], []byte(salt), nil), saltedSeed[:])
    90  	if err != nil {
    91  		return nil, errors.Trace(err)
    92  	}
    93  	return saltedSeed, nil
    94  }
    95  
    96  // PRNG is a seeded, unbiased PRNG based on chacha20.
    97  type PRNG struct {
    98  	rand                   *rand.Rand
    99  	randomStreamMutex      sync.Mutex
   100  	randomStreamSeed       *Seed
   101  	randomStream           *chacha20.Cipher
   102  	randomStreamUsed       uint64
   103  	randomStreamRekeyCount uint64
   104  }
   105  
   106  // NewPRNG generates a seed and creates a PRNG with that seed.
   107  func NewPRNG() (*PRNG, error) {
   108  	seed, err := NewSeed()
   109  	if err != nil {
   110  		return nil, errors.Trace(err)
   111  	}
   112  	return NewPRNGWithSeed(seed), nil
   113  }
   114  
   115  // NewPRNGWithSeed initializes a new PRNG using an existing seed.
   116  func NewPRNGWithSeed(seed *Seed) *PRNG {
   117  	p := &PRNG{
   118  		randomStreamSeed: seed,
   119  	}
   120  	p.rekey()
   121  	p.rand = rand.New(p)
   122  	return p
   123  }
   124  
   125  // NewPRNGWithSaltedSeed initializes a new PRNG using a seed derived from an
   126  // existing seed and a salt with NewSaltedSeed.
   127  func NewPRNGWithSaltedSeed(seed *Seed, salt string) (*PRNG, error) {
   128  	saltedSeed, err := NewSaltedSeed(seed, salt)
   129  	if err != nil {
   130  		return nil, errors.Trace(err)
   131  	}
   132  	return NewPRNGWithSeed(saltedSeed), nil
   133  }
   134  
   135  // GetSeed returns the seed for the PRNG. The returned value must not be mutated.
   136  func (p *PRNG) GetSeed() *Seed {
   137  	// Concurrency note: p.randomStreamSeed is not mutated after creationg, and
   138  	// is safe for concurrent reads. p.randomStreamSeed is reread internally, and
   139  	// so must not be mutated.
   140  	return p.randomStreamSeed
   141  }
   142  
   143  // Read reads random bytes from the PRNG stream into b. Read conforms to
   144  // io.Reader and always returns len(p), nil.
   145  func (p *PRNG) Read(b []byte) (int, error) {
   146  
   147  	p.randomStreamMutex.Lock()
   148  	defer p.randomStreamMutex.Unlock()
   149  
   150  	// Re-key before reaching the 2^38-64 chacha20 key stream limit.
   151  	if p.randomStreamUsed+uint64(len(b)) >= uint64(1<<38-64) {
   152  		p.rekey()
   153  	}
   154  
   155  	p.randomStream.KeyStream(b)
   156  
   157  	p.randomStreamUsed += uint64(len(b))
   158  
   159  	return len(b), nil
   160  }
   161  
   162  func (p *PRNG) rekey() {
   163  
   164  	// chacha20 has a stream limit of 2^38-64. Before that limit is reached,
   165  	// the cipher must be rekeyed. To rekey without changing the seed, we use
   166  	// a counter for the nonce.
   167  	//
   168  	// Limitation: the counter wraps at 2^64, which produces a cycle in the
   169  	// PRNG after 2^64 * 2^38-64 bytes.
   170  	//
   171  	// TODO: this could be extended by using all 2^96 bits of the nonce for
   172  	// the counter; and even further by using the 24 byte XChaCha20 nonce.
   173  	var randomKeyNonce [12]byte
   174  	binary.BigEndian.PutUint64(randomKeyNonce[0:8], p.randomStreamRekeyCount)
   175  
   176  	var err error
   177  	p.randomStream, err = chacha20.NewCipher(
   178  		p.randomStreamSeed[:], randomKeyNonce[:])
   179  	if err != nil {
   180  		// Functions returning random values, which may call rekey, don't
   181  		// return an error. As of github.com/Yawning/chacha20 rev. e3b1f968,
   182  		// the only possible errors from chacha20.NewCipher invalid key or
   183  		// nonce size, and since we use the correct sizes, there should never
   184  		// be an error here. So panic in this unexpected case.
   185  		panic(errors.Trace(err))
   186  	}
   187  
   188  	p.randomStreamRekeyCount += 1
   189  	p.randomStreamUsed = 0
   190  }
   191  
   192  // Int63 is equivilent to math/rand.Int63.
   193  func (p *PRNG) Int63() int64 {
   194  	i := p.Uint64()
   195  	return int64(i & (1<<63 - 1))
   196  }
   197  
   198  // Int63 is equivilent to math/rand.Uint64.
   199  func (p *PRNG) Uint64() uint64 {
   200  	var b [8]byte
   201  	p.Read(b[:])
   202  	return binary.BigEndian.Uint64(b[:])
   203  }
   204  
   205  // Seed must exist in order to use a PRNG as a math/rand.Source. This call is
   206  // not supported and ignored.
   207  func (p *PRNG) Seed(_ int64) {
   208  }
   209  
   210  // FlipCoin randomly returns true or false.
   211  func (p *PRNG) FlipCoin() bool {
   212  	return p.rand.Int31n(2) == 1
   213  }
   214  
   215  // FlipWeightedCoin returns the result of a weighted
   216  // random coin flip. If the weight is 0.5, the outcome
   217  // is equally likely to be true or false. If the weight
   218  // is 1.0, the outcome is always true, and if the
   219  // weight is 0.0, the outcome is always false.
   220  //
   221  // Input weights > 1.0 are treated as 1.0.
   222  func (p *PRNG) FlipWeightedCoin(weight float64) bool {
   223  	if weight > 1.0 {
   224  		weight = 1.0
   225  	}
   226  	f := float64(p.Int63()) / float64(math.MaxInt64)
   227  	return f > 1.0-weight
   228  }
   229  
   230  // Intn is equivilent to math/rand.Intn, except it returns 0 if n <= 0
   231  // instead of panicking.
   232  func (p *PRNG) Intn(n int) int {
   233  	if n <= 0 {
   234  		return 0
   235  	}
   236  	return p.rand.Intn(n)
   237  }
   238  
   239  // Int63n is equivilent to math/rand.Int63n, except it returns 0 if n <= 0
   240  // instead of panicking.
   241  func (p *PRNG) Int63n(n int64) int64 {
   242  	if n <= 0 {
   243  		return 0
   244  	}
   245  	return p.rand.Int63n(n)
   246  }
   247  
   248  // ExpFloat64Range returns a pseudo-exponentially distributed float64 in the
   249  // range [min, max] with the specified lambda. Numbers are selected using
   250  // math/rand.ExpFloat64 and discarding values that exceed max.
   251  //
   252  // If max < min or lambda is <= 0, min is returned.
   253  func (p *PRNG) ExpFloat64Range(min, max, lambda float64) float64 {
   254  	if max <= min || lambda <= 0.0 {
   255  		return min
   256  	}
   257  	var value float64
   258  	for {
   259  		value = min + (rand.ExpFloat64()/lambda)*(max-min)
   260  		if value <= max {
   261  			break
   262  		}
   263  	}
   264  	return value
   265  }
   266  
   267  // Perm is equivilent to math/rand.Perm.
   268  func (p *PRNG) Perm(n int) []int {
   269  	return p.rand.Perm(n)
   270  }
   271  
   272  // Range selects a random integer in [min, max].
   273  // If min < 0, min is set to 0. If max < min, min is returned.
   274  func (p *PRNG) Range(min, max int) int {
   275  	if min < 0 {
   276  		min = 0
   277  	}
   278  	if max < min {
   279  		return min
   280  	}
   281  	n := p.Intn(max - min + 1)
   282  	n += min
   283  	return n
   284  }
   285  
   286  // Bytes returns a new slice containing length random bytes.
   287  func (p *PRNG) Bytes(length int) []byte {
   288  	b := make([]byte, length)
   289  	p.Read(b)
   290  	return b
   291  }
   292  
   293  // Padding selects a random padding length in the indicated
   294  // range and returns a random byte slice of the selected length.
   295  // If maxLength <= minLength, the padding is minLength.
   296  func (p *PRNG) Padding(minLength, maxLength int) []byte {
   297  	return p.Bytes(p.Range(minLength, maxLength))
   298  }
   299  
   300  // Period returns a random duration, within a given range.
   301  // If max <= min, the duration is min.
   302  func (p *PRNG) Period(min, max time.Duration) time.Duration {
   303  	duration := p.Int63n(max.Nanoseconds() - min.Nanoseconds())
   304  	return min + time.Duration(duration)
   305  }
   306  
   307  // Jitter returns n +/- the given factor.
   308  // For example, for n = 100 and factor = 0.1, the
   309  // return value will be in the range [90, 110].
   310  func (p *PRNG) Jitter(n int64, factor float64) int64 {
   311  	a := int64(math.Ceil(float64(n) * factor))
   312  	r := p.Int63n(2*a + 1)
   313  	return n + r - a
   314  }
   315  
   316  // JitterDuration invokes Jitter for time.Duration.
   317  func (p *PRNG) JitterDuration(d time.Duration, factor float64) time.Duration {
   318  	return time.Duration(p.Jitter(int64(d), factor))
   319  }
   320  
   321  // HexString returns a hex encoded random string.
   322  // byteLength specifies the pre-encoded data length.
   323  func (p *PRNG) HexString(byteLength int) string {
   324  	return hex.EncodeToString(p.Bytes(byteLength))
   325  }
   326  
   327  // Base64String returns a base64 encoded random string.
   328  // byteLength specifies the pre-encoded data length.
   329  func (p *PRNG) Base64String(byteLength int) string {
   330  	return base64.StdEncoding.EncodeToString(p.Bytes(byteLength))
   331  }
   332  
   333  var p *PRNG
   334  
   335  func DefaultPRNG() *PRNG {
   336  	return p
   337  }
   338  
   339  func Read(b []byte) (int, error) {
   340  	return p.Read(b)
   341  }
   342  
   343  func Int63() int64 {
   344  	return p.Int63()
   345  }
   346  
   347  func Uint64() uint64 {
   348  	return p.Uint64()
   349  }
   350  
   351  func FlipCoin() bool {
   352  	return p.FlipCoin()
   353  }
   354  
   355  func FlipWeightedCoin(weight float64) bool {
   356  	return p.FlipWeightedCoin(weight)
   357  }
   358  
   359  func Intn(n int) int {
   360  	return p.Intn(n)
   361  }
   362  
   363  func Int63n(n int64) int64 {
   364  	return p.Int63n(n)
   365  }
   366  
   367  func ExpFloat64Range(min, max, lambda float64) float64 {
   368  	return p.ExpFloat64Range(min, max, lambda)
   369  }
   370  
   371  func Perm(n int) []int {
   372  	return p.Perm(n)
   373  }
   374  
   375  func Range(min, max int) int {
   376  	return p.Range(min, max)
   377  }
   378  
   379  func Bytes(length int) []byte {
   380  	return p.Bytes(length)
   381  }
   382  
   383  func Padding(minLength, maxLength int) []byte {
   384  	return p.Padding(minLength, maxLength)
   385  }
   386  
   387  func Period(min, max time.Duration) time.Duration {
   388  	return p.Period(min, max)
   389  }
   390  
   391  func Jitter(n int64, factor float64) int64 {
   392  	return p.Jitter(n, factor)
   393  }
   394  
   395  func JitterDuration(d time.Duration, factor float64) time.Duration {
   396  	return p.JitterDuration(d, factor)
   397  }
   398  
   399  func HexString(byteLength int) string {
   400  	return p.HexString(byteLength)
   401  }
   402  
   403  func Base64String(byteLength int) string {
   404  	return p.Base64String(byteLength)
   405  }
   406  
   407  func init() {
   408  
   409  	// Limitation: if crypto/rand.Read fails, the global PRNG will be
   410  	// initialized with a zero-byte seed. This ensures that non-security-
   411  	// critical use of the global PRNG can proceed.
   412  	//
   413  	// As of Go 1.9, with https://github.com/golang/go/issues/19274, on Linux
   414  	// kernels v3.17+, cryto/rand.Read should now block instead of failing or
   415  	// returning predictable bytes.
   416  	var err error
   417  	p, err = NewPRNG()
   418  	if err != nil {
   419  		p = NewPRNGWithSeed(new(Seed))
   420  	}
   421  }