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
| package totp
import (
"crypto/hmac"
"crypto/sha1" // SHA-1 is the default algorithm for compatibility
"encoding/binary"
"fmt"
"hash"
"math"
"time"
// You can uncomment these if you need SHA-256 or SHA-512 support
// "crypto/sha256"
// "crypto/sha512"
)
// Algorithm represents the hash algorithm constructor function.
type Algorithm func() hash.Hash
var (
AlgorithmSHA Algorithm = sha1.New
// AlgorithmSHA256 Algorithm = sha256.New // Uncomment if needed
// AlgorithmSHA512 Algorithm = sha512.New // Uncomment if needed
)
// Constants for common TOTP parameters
var (
DefaultStep = 30 // seconds
DefaultDigits = 6
DefaultAlgorithm = AlgorithmSHA
)
// GenerateTOTP generates a Time-based One-Time Password.
// It implements the algorithm defined in RFC 6238.
//
// secret: The shared secret key as a byte slice. This is the key shared
//
// between the server and the client (authenticator app).
//
// t: The time for which to generate the code. Usually time.Now().
// step: The time step in seconds (e.g., 30 seconds is common).
// digits: The number of digits in the output code (e.g., 6 or 8).
// algorithm: The hash algorithm constructor function (e.g., sha1.New).
//
// Returns the TOTP code as a string (padded with leading zeros) and an error.
func GenerateTOTP(secret []byte, t time.Time, step int, digits int, algorithm Algorithm) (string, error) {
if len(secret) == 0 {
return "", fmt.Errorf("secret cannot be empty")
}
if step <= 0 {
return "", fmt.Errorf("step must be positive")
}
if digits <= 0 || digits > 9 { // Practical limit for digits in common use
return "", fmt.Errorf("digits must be between 1 and 9")
}
if algorithm == nil {
return "", fmt.Errorf("algorithm cannot be nil")
}
// Calculate the time-based counter T
// T = floor((Current Unix time - T0) / Time Step)
// T0 is the Unix epoch (Jan 1, 1970 00:00:00 UTC)
// For simplicity and common use, T0 is usually 0, so T = floor(Current Unix time / Time Step)
counter := uint64(t.Unix() / int64(step))
// Generate the HOTP code using the calculated counter
code, err := generateHOTP(secret, counter, digits, algorithm)
if err != nil {
return "", fmt.Errorf("failed to generate HOTP: %w", err)
}
// Format the code with leading zeros to the specified number of digits
format := fmt.Sprintf("%%0%dd", digits)
return fmt.Sprintf(format, code), nil
}
// generateHOTP generates an HMAC-based One-Time Password.
// It implements the algorithm defined in RFC 4226.
//
// secret: The shared secret key.
// counter: The counter value (a monotonically increasing value).
// digits: The number of digits in the output code.
// algorithm: The hash algorithm constructor.
//
// Returns the HOTP code as an integer and an error.
func generateHOTP(secret []byte, counter uint64, digits int, algorithm Algorithm) (int, error) {
// Convert the counter (uint64) to an 8-byte slice in big-endian order
counterBytes := make([]byte, 8)
binary.BigEndian.PutUint64(counterBytes, counter)
// Create a new HMAC hash instance with the specified algorithm and secret key
h := hmac.New(algorithm, secret)
// Write the counter bytes to the HMAC hash
h.Write(counterBytes)
// Compute the HMAC sum
hmacResult := h.Sum(nil)
// Perform Dynamic Truncation (RFC 4226, Section 5.3)
// 1. Take the last 4 bits of the HMAC result as an offset.
offset := int(hmacResult[len(hmacResult)-1] & 0x0F)
// 2. Extract 4 bytes from the HMAC result starting from the offset.
// These 4 bytes are treated as a 32-bit integer.
truncatedHash := hmacResult[offset : offset+4]
// 3. Convert the 4 bytes to a 32-bit integer in big-endian order.
// Ignore the most significant bit (MSB) to avoid signed integer issues.
otpValue := binary.BigEndian.Uint32(truncatedHash) & 0x7FFFFFFF
// Calculate the final code by taking the integer modulo 10^digits
// This ensures the code has the desired number of digits.
mod := int(math.Pow10(digits))
code := int(otpValue) % mod
return code, nil
}
// GenerateTOTP6DigitsSHA1 is a convenience function to generate a 6-digit
// TOTP using the default SHA-1 algorithm and a 30-second time step.
// This is the most common configuration compatible with apps like Google Authenticator.
//
// secret: The shared secret key as a byte slice.
// t: The time for which to generate the code.
//
// Returns the 6-digit TOTP code as a string and an error.
func GenerateTOTP6DigitsSHA1(secret []byte, t time.Time) (string, error) {
return GenerateTOTP(secret, t, DefaultStep, DefaultDigits, DefaultAlgorithm)
}
|