在 Go 中實現 TOTP 認證:實踐指南

時間性一次性密碼(TOTP)已成爲現代應用中實現雙因素認證(2FA)的標準。在本指南中,我們將探討如何在 Go 中使用流行的 

github.com/pquerna/otp 庫實現 TOTP。

什麼是 TOTP?

TOTP 生成臨時密碼,這些密碼在短時間內(通常是 30 秒)有效。這項技術是 Google Authenticator、Authy 等認證器應用背後的核心技術。TOTP 基於服務器和客戶端之間的共享密鑰,並結合當前時間生成同步的代碼。

實現指南

讓我們構建一個完整的 TOTP 實現,包括以下功能:

設置項目

首先,讓我們獲取所需的依賴:

go mod init totp-example
go get github.com/pquerna/otp

核心實現

我們創建一個 TOTP 服務來處理所有的 2FA 需求:

package totp

import (
"encoding/base32"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)

type TOTPService struct {
 issuer string
}

func NewTOTPService(issuer string) *TOTPService {
return &TOTPService{
  issuer: issuer,
 }
}

// GenerateSecret 爲用戶創建一個新的 TOTP 密鑰
func (s *TOTPService) GenerateSecret(username string) (*otp.Key, error) {
 key, err := totp.Generate(totp.GenerateOpts{
  Issuer:      s.issuer,
  AccountName: username,
  Algorithm:   otp.AlgorithmSHA1,
  Digits:      6,
  Period:      30,
  // 你可以手動生成密鑰並在這裏設置。如果未提供密鑰,庫會生成一個隨機密鑰。
  // Secret: secret,
 })
if err != nil {
return nil, err
 }
return key, nil
}

// ValidateCode 驗證提供的代碼是否有效
func (s *TOTPService) ValidateCode(secret string, code string) bool {
 valid := totp.Validate(code, secret)
return valid
}

實踐示例

以下是一個完整的示例,展示瞭如何將 TOTP 集成到用戶註冊和登錄流程中:

package main

import (
"fmt"
"log"
)

type User struct {
 Username    string
 TOTPSecret  string
 TOTPEnabled bool
}

type UserService struct {
 totpService *TOTPService
 users       map[string]*User // 實際應用中使用真正的數據庫
}

func NewUserService() *UserService {
return &UserService{
  totpService: NewTOTPService("MyApp"),
  users:       make(map[string]*User),
 }
}

// EnableTOTP 爲用戶設置 2FA
func (s *UserService) EnableTOTP(username string) (string, string, error) {
 user, exists := s.users[username]
if !exists {
return"", "", fmt.Errorf("user not found")
 }
 key, err := s.totpService.GenerateSecret(username)
if err != nil {
return"", "", err
 }
 // 存儲密鑰
 user.TOTPSecret = key.Secret()
 // 返回密鑰和二維碼 URL
return key.Secret(), key.URL(), nil
}

// ValidateAndEnableTOTP 驗證並啓用 TOTP
func (s *UserService) ValidateAndEnableTOTP(username, code string) error {
 user, exists := s.users[username]
if !exists {
return fmt.Errorf("user not found")
 }
if s.totpService.ValidateCode(user.TOTPSecret, code) {
  user.TOTPEnabled = true
return nil
 }
return fmt.Errorf("invalid code")
}

// Login 在登錄時驗證用戶的 TOTP 代碼
func (s *UserService) Login(username, totpCode string) error {
 user, exists := s.users[username]
if !exists {
return fmt.Errorf("user not found")
 }
if user.TOTPEnabled {
if !s.totpService.ValidateCode(user.TOTPSecret, totpCode) {
   return fmt.Errorf("invalid TOTP code")
  }
 }
return nil // 登錄成功
}

示例使用

以下是如何使用上述實現的示例:

func main() {
 userService := NewUserService()

 // 註冊一個新用戶
 userService.users["john"] = &User{Username: "john"}

 // 爲用戶啓用 TOTP
 secret, qrURL, err := userService.EnableTOTP("john")
if err != nil {
  log.Fatal(err)
 }
 fmt.Printf("Secret: %s\n", secret)
 fmt.Printf("QR Code URL: %s\n", qrURL)

 // 用戶在認證器應用中掃描二維碼並輸入代碼
 code := "123456" // 用戶輸入的代碼
 err = userService.ValidateAndEnableTOTP("john", code)
if err != nil {
  log.Fatal(err)
 }

 // 之後,在登錄時
 loginCode := "654321" // 用戶輸入的登錄代碼
 err = userService.Login("john", loginCode)
if err != nil {
  log.Fatal(err)
 }
}

最佳實踐和安全考慮

密鑰存儲

始終安全地存儲 TOTP 密鑰:

使用靜態加密存儲

對於大規模應用,考慮使用硬件安全模塊(HSM) 不要以明文形式記錄或暴露密鑰

時間同步

確保服務器的時間準確: 使用 NTP 保持服務器同步 考慮允許少量時間偏差(庫會自動處理)

備份代碼

實現備份代碼以便賬戶恢復:

func generateBackupCodes() []string {
 codes := make([]string, 8)
 for i := range codes {
  // 生成隨機的 8 字符代碼
  // 將這些代碼安全存儲
 }
 return codes
}

速率限制

爲 TOTP 驗證嘗試數實現速率限制:

func (s *TOTPService) ValidateCode(secret, code string) bool {
 // 在驗證前檢查速率限制
 if isRateLimited(secret) {
  return false
 }
 return totp.Validate(code, secret)
}

常見陷阱

  1. 時間窗口 不要將驗證窗口設置得太大或太小。

  2. 密鑰長度 始終使用庫的生成函數,不要自己生成密鑰。

  3. 缺少恢復選項 始終提供備份代碼或恢復方法。

  4. 缺乏速率限制 實現速率限制以防止暴力破解攻擊。

結論

使用 pquerna/otp 庫在 Go 中實現 TOTP 是簡單且安全的。該庫處理了複雜的加密操作,開發者能夠專注於集成和安全考慮。

記住以下要點:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/f_FWbK8wJussMW807-cg3A