/
generator.go
91 lines (75 loc) · 2.3 KB
/
generator.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
package application
import (
"crypto/rand"
"encoding/base64"
"io"
"strconv"
"flamingo.me/flamingo/core/captcha/domain"
"github.com/dchest/captcha"
"github.com/pkg/errors"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/scrypt"
)
type (
// Generator to create captchas
Generator struct {
encryptionKey [32]byte
}
)
// Inject dependencies
func (g *Generator) Inject(
config *struct {
EncryptionPassPhrase string `inject:"config:captcha.encryptionPassPhrase"`
},
) {
// values are standard recommendation of scrypt docu
salt := captcha.RandomDigits(8)
key, err := scrypt.Key([]byte(config.EncryptionPassPhrase), salt, 32768, 8, 1, 32)
if err != nil {
panic(err)
}
copy(g.encryptionKey[:], key[:32])
}
// NewCaptcha generates a new digit-only captcha with the given length
func (g *Generator) NewCaptcha(length int) *domain.Captcha {
digits := captcha.RandomDigits(length)
var solution string
for _, d := range digits {
solution += strconv.Itoa(int(d))
}
return g.NewCaptchaBySolution(solution)
}
// NewCaptchaByHash recreates a captcha by the given hash.
// The hash must be generated with the same encryption key, normally by the same instance of Generator
func (g *Generator) NewCaptchaByHash(hash string) (*domain.Captcha, error) {
encrypted, err := base64.URLEncoding.DecodeString(hash)
if err != nil {
return nil, err
}
var decryptNonce [24]byte
copy(decryptNonce[:], encrypted[:24])
decrypted, ok := secretbox.Open(nil, encrypted[24:], &decryptNonce, &g.encryptionKey)
if !ok {
return nil, errors.New("invalid key")
}
return &domain.Captcha{
Solution: string(decrypted),
Hash: hash,
}, nil
}
// NewCaptchaBySolution creates a new captcha containing the given solution.
// However, multiple calls with the same solution will have different hashes because the encryption nonce is selected by random
func (g *Generator) NewCaptchaBySolution(solution string) *domain.Captcha {
solutionBytes := []byte(solution)
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
panic(err)
}
// nonce is stored in first 24 bytes of the encrypted string
encrypted := secretbox.Seal(nonce[:], solutionBytes, &nonce, &g.encryptionKey)
hash := base64.URLEncoding.EncodeToString(encrypted)
return &domain.Captcha{
Solution: solution,
Hash: hash,
}
}