package auth import "crypto/hmac" import "crypto/rand" import "crypto/sha1" import "encoding/base32" import "encoding/binary" import "fmt" import "net/url" import "strconv" import "strings" import "time" const TOTPPeriod int64 = 30 const TOTPDigits int = 6 func NewTOTPSecret() (string, error) { var raw []byte var err error var enc *base32.Encoding raw = make([]byte, 20) _, err = rand.Read(raw) if err != nil { return "", err } enc = base32.StdEncoding.WithPadding(base32.NoPadding) return enc.EncodeToString(raw), nil } func TOTPProvisioningURL(issuer string, account string, secret string) string { var label string var values url.Values label = strings.TrimSpace(issuer) + ":" + strings.TrimSpace(account) values = url.Values{} values.Set("secret", strings.TrimSpace(secret)) values.Set("issuer", strings.TrimSpace(issuer)) values.Set("algorithm", "SHA1") values.Set("digits", strconv.Itoa(TOTPDigits)) values.Set("period", strconv.FormatInt(TOTPPeriod, 10)) return "otpauth://totp/" + url.PathEscape(label) + "?" + values.Encode() } func ValidateTOTP(code string, secret string, now time.Time) bool { var value string var counter int64 var i int64 value = strings.TrimSpace(code) if len(value) != TOTPDigits { return false } counter = now.Unix() / TOTPPeriod for i = -1; i <= 1; i++ { if totpCode(secret, counter+i) == value { return true } } return false } func totpCode(secret string, counter int64) string { var key []byte var msg []byte var mac hashHMAC var sum []byte var offset byte var binaryCode uint32 var code uint32 var enc *base32.Encoding var err error enc = base32.StdEncoding.WithPadding(base32.NoPadding) key, err = enc.DecodeString(strings.ToUpper(strings.TrimSpace(secret))) if err != nil { return "" } msg = make([]byte, 8) binary.BigEndian.PutUint64(msg, uint64(counter)) mac = hmac.New(sha1.New, key) _, _ = mac.Write(msg) sum = mac.Sum(nil) offset = sum[len(sum)-1] & 0x0f binaryCode = (uint32(sum[offset])&0x7f)<<24 | (uint32(sum[offset+1])&0xff)<<16 | (uint32(sum[offset+2])&0xff)<<8 | (uint32(sum[offset+3]) & 0xff) code = binaryCode % 1000000 return fmt.Sprintf("%06d", code) } type hashHMAC interface { Write([]byte) (int, error) Sum([]byte) []byte }