-
Notifications
You must be signed in to change notification settings - Fork 67
/
otp.go
70 lines (62 loc) · 1.6 KB
/
otp.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
package gotp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"fmt"
"hash"
"math"
"strings"
)
type Hasher struct {
HashName string
Digest func() hash.Hash
}
type OTP struct {
secret string // secret in base32 format
digits int // number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
hasher *Hasher // digest function to use in the HMAC (expected to be sha1)
}
func NewOTP(secret string, digits int, hasher *Hasher) OTP {
if hasher == nil {
hasher = &Hasher{
HashName: "sha1",
Digest: sha1.New,
}
}
return OTP{
secret: secret,
digits: digits,
hasher: hasher,
}
}
/*
params
input: the HMAC counter value to use as the OTP input. Usually either the counter, or the computed integer based on the Unix timestamp
*/
func (o *OTP) generateOTP(input int) string {
if input < 0 {
panic("input must be positive integer")
}
hasher := hmac.New(o.hasher.Digest, o.byteSecret())
hasher.Write(Itob(input))
hmacHash := hasher.Sum(nil)
offset := int(hmacHash[len(hmacHash)-1] & 0xf)
code := ((int(hmacHash[offset]) & 0x7f) << 24) |
((int(hmacHash[offset+1] & 0xff)) << 16) |
((int(hmacHash[offset+2] & 0xff)) << 8) |
(int(hmacHash[offset+3]) & 0xff)
code = code % int(math.Pow10(o.digits))
return fmt.Sprintf(fmt.Sprintf("%%0%dd", o.digits), code)
}
func (o *OTP) byteSecret() []byte {
missingPadding := len(o.secret) % 8
if missingPadding != 0 {
o.secret = o.secret + strings.Repeat("=", 8-missingPadding)
}
bytes, err := base32.StdEncoding.DecodeString(o.secret)
if err != nil {
panic("decode secret failed")
}
return bytes
}