-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtotp.go
119 lines (96 loc) · 3.86 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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/*
Package totp implements Time-based One-Time Password (TOTP) generation and validation.
TOTP is an algorithm that computes a one-time password from a shared secret key and the current time.
This package provides functions to generate a TOTP secret, generate TOTP codes, and validate them.
*/
package totp
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"strings"
"time"
)
// GenerateSecret generates a random TOTP secret key in base32 encoding.
// The length of the generated secret is fixed to 10 bytes.
// It returns the base32-encoded secret and an error if the generation fails.
func GenerateSecret() (string, error) {
secretLength := 10
// Define the length of the random byte sequence (usually between 10 to 20 bytes for TOTP)
randomBytes := make([]byte, secretLength)
// Generate random bytes
_, err := rand.Read(randomBytes)
if err != nil {
return "", fmt.Errorf("error generating random bytes: %v", err)
}
// Encode the random bytes to base32
secret := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(randomBytes)
return secret, nil
}
// TOTP generates a 6-digit TOTP code using the given base32-encoded secret
// and a time step in seconds (default is 30 seconds).
// It returns the generated TOTP code and an error if the generation fails.
func TOTP(secret string, duration int) (string, error) {
// Decode the base32-encoded secret
secret = strings.ToUpper(secret) // TOTP secrets are usually upper-case
// Calculate the time counter, which is the number of 30-second intervals since Unix epoch
timestamp := int64(time.Now().Unix())
return generateTOTP(secret, timestamp, duration)
}
// Validate checks if the provided TOTP code matches the generated TOTP code
// for the given secret within a +/- 30-second time window.
// It returns true if the code is valid, otherwise false.
func Validate(secret string, duration int, code string) bool {
if duration < 1 {
duration = 30
}
// Allow for a +/- 30-second time window to account for clock drift
currentTimestamp := time.Now().Unix()
for i := -1; i <= 1; i++ {
// Generate the TOTP for the current time window
timestamp := int64(currentTimestamp + int64(i * duration))
generatedCode, err := generateTOTP(secret, timestamp, duration)
if err != nil {
fmt.Println("Error generating TOTP:", err)
return false
}
// Check if the generated TOTP matches the provided code
if generatedCode == code {
return true
}
}
return false
}
// generateTOTP generates a TOTP code for the specified timestamp.
// It takes a base32-encoded secret, a timestamp, and a duration in seconds.
// It returns the generated TOTP code and an error if the generation fails.
func generateTOTP(secret string, timestamp int64, duration int) (string, error) {
// Decode the base32-encoded secret
secret = strings.ToUpper(secret) // TOTP secrets are usually upper-case
key, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", fmt.Errorf("error decoding secret: %v", err)
}
// Calculate the time counter, based on the timestamp
interval := uint64(timestamp / int64(duration))
// Convert the time counter to a byte array (big-endian)
var counterBytes [8]byte
binary.BigEndian.PutUint64(counterBytes[:], interval)
// Create an HMAC-SHA1 hash using the counter as the message and the secret as the key
hmacHash := hmac.New(sha1.New, key)
hmacHash.Write(counterBytes[:])
hash := hmacHash.Sum(nil)
// Perform dynamic truncation to get a 4-byte string (as per TOTP spec)
offset := hash[len(hash)-1] & 0x0F
code := (int(hash[offset]&0x7F) << 24) |
(int(hash[offset+1]&0xFF) << 16) |
(int(hash[offset+2]&0xFF) << 8) |
(int(hash[offset+3] & 0xFF))
// Get the last 6 digits of the code as the OTP
otp := code % 1000000
// Return the OTP as a zero-padded 6-digit string
return fmt.Sprintf("%06d", otp), nil
}