forked from dchest/captcha
-
Notifications
You must be signed in to change notification settings - Fork 0
/
random.go
127 lines (110 loc) · 3.23 KB
/
random.go
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
// Copyright 2011-2014 Dmitry Chestnykh. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package captcha
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
)
// idLen is a length of captcha id string.
// (20 bytes of 62-letter alphabet give ~119 bits.)
const idLen = 20
// idChars are characters allowed in captcha id.
var idChars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")
var captchaCharSet = []byte("0123456789")
// var captchaCharSet = []byte("123456789ABCEFHJKLMNPRSTUVXYZ")
var captchaCharIndexMap = map[byte]byte{}
// rngKey is a secret key used to deterministically derive seeds for
// PRNGs used in image and audio. Generated once during initialization.
var rngKey [32]byte
func init() {
if _, err := io.ReadFull(rand.Reader, rngKey[:]); err != nil {
panic("captcha: error reading random source: " + err.Error())
}
for index, b := range captchaCharSet {
captchaCharIndexMap[b] = byte(index)
}
}
// Purposes for seed derivation. The goal is to make deterministic PRNG produce
// different outputs for images and audio by using different derived seeds.
const (
imageSeedPurpose = 0x01
audioSeedPurpose = 0x02
)
// deriveSeed returns a 16-byte PRNG seed from rngKey, purpose, id and digits.
// Same purpose, id and digits will result in the same derived seed for this
// instance of running application.
//
// out = HMAC(rngKey, purpose || id || 0x00 || digits) (cut to 16 bytes)
//
func deriveSeed(purpose byte, id string, digits []byte) (out [16]byte) {
var buf [sha256.Size]byte
h := hmac.New(sha256.New, rngKey[:])
h.Write([]byte{purpose})
io.WriteString(h, id)
h.Write([]byte{0})
h.Write(digits)
sum := h.Sum(buf[:0])
copy(out[:], sum)
return
}
func captchaToBytes(captchaWords string) []byte {
bts := []byte{}
for _, b := range []byte(captchaWords) {
if index, found := captchaCharIndexMap[b]; found {
bts = append(bts, byte(index))
}
}
return bts
}
// RandomDigits returns a byte slice of the given length containing
// pseudorandom numbers in range 0-9. The slice can be used as a captcha
// solution.
func RandomCaptcha(length int) []byte {
return randomBytesMod(length, byte(len(captchaCharSet)))
}
// randomBytes returns a byte slice of the given length read from CSPRNG.
func randomBytes(length int) (b []byte) {
b = make([]byte, length)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
panic("captcha: error reading random source: " + err.Error())
}
return
}
// randomBytesMod returns a byte slice of the given length, where each byte is
// a random number modulo mod.
func randomBytesMod(length int, mod byte) (b []byte) {
if length == 0 {
return nil
}
if mod == 0 {
panic("captcha: bad mod argument for randomBytesMod")
}
maxrb := 255 - byte(256%int(mod))
b = make([]byte, length)
i := 0
for {
r := randomBytes(length + (length / 4))
for _, c := range r {
if c > maxrb {
// Skip this number to avoid modulo bias.
continue
}
b[i] = c % mod
i++
if i == length {
return
}
}
}
}
// randomId returns a new random id string.
func randomId() string {
b := randomBytesMod(idLen, byte(len(idChars)))
for i, c := range b {
b[i] = idChars[c]
}
return string(b)
}