Skip to content

Instantly share code, notes, and snippets.

@Opposite34
Created August 26, 2024 17:55
Show Gist options
  • Select an option

  • Save Opposite34/6204b44ad24001ba8c86a79ae779da8c to your computer and use it in GitHub Desktop.

Select an option

Save Opposite34/6204b44ad24001ba8c86a79ae779da8c to your computer and use it in GitHub Desktop.
Time-based OTP (TOTP) implementation in Go
package main
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base32"
"fmt"
"log"
"math"
"os"
"strings"
"time"
)
const privkeyEnvVar = "OTP_PRIVATE_KEY"
const privkeyFile = ".privkey"
func main() {
privkeyEncoded := os.Getenv(privkeyEnvVar)
if strings.Compare(privkeyEncoded, "") == 0 {
fmt.Println("KEY NOT FOUND, CREATING A NEW KEY")
privkeyEncoded = genAndWriteKey(40)
fmt.Printf("KEY IS CREATED INTO %s\n", privkeyFile)
fmt.Println("TO USE IT IN THE NEXT SESSION, RUN THE FOLLOWING COMMAND:")
fmt.Printf("source %s\n", privkeyFile)
}
privkey, encodingErr := base32.StdEncoding.DecodeString(privkeyEncoded)
if encodingErr != nil {
log.Fatalf("Key Decoding Error: %s", encodingErr)
}
var inputOTP int
fmt.Println("TYPE YOUR OTP:")
fmt.Scan(&inputOTP)
if getTOTP(privkey) == inputOTP {
fmt.Println("ACCESS GRANTED")
} else {
fmt.Println("ACCESS DENIED")
}
}
func getTOTP(key[] byte) int {
mac := hmac.New(sha1.New, key)
timeStamp := time.Now().Unix() / 30
timeStampBytes := make([]byte, 8)
for i:=7; i>=0; i-- {
timeStampBytes[i] = byte(timeStamp & 0xff)
timeStamp = timeStamp >> 8
}
mac.Write(timeStampBytes)
macResult := mac.Sum(nil)
offset := int(macResult[len(macResult)-1]) & 0xf
truncatedResult := int(macResult[offset] & 0x7f) << 24 |
int(macResult[offset+1] & 0xff) << 16 |
int(macResult[offset+2] & 0xff) << 8 |
int(macResult[offset+3] & 0xff)
return truncatedResult % int(math.Pow10(6))
}
func validateHMAC(msg, msgMAC, key []byte) bool {
mac := hmac.New(sha1.New, key)
mac.Write(msg)
expectedMAC := mac.Sum(nil)
//hmac.Equal performs in constant time, preventing sidechannel timing attacks
return hmac.Equal(msgMAC, expectedMAC)
}
func genAndWriteKey(keyLen int) string {
key, genKeyErr := genKey(keyLen)
if genKeyErr != nil {
log.Fatalf("Key Generation Error: %s\n", genKeyErr)
}
encodedKey := base32.StdEncoding.EncodeToString(key)
f, fileCreationErr := os.Create(privkeyFile)
if fileCreationErr != nil {
log.Fatalf("Cannot open/create %s file: %s\n", privkeyFile, fileCreationErr)
}
defer f.Close()
_, fileWriteErr := f.WriteString(
"export " + privkeyEnvVar + "=" + encodedKey + "\n",
)
if fileWriteErr != nil {
log.Fatalf("Cannot write to %s file: %s\n", privkeyFile, fileWriteErr)
}
return encodedKey
}
func genKey(keyLen int) ([]byte, error) {
key := make([]byte, keyLen)
_, err := rand.Read(key)
if err != nil {
return []byte(""), err
}
return key, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment