基於TOTP演算法的Github兩步驗證2FA(雙因子)機制Python3.10實現

劉悅的技術部落格發表於2023-09-29

從今年(2023)三月份開始,Github開始強制使用者開啟兩步驗證2FA(雙因子)登入驗證,毫無疑問,是出於安全層面的考慮,畢竟Github賬號一旦被盜,所有程式碼倉庫都會毀於一旦,關於雙因子登入的必要性請參見:別讓你的伺服器(vps)淪為肉雞(ssh暴力破解),金鑰驗證、雙向因子登入值得擁有

雙因子登入說白了就是透過第三方裝置證明"你是你自己"的一個措施,Github官方推薦在移動端下載1Password、Authy、Microsoft Authenticator等APP來透過掃碼進行驗證,其實大可不必如此麻煩,本次我們透過Python/Golang程式碼來實現雙因子登入驗證。

TOTP演算法

Time-based One-Time Password(TOTP)是一種基於時間的一次性密碼演算法,用於增強身份驗證的安全性。

TOTP基於HMAC(Hash-based Message Authentication Code)演算法和時間戳生成一次性密碼。使用者和伺服器之間共享一個金鑰,通常在初始化身份驗證時交換。基於該金鑰,伺服器生成一個用於驗證的初始值。

在每個時間步長(通常是30秒),基於當前時間戳和共享金鑰,使用HMAC演算法生成一個雜湊值。然後,從雜湊值中提取一個固定長度的動態密碼。這個動態密碼在設定的時間步長內有效,之後會自動失效。

使用者在進行身份驗證時,需要輸入當前時間步長內生成的動態密碼。伺服器會使用相同的演算法和共享金鑰,驗證使用者提供的密碼是否匹配。由於動態密碼在時間步長過期後就會失效,即使被截獲,也無法在下一個時間步長內重複使用。

TOTP廣泛應用於雙因素身份驗證(2FA)和多因素身份驗證(MFA)的實現中。透過結合使用者的密碼和每次生成的動態密碼,TOTP提供了一層額外的安全保護,有效降低了密碼被盜用或猜測的風險。

常見的TOTP應用包括Google Authenticator和Authy等身份驗證應用程式,它們生成基於TOTP演算法的動態密碼,並與使用者的線上賬戶相繫結,提供更安全的登入方式。

說白了,就是一個帶生命週期的金鑰,30秒之後這個金鑰就會過期,客戶端和服務端共享一個金鑰,透過HMAC演算法來驗證金鑰的合法性。

TOTP演算法實現(Python3.10)

首先在服務端應該先生成一個金鑰,該金鑰在客戶端和認證伺服器之間共享。金鑰可以是字串,但Github官方把該金鑰弄成了二維碼,以方便使用者在移動端掃碼驗證,開啟Github賬戶,選擇設定-》兩步驗證:

點選綠色按鈕,選擇開啟兩步驗證。

此時系統會自動生成一個二維碼,這就是我們共享的金鑰:

該金鑰的字串形式可以透過點選setup key超連結來獲取。

拿到系統金鑰之後,我們安裝基於Python的TOTP庫:

pip3 install pyotp

隨後編寫程式碼生成當前時序的驗證碼:

import pyotp  
import time  
  
# 設定服務端金鑰  
secret_key = "Github服務端生成的金鑰(即二維碼)"  
  
# 使用金鑰和時間間隔(預設為 30 秒)建立一個 TOTP 物件  
totp = pyotp.TOTP(secret_key)  
  
# 生成當前的 OTP  
current_otp = totp.now()  
print(f"當前OTP: {current_otp}")

執行結果:

python -u "d:\jiyun\積雲\boo3_public\test_totp.py"  
當前OTP: 809888

可以看到根據金鑰我們生成了30秒以內有效期的驗證碼,隨後將該驗證碼填入頁面中的Verify the code from the app文字框即可。簡單方便,並不需要移動端的參與。

Golang1.21實現TOTP演算法

如果客戶端的語言是Golang,也可以輕鬆實現TOTP演算法,首先確保本機安裝Golang1.18以上的版本,這裡我們使用的是最新的Golang1.21:

PS C:\Users\zcxey> go version  
go version go1.21.1 windows/amd64

隨後透過go get安裝對應的totp包:

go get github.com/pquerna/otp  
go get github.com/pquerna/otp/totp

接著編寫入口程式碼main.go檔案:

package main  
  
import (  
	"encoding/base32"  
	"fmt"  
	"time"  
  
	"github.com/pquerna/otp"  
	"github.com/pquerna/otp/totp"  
)  
  
// Demo function, not used in main  
// Generates Passcode using a UTF-8 (not base32) secret and custom parameters  
func GeneratePassCode(utf8string string) string {  
	secret := base32.StdEncoding.EncodeToString([]byte(utf8string))  
	passcode, err := totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{  
		Period:    30,  
		Skew:      1,  
		Digits:    otp.DigitsSix,  
		Algorithm: otp.AlgorithmSHA512,  
	})  
	if err != nil {  
		panic(err)  
	}  
	return passcode  
}  
  
func main() {  
  
	passcode := GeneratePassCode("Github官方生成的金鑰")  
  
	fmt.Print(passcode)  
  
}

這裡透過GeneratePassCode函式來生成驗證碼,預設有效期同樣是30秒,演算法基於otp.AlgorithmSHA512。

執行結果:

go run "d:\jiyun\積雲\boo3_public\main.go"  
692540

隨後同樣將該驗證碼填入頁面中的Verify the code from the app文字框即可。和Python不同的是,Golang直接編譯好以後可以在任意平臺直接執行,理論上要比Python要方便的多。

結語

總體而言,GitHub的雙因子登入提供了更高的賬戶安全性,保護使用者免受未經授權的訪問和潛在的資料洩露。它是一種簡單而有效的安全措施,值得使用者採取以保護他們的GitHub賬戶和相關程式碼資產,不過話說回來,Github官方力推收費的1Password軟體,應該是有一些利益上的繫結,但對於會程式碼的我們來說,這都不算事兒。

相關文章