Created
June 12, 2024 08:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
create
totp.keys
file with a format like this (the totp standard URL format) next to the project binary or source code:build and run project:
Output: