-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathotpgen.go
132 lines (109 loc) · 3.08 KB
/
otpgen.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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// Package otpgen implements functions to generate TOTP/HOTP codes.
package otpgen
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/base32"
"encoding/binary"
"fmt"
"hash"
"math"
"strconv"
"strings"
"time"
)
// TOTP represents Time-based OTP.
// See https://datatracker.ietf.org/doc/html/rfc6238
type TOTP struct {
Secret string // Secret key (required)
Digits int // OTP digit count (default: 6)
Algorithm string // OTP Algorithm ("SHA1" or "SHA256" or "SHA512") (default: SHA1)
Period int64 // Period for which OTP is valid (seconds) (default: 30)
UnixTime int64 // (Optional) Unix Timestamp (default: Current unix timestamp)
}
// HOTP represents HMAC-Based One-Time Password Algorithm
// See https://datatracker.ietf.org/doc/html/rfc4226
type HOTP struct {
Secret string // Secret key (required)
Digits int //OTP digit count (default: 6)
Counter int64 // Counter value (default: 0)
}
// Generate TOTP code and returns OTP as string and any error encountered.
func (totp *TOTP) Generate() (string, error) {
var T0 int64 = 0
var currentUnixTime int64
if totp.Secret == "" {
return "", fmt.Errorf("no secret key provided")
}
if totp.Digits == 0 {
totp.Digits = 6
}
if totp.Algorithm == "" {
totp.Algorithm = "SHA1"
}
if totp.Period == 0 {
totp.Period = 30
}
if totp.UnixTime != 0 {
currentUnixTime = totp.UnixTime
} else {
currentUnixTime = time.Now().Unix() - T0
}
currentUnixTime /= totp.Period
return generateOTP(totp.Secret, currentUnixTime, totp.Digits, totp.Algorithm)
}
// Generate HOTP code and returns OTP as string and any error encountered.
func (hotp *HOTP) Generate() (string, error) {
if hotp.Secret == "" {
return "", fmt.Errorf("no secret key provided")
}
if hotp.Digits == 0 {
hotp.Digits = 6
}
return generateOTP(hotp.Secret, hotp.Counter, hotp.Digits, "SHA1")
}
// The main generate function that generates TOTP/HOTP code.
func generateOTP(base32Key string, counter int64, digits int, algo string) (string, error) {
var hmacinit hash.Hash
counterbytes := make([]byte, 8)
binary.BigEndian.PutUint64(counterbytes, uint64(counter)) //convert counter to byte array
secretKey, err := base32.StdEncoding.DecodeString(base32Key) //decode base32 secret to byte array
if err != nil {
return "", fmt.Errorf("bad secret key")
}
switch strings.ToUpper(algo) {
case "SHA1":
{
hmacinit = hmac.New(sha1.New, secretKey)
}
case "SHA256":
{
hmacinit = hmac.New(sha256.New, secretKey)
}
case "SHA512":
{
hmacinit = hmac.New(sha512.New, secretKey)
}
default:
{
return "", fmt.Errorf("invalid algorithm. Please use any one of SHA1/SHA256/SHA512")
}
}
_, err = hmacinit.Write(counterbytes)
if err != nil {
return "", fmt.Errorf("unable to compute HMAC")
}
hash := hmacinit.Sum(nil)
offset := hash[len(hash)-1] & 0xF
hash = hash[offset : offset+4]
hash[0] = hash[0] & 0x7F
decimal := binary.BigEndian.Uint32(hash)
otp := decimal % uint32(math.Pow10(digits))
result := strconv.Itoa(int(otp))
for len(result) != digits {
result = "0" + result
}
return result, nil
}