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 }