/
hotp.go
80 lines (69 loc) · 1.91 KB
/
hotp.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
package oath
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base32"
"encoding/binary"
"encoding/hex"
"fmt"
"math"
"strings"
)
type HOTP struct {
base32 bool
hashMethod string
counter int64
valueLength int
}
func NewHOTP(base32 bool, hash string, counter int64, length int) *HOTP {
return &HOTP{
base32: base32,
hashMethod: hash,
counter: counter,
valueLength: length,
}
}
func (t *HOTP) GeneratePassCode(secretKey string) (code string, err error) {
var secret []byte
if t.base32 {
// remove spaces and convert to uppercase
secretKey = strings.Join(strings.Fields(secretKey), "")
secretKey = strings.ToUpper(secretKey)
secret, err = base32.StdEncoding.DecodeString(secretKey)
if err != nil {
return "", fmt.Errorf("base32 decoding failed: Base32-encoded secret key is invalid")
}
} else {
// remove spaces, hexadecimal is not case-sensitive
secretKey = strings.Join(strings.Fields(secretKey), "")
secret, err = hex.DecodeString(secretKey)
if err != nil {
return "", fmt.Errorf("hex decoding failed: hex-encoded secret key is invalid")
}
}
var sum []byte
switch t.hashMethod {
case "SHA1":
mac := hmac.New(sha1.New, secret)
mac.Write(counterToBytes(t.counter))
sum = mac.Sum(nil)
case "SHA256":
mac := hmac.New(sha256.New, secret)
mac.Write(counterToBytes(t.counter))
sum = mac.Sum(nil)
case "SHA512":
mac := hmac.New(sha512.New, secret)
mac.Write(counterToBytes(t.counter))
sum = mac.Sum(nil)
default:
return "", fmt.Errorf("invalid hash algorithm. valid hash algorithms include values SHA1, SHA256, or SHA512")
}
offset := sum[len(sum)-1] & 0xf
binaryCode := binary.BigEndian.Uint32(sum[offset:])
verificationCode := int64(binaryCode) & 0x7FFFFFFF
truncatedCode := verificationCode % int64(math.Pow10(t.valueLength))
code = fmt.Sprintf(fmt.Sprintf("%%0%dd", t.valueLength), truncatedCode)
return code, err
}