86 lines
2.6 KiB
Go
86 lines
2.6 KiB
Go
package data
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"crypto/sha1"
|
|
"encoding/base32"
|
|
"encoding/binary"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// GenerateRandomBase32 generates a random base32 encoded string of the specified length
|
|
func GenerateRandomBase32(length int) (string, error) {
|
|
// Generate random bytes
|
|
randomBytes := make([]byte, length)
|
|
_, err := rand.Read(randomBytes)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Encode to base32
|
|
encoder := base32.StdEncoding.WithPadding(base32.NoPadding)
|
|
encoded := encoder.EncodeToString(randomBytes)
|
|
|
|
// Convert to uppercase and remove any padding
|
|
return strings.ToUpper(encoded), nil
|
|
}
|
|
|
|
// ValidateTOTP validates a TOTP code against a secret
|
|
func ValidateTOTP(secret, code string) bool {
|
|
// Allow for a time skew of 30 seconds in either direction
|
|
timeWindow := 1 // 1 before and 1 after current time
|
|
currentTime := time.Now().Unix() / 30
|
|
|
|
// Try the time window
|
|
for i := -timeWindow; i <= timeWindow; i++ {
|
|
if calculateTOTP(secret, currentTime+int64(i)) == code {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// calculateTOTP calculates the TOTP code for a given secret and time
|
|
func calculateTOTP(secret string, timeCounter int64) string {
|
|
// Decode the secret from base32
|
|
encoder := base32.StdEncoding.WithPadding(base32.NoPadding)
|
|
secretBytes, err := encoder.DecodeString(strings.ToUpper(secret))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
// Convert time counter to bytes (big endian)
|
|
timeBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(timeBytes, uint64(timeCounter))
|
|
|
|
// Calculate HMAC-SHA1
|
|
h := hmac.New(sha1.New, secretBytes)
|
|
h.Write(timeBytes)
|
|
hash := h.Sum(nil)
|
|
|
|
// Dynamic truncation
|
|
offset := hash[len(hash)-1] & 0x0F
|
|
truncatedHash := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7FFFFFFF
|
|
otp := truncatedHash % 1000000
|
|
|
|
// Convert to 6-digit string with leading zeros if needed
|
|
result := ""
|
|
if otp < 10 {
|
|
result = "00000" + string(otp+'0')
|
|
} else if otp < 100 {
|
|
result = "0000" + string((otp/10)+'0') + string((otp%10)+'0')
|
|
} else if otp < 1000 {
|
|
result = "000" + string((otp/100)+'0') + string(((otp/10)%10)+'0') + string((otp%10)+'0')
|
|
} else if otp < 10000 {
|
|
result = "00" + string((otp/1000)+'0') + string(((otp/100)%10)+'0') + string(((otp/10)%10)+'0') + string((otp%10)+'0')
|
|
} else if otp < 100000 {
|
|
result = "0" + string((otp/10000)+'0') + string(((otp/1000)%10)+'0') + string(((otp/100)%10)+'0') + string(((otp/10)%10)+'0') + string((otp%10)+'0')
|
|
} else {
|
|
result = string((otp/100000)+'0') + string(((otp/10000)%10)+'0') + string(((otp/1000)%10)+'0') + string(((otp/100)%10)+'0') + string(((otp/10)%10)+'0') + string((otp%10)+'0')
|
|
}
|
|
|
|
return result
|
|
} |