forked from rsc/2fa
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtotp.go
100 lines (83 loc) · 2.32 KB
/
totp.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
package totp
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"strings"
"time"
"unicode"
)
type Totp struct {
Secret string
Issuer string
Account string
Algorithm string
Digits int
Peroid int
}
func Secret() string {
secret := make([]byte, 10)
_, err := rand.Read(secret)
if err != nil {
fmt.Println(err)
}
return base32.StdEncoding.EncodeToString(secret)
}
func GenerateTotp(totp Totp) string {
issuer := totp.Issuer
secret := totp.Secret
account := totp.Account
algorithm := totp.Algorithm
digits := totp.Digits
period := totp.Peroid
// adhere to key-uri format: otpauth://TYPE/LABEL?PARAMETERS
// eg: otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
url := fmt.Sprintf("otpauth://totp/%s:%s?algorithm=%s&&digits=%d&issuer=%s&period=%d&secret=%s",
issuer, account, algorithm, digits, issuer, period, secret)
return url
}
// ValidateTotp validates input code with stored code (stored code can be computed from key stored in database)
func ValidateTotp(inputCode, dbCode string) bool {
if inputCode == dbCode {
return true
} else {
return false
}
}
// we are adding three return string since first and last one wil be for skew values.
func CalculateTotp(dbcode string) (string, string, string) {
finalKey, _ := decodeKey(dbcode)
nowtime := time.Now()
skewsub := nowtime.Add(time.Duration(-3e+10))
skewadd := nowtime.Add(time.Duration(3e+10))
currentCode := totp(([]byte(finalKey)), nowtime, 6)
skewSubCode := totp(([]byte(finalKey)), skewsub, 6)
skewAddCode := totp(([]byte(finalKey)), skewadd, 6)
return fmt.Sprintf("%0*d", 6, skewSubCode), fmt.Sprintf("%0*d", 6, currentCode), fmt.Sprintf("%0*d", 6, skewAddCode)
}
func noSpace(r rune) rune {
if unicode.IsSpace(r) {
return -1
}
return r
}
func decodeKey(key string) ([]byte, error) {
return base32.StdEncoding.DecodeString(strings.ToUpper(key))
}
func hotp(key []byte, counter uint64, digits int) int {
h := hmac.New(sha1.New, key)
binary.Write(h, binary.BigEndian, counter)
sum := h.Sum(nil)
v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
d := uint32(1)
for i := 0; i < digits && i < 8; i++ {
d *= 10
}
return int(v % d)
}
func totp(key []byte, t time.Time, digits int) int {
return hotp(key, uint64(t.UnixNano())/30e9, digits)
}