123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- // 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")
- // 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())
- }
- }
- // 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
- }
- // 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 RandomDigits(length int) []byte {
- return randomBytesMod(length, 10)
- }
- // 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)
- }
|