/
g2fa.go
91 lines (81 loc) · 2.17 KB
/
g2fa.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 g2fa implements temporary time based password generation for Google
Authenticator app. Works accordingly to RFC 4226 and
https://en.wikipedia.org/wiki/Google_Authenticator
See README for details.
*/
package g2fa
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"time"
)
const (
googleAuthenticatorKeySize = 10
defaultTimeWindowSize = 30
)
// GenerateKey generates random crypto key of requested length in bytes.
func GenerateKey() ([]byte, error) {
key := make([]byte, googleAuthenticatorKeySize)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, err
}
return key, nil
}
// EncodeKey converts a binary key to a user friendly base32 string.
func EncodeKey(key []byte) string {
return base32.StdEncoding.EncodeToString(key)
}
// DecodeKey converts a base32 key to a binary representation.
func DecodeKey(skey string) ([]byte, error) {
key, err := base32.StdEncoding.DecodeString(strings.ToUpper(skey))
if err != nil {
return nil, err
}
if len(key) != googleAuthenticatorKeySize {
return nil, errors.New("Key is not 80 bits")
}
return key, err
}
func timeVariable() int64 {
return time.Now().Unix() / defaultTimeWindowSize
}
// GetTimedAuthCode returns a temp password valid for a 30 seconds window.
func GetTimedAuthCode(key []byte) (string, error) {
code, err := generateHMAC(key, timeVariable())
if err != nil {
return "", err
}
return fmt.Sprintf("%06d", decodeHMAC(code)), nil
}
// decodeHMAC extracts code from a HMAC according to RFC4226
func decodeHMAC(hash []byte) int32 {
if len(hash) != 20 {
panic("Not a HMAC-SHA1 value")
}
offset := hash[19] & 0xf
binCode := int32(0)
binCode += int32(hash[offset+3])
binCode += (int32(hash[offset+2]) << 8)
binCode += (int32(hash[offset+1]) << 16)
binCode += (int32(hash[offset]&0x7f) << 24)
return binCode % 1000000
}
func generateHMAC(key []byte, variable int64) ([]byte, error) {
list := bytes.Buffer{}
err := binary.Write(&list, binary.BigEndian, variable)
if err != nil {
return nil, err
}
macProducer := hmac.New(sha1.New, key)
macProducer.Write(list.Bytes())
return macProducer.Sum(nil), nil
}