Skip to content

Instantly share code, notes, and snippets.

@rseyf
Created June 12, 2024 08:56
Show Gist options
  • Save rseyf/7ee387bf4627f77d5aed3cb354d07765 to your computer and use it in GitHub Desktop.
Save rseyf/7ee387bf4627f77d5aed3cb354d07765 to your computer and use it in GitHub Desktop.
Go Script to read TOTP secrets from a file and generating auth codes
package main
import (
"bufio"
"fmt"
"log"
"net/url"
"os"
"strings"
"time"
"github.com/olekukonko/tablewriter"
"github.com/pquerna/otp/totp"
)
type TOTPInfo struct {
Secret string
Title string
Account string
}
func main() {
file, err := os.Open("totp.keys")
if err != nil {
log.Fatalf("failed to open file: %v", err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
var totpInfos []TOTPInfo
for scanner.Scan() {
totpURL := scanner.Text()
secret, title, account := extractSecretAndTitle(totpURL)
if secret != "" && title != "" && account != "" {
totpInfos = append(totpInfos, TOTPInfo{Secret: secret, Title: title, Account: account})
}
}
if err := scanner.Err(); err != nil {
log.Fatalf("error reading file: %v", err)
}
stopSpinner := make(chan bool)
defer close(stopSpinner)
go spinner(stopSpinner)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
data := [][]string{}
for _, info := range totpInfos {
code, err := totp.GenerateCode(info.Secret, time.Now())
if err != nil {
log.Printf("failed to generate TOTP code for %s: %v", info.Title, err)
continue
}
data = append(data, []string{info.Title, info.Account, code})
}
printTable(data)
}
}
}
func extractSecretAndTitle(totpURL string) (string, string, string) {
u, err := url.Parse(totpURL)
if err != nil {
log.Printf("failed to parse TOTP URL: %v", err)
return "", "", ""
}
secret := u.Query().Get("secret")
if secret == "" {
log.Printf("failed to find secret in TOTP URL")
return "", "", ""
}
pathParts := strings.Split(u.Path, ":")
if len(pathParts) < 2 {
log.Printf("failed to find title in TOTP URL")
return "", "", ""
}
title := pathParts[0]
account := pathParts[1]
return secret, title, account
}
func spinner(stop chan bool) {
spinChars := []rune{'|', '/', '-', '\\'}
i := 0
for {
select {
case <-stop:
return
default:
fmt.Printf("\r%c", spinChars[i])
i = (i + 1) % len(spinChars)
time.Sleep(100 * time.Millisecond)
}
}
}
func printTable(data [][]string) {
fmt.Printf("\r")
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"APP Name", "Account", "TOTP Code"})
for _, v := range data {
table.Append(v)
}
table.Render()
}
@rseyf
Copy link
Author

rseyf commented Jun 12, 2024

create totp.keys file with a format like this (the totp standard URL format) next to the project binary or source code:

otpauth://totp/reza%20app:user%40email.com?secret=5X5F4LZHKVBGRISXLVBEK7K4TU&issuer=reza%20app 
otpauth://totp/reza%20app:user%40somehow.com?secret=5X5F4LZHKVBGRISXLVBEK7K4TU&issuer=reza%20app 

build and run project:

go build
./totp-reader

Output:
Screenshot_20240612_122735

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment