package main import ( "crypto/hmac" "crypto/rand" "crypto/sha1" "crypto/sha256" "encoding/base32" "encoding/binary" "encoding/hex" "fmt" "strings" "time" ) func newSecret() string { b := make([]byte, 20) rand.Read(b) return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b) } func totpCode(secret string, t time.Time) string { key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(strings.ToUpper(secret)) if err != nil { return "" } counter := make([]byte, 8) binary.BigEndian.PutUint64(counter, uint64(t.Unix()/30)) mac := hmac.New(sha1.New, key) mac.Write(counter) sum := mac.Sum(nil) offset := sum[len(sum)-1] & 0x0f code := binary.BigEndian.Uint32(sum[offset:offset+4]) & 0x7fffffff return fmt.Sprintf("%06d", code%1000000) } func totpValid(secret, code string) bool { code = strings.ReplaceAll(strings.TrimSpace(code), " ", "") now := time.Now() for _, d := range []time.Duration{0, -30 * time.Second, 30 * time.Second} { if hmac.Equal([]byte(totpCode(secret, now.Add(d))), []byte(code)) { return true } } return false } func newRecoveryCodes() ([]string, string) { var codes, hashes []string for i := 0; i < 8; i++ { b := make([]byte, 5) rand.Read(b) code := hex.EncodeToString(b) codes = append(codes, code[:5]+"-"+code[5:]) sum := sha256.Sum256([]byte(code)) hashes = append(hashes, hex.EncodeToString(sum[:])) } return codes, strings.Join(hashes, ",") } func useRecovery(stored, code string) (string, bool) { code = strings.ToLower(strings.ReplaceAll(strings.TrimSpace(code), "-", "")) sum := sha256.Sum256([]byte(code)) h := hex.EncodeToString(sum[:]) var kept []string found := false for _, x := range strings.Split(stored, ",") { if x == h && !found { found = true continue } if x != "" { kept = append(kept, x) } } return strings.Join(kept, ","), found }