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 }