random.go (view raw)
1// Copyright 2011-2014 Dmitry Chestnykh. All rights reserved.
2// Use of this source code is governed by a MIT-style
3// license that can be found in the LICENSE file.
4
5package captcha
6
7import (
8 "crypto/hmac"
9 "crypto/rand"
10 "crypto/sha256"
11 "io"
12)
13
14// idLen is a length of captcha id string.
15// (20 bytes of 62-letter alphabet give ~119 bits.)
16const idLen = 20
17
18// idChars are characters allowed in captcha id.
19var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
20
21// rngKey is a secret key used to deterministically derive seeds for
22// PRNGs used in image and audio. Generated once during initialization.
23var rngKey [32]byte
24
25func init() {
26 if _, err := io.ReadFull(rand.Reader, rngKey[:]); err != nil {
27 panic("captcha: error reading random source: " + err.Error())
28 }
29}
30
31// Purposes for seed derivation. The goal is to make deterministic PRNG produce
32// different outputs for images and audio by using different derived seeds.
33const (
34 imageSeedPurpose = 0x01
35 audioSeedPurpose = 0x02
36)
37
38// deriveSeed returns a 16-byte PRNG seed from rngKey, purpose, id and digits.
39// Same purpose, id and digits will result in the same derived seed for this
40// instance of running application.
41//
42// out = HMAC(rngKey, purpose || id || 0x00 || digits) (cut to 16 bytes)
43//
44func deriveSeed(purpose byte, id string, digits []byte) (out [16]byte) {
45 var buf [sha256.Size]byte
46 h := hmac.New(sha256.New, rngKey[:])
47 h.Write([]byte{purpose})
48 io.WriteString(h, id)
49 h.Write([]byte{0})
50 h.Write(digits)
51 sum := h.Sum(buf[:0])
52 copy(out[:], sum)
53 return
54}
55
56// RandomDigits returns a byte slice of the given length containing
57// pseudorandom numbers in range 0-9. The slice can be used as a captcha
58// solution.
59func RandomDigits(length int) []byte {
60 return randomBytesMod(length, 10)
61}
62
63// randomBytes returns a byte slice of the given length read from CSPRNG.
64func randomBytes(length int) (b []byte) {
65 b = make([]byte, length)
66 if _, err := io.ReadFull(rand.Reader, b); err != nil {
67 panic("captcha: error reading random source: " + err.Error())
68 }
69 return
70}
71
72// randomBytesMod returns a byte slice of the given length, where each byte is
73// a random number modulo mod.
74func randomBytesMod(length int, mod byte) (b []byte) {
75 b = make([]byte, length)
76 maxrb := byte(256 - (256 % int(mod)))
77 i := 0
78 for {
79 r := randomBytes(length + (length / 4))
80 for _, c := range r {
81 if c >= maxrb {
82 // Skip this number to avoid modulo bias.
83 continue
84 }
85 b[i] = c % mod
86 i++
87 if i == length {
88 return
89 }
90 }
91 }
92 panic("unreachable")
93}
94
95// randomId returns a new random id string.
96func randomId() string {
97 b := randomBytesMod(idLen, byte(len(idChars)))
98 for i, c := range b {
99 b[i] = idChars[c]
100 }
101 return string(b)
102}