all repos — captcha @ master

Go package captcha implements generation and verification of image and audio CAPTCHAs.

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	if length == 0 {
 76		return nil
 77	}
 78	if mod == 0 {
 79		panic("captcha: bad mod argument for randomBytesMod")
 80	}
 81	maxrb := 255 - byte(256%int(mod))
 82	b = make([]byte, length)
 83	i := 0
 84	for {
 85		r := randomBytes(length + (length / 4))
 86		for _, c := range r {
 87			if c > maxrb {
 88				// Skip this number to avoid modulo bias.
 89				continue
 90			}
 91			b[i] = c % mod
 92			i++
 93			if i == length {
 94				return
 95			}
 96		}
 97	}
 98
 99}
100
101// randomId returns a new random id string.
102func randomId() string {
103	b := randomBytesMod(idLen, byte(len(idChars)))
104	for i, c := range b {
105		b[i] = idChars[c]
106	}
107	return string(b)
108}