From 3709c4a20b0fce5ddefc1c4b54133dfc716877c4 Mon Sep 17 00:00:00 2001
From: Hangkun Ung <hangkun.ung@gmail.com>
Date: Sun, 20 Feb 2022 19:13:34 -0500
Subject: [PATCH] Add support for SHA256 and SHA512

---
 .gitignore                   |   1 +
 README.md                    |   3 ++-
 gauth/gauth.go               |  27 ++++++++++++++++++++++++---
 gauth/testdata/encrypted.csv | Bin 64 -> 176 bytes
 gauth/testdata/plaintext.csv |   2 +-
 5 files changed, 28 insertions(+), 5 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..fd5106f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.DS_STORE
diff --git a/README.md b/README.md
index 08264de..5063022 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Usage
         Airbnb:abcd efgh ijkl mnop
         Google:a2b3c4d5e6f7ghij
         Github:234567qrstuvwxyz
+        otpauth://totp/testOrg:testuser?secret=AAAQEAYEAUDAOCAJ======&issuer=testOrg&algorithm=SHA512&digits=8&period=30
 
 - Restrict access to your user:
 
@@ -53,7 +54,7 @@ Encryption
 `gauth` will then prompt you for that password on every run:
 
         $ gauth
-        Encryption password: 
+        Encryption password:
                    prev   curr   next
         LastPass   915200 479333 408710
 
diff --git a/gauth/gauth.go b/gauth/gauth.go
index 3b2af0a..bb26114 100644
--- a/gauth/gauth.go
+++ b/gauth/gauth.go
@@ -6,9 +6,12 @@ import (
 	"bytes"
 	"crypto/aes"
 	"crypto/cipher"
+	"crypto/sha1"
 	"crypto/sha256"
+	"crypto/sha512"
 	"errors"
 	"fmt"
+	"hash"
 	"io/ioutil"
 	"strings"
 	"time"
@@ -24,6 +27,21 @@ func IndexNow() (uint64, int) {
 	return uint64(time / 30), int(time % 30)
 }
 
+// pickAlgorithm returns a constructor for the named hash function, or
+// an error if the name is not a supported algorithm.
+func pickAlgorithm(name string) (func() hash.Hash, error) {
+	switch name {
+	case "", "SHA1":
+		return sha1.New, nil
+	case "SHA256":
+		return sha256.New, nil
+	case "SHA512":
+		return sha512.New, nil
+	default:
+		return nil, fmt.Errorf("unsupported algorithm: %q", name)
+	}
+}
+
 // Codes returns the previous, current, and next codes from u.
 func Codes(u *otpauth.URL) (prev, curr, next string, _ error) {
 	var ts uint64
@@ -40,11 +58,14 @@ func Codes(u *otpauth.URL) (prev, curr, next string, _ error) {
 func CodesAtTimeStep(u *otpauth.URL, timeStep uint64) (prev, curr, next string, _ error) {
 	if u.Type != "totp" {
 		return "", "", "", fmt.Errorf("unsupported type: %q", u.Type)
-	} else if u.Algorithm != "" && u.Algorithm != "SHA1" {
-		return "", "", "", fmt.Errorf("unsupported algorithm: %q", u.Algorithm)
 	}
 
-	cfg := otp.Config{Digits: u.Digits}
+	alg, err := pickAlgorithm(u.Algorithm)
+	if err != nil {
+		return "", "", "", err
+	}
+
+	cfg := otp.Config{Hash: alg, Digits: u.Digits}
 	if err := cfg.ParseKey(u.RawSecret); err != nil {
 		return "", "", "", fmt.Errorf("invalid secret: %v", err)
 	}
diff --git a/gauth/testdata/encrypted.csv b/gauth/testdata/encrypted.csv
index 0185d1b96cf5e2719b74c59f1df8527df2e2d79c..ab8697f6d509c5a47349a1676e6f7547fe1521c7 100644
GIT binary patch
literal 176
zcmV;h08jr@VQh3|WM5xp#JFzH%k`L@{D8=~18iQS(*RM}<eR1#BEK+|B4ij_g7AL_
z+&Y50;%p^NZaz1(LQrB4Ck*?qa?Q+pU(7^+h@R(H)iR0bxBS9K+GLQO%Lk=e3DJkq
zTnJ2}Uq4P9SY=-kaDwFAwzdGwRs%`??*Qr3?9sG`NnlpA-5&KHCr@T_O7wJWgcsts
e|63hQdZnD9Dm(lG5#`Rpwp;BTq@Q=rzCW>NrC8tq

literal 64
zcmV-G0KflJVQh3|WM5w-G3q)M!zYxZ3(#$~4iEvv?1ke+)-pQY`Uu`$j~$OC)r(o5
W`4~(QDv{t5Q#$sSJ@}7H$lIQciyhzq

diff --git a/gauth/testdata/plaintext.csv b/gauth/testdata/plaintext.csv
index ec462d0..6da0364 100644
--- a/gauth/testdata/plaintext.csv
+++ b/gauth/testdata/plaintext.csv
@@ -1,3 +1,3 @@
 test2:AEBAGBAFAYDQQCIK
 test1:AAAQEAYEAUDAOCAJ
-
+otpauth://totp/test3:testuser3?secret=AAAQEAYEAUDAOCAJ======&issuer=test3&algorithm=SHA512&digits=8&period=30