d0zingcat
发布于 2024-03-24 / 23 阅读
0

TOTP Golang 实现

一个很小的 TOTP 的实现 代码写的非常简洁易懂 代码注释已经说明了每一步的实现

package main

import (
        "crypto/hmac"
        "crypto/sha1"
        "encoding/base32"
        "encoding/binary"
        "fmt"
        "strings"
        "time"
)

func generateTOTP(secretKey string, timestamp int64) uint32 {
        // The base32 encoded secret key string is decoded to a byte slice
        base32Decoder := base32.StdEncoding.WithPadding(base32.NoPadding)
        secretKey = strings.ToUpper(strings.TrimSpace(secretKey)) // preprocess
        secretBytes, _ := base32Decoder.DecodeString(secretKey)   // decode

        // The truncated timestamp / 30 is converted to an 8-byte big-endian
        // unsigned integer slice
        timeBytes := make([]byte, 8)
        binary.BigEndian.PutUint64(timeBytes, uint64(timestamp)/30)

        // The timestamp bytes are concatenated with the decoded secret key
        // bytes. Then a 20-byte SHA-1 hash is calculated from the byte slice
        hash := hmac.New(sha1.New, secretBytes)
        hash.Write(timeBytes) // Concat the timestamp byte slice
        h := hash.Sum(nil)    // Calculate 20-byte SHA-1 digest

        // AND the SHA-1 with 0x0F (15) to get a single-digit offset
        offset := h[len(h)-1] & 0x0F

        // Truncate the SHA-1 by the offset and convert it into a 32-bit
        // unsigned int. AND the 32-bit int with 0x7FFFFFFF (2147483647)
        // to get a 31-bit unsigned int.
        truncatedHash := binary.BigEndian.Uint32(h[offset:]) & 0x7FFFFFFF

        // Take modulo 1_000_000 to get a 6-digit code
        return truncatedHash % 1_000_000
}

func main() {
        // Collect it from a TOTP server like GitHub 2FA panel
        secretKey := "ddddd" // This is a fake one!

        now := time.Now().Unix()
        totpCode := generateTOTP(secretKey, now)

        fmt.Printf("Current TOTP code: %06d\n", totpCode)
}

Ported from: https://rednafi.com/go/totp_client/

至于当前有效时间,我们可以简单做一下增强。按照一般的间隔 30s 对当前的 unixtimestamp 做一下求余即可得到。

// ...
const (
	INTERVAL = 30
)
// ...
func main()
	// Collect it from a TOTP server like GitHub 2FA panel
	secretKey := "dddd" // This is a fake one!

	now := time.Now().Unix()
	totpCode := generateTOTP(secretKey, now)

	remainingSecs := INTERVAL - now%INTERVAL

	fmt.Printf("Current TOTP code: %06d Remaining: %02ds\n", totpCode, remainingSecs)