golang:2FA雙因素認證

image

原文地址https://mojotv.cn/go/golang-2fa

1. 前言

雙重認證(英語:Two-factor authentication,縮寫爲2FA), 又譯爲雙重驗證、雙因子認證、雙因素認證、二元認證,又稱兩步驟驗證(2-Step Verification,又譯兩步驗證), 是一種認證方法,使用兩種不一樣的元素,合併在一塊兒,來確認用戶的身份,是多因素驗證中的一個特例.html

  • 使用銀行卡時,須要另外輸入PIN碼,確認以後才能使用其轉帳功能.
  • 登錄電腦版微信時,用已經登陸同一帳號的手機版微信掃描特定二維碼進行驗證.
  • 登錄校園網系統時,經過手機短信或學校指定的手機軟件進行驗證.
  • 登錄Steam和Uplay等遊戲平臺時,使用手機令牌或Google身份驗證器進行驗證.

2. TOTP的概念

TOTP 的全稱是」基於時間的一次性密碼」(Time-based One-time Password). 它是公認的可靠解決方案,已經寫入國際標準 RFC6238.git

它的步驟以下.github

  • 第一步,用戶開啓雙因素認證後,服務器生成一個密鑰.
  • 第二步:服務器提示用戶掃描二維碼(或者使用其餘方式),把密鑰保存到用戶的手機.也就是說,服務器和用戶的手機,如今都有了同一把密鑰.
  • 第三步,用戶登陸時,手機客戶端使用這個密鑰和當前時間戳,生成一個哈希,有效期默認爲30秒.用戶在有效期內,把這個哈希提交給服務器.(注意,密鑰必須跟手機綁定.一旦用戶更換手機,就必須生成全新的密鑰.)
  • 第四步,服務器也使用密鑰和當前時間戳,生成一個哈希,跟用戶提交的哈希比對.只要二者不一致,就拒絕登陸.

3.RFC6238

根據RFC 6238標準,供參考的實現以下:golang

  • 生成一個任意字節的字符串密鑰K,與客戶端安全地共享.
  • 基於T0的協商後,Unix時間從時間間隔(TI)開始計數時間步驟,TI則用於計算計數器C(默認狀況下TI的數值是T0和30秒)的數值
  • 協商加密哈希算法(默認爲SHA-1)
  • 協商密碼長度(默認6位)

4. 2FA雙因素認證 Golang 代碼實現 TOTP

生成一次性密碼的僞代碼算法

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := floor(current Unix time / 30)
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code

生成事件性或計數性的一次性密碼僞代碼安全

function GoogleAuthenticatorCode(string secret)
  key := base32decode(secret)
  message := counter encoded on 8 bytes
  hash := HMAC-SHA1(key, message)
  offset := last nibble of hash
  truncatedHash := hash[offset..offset+3]  //4 bytes starting at the offset
  Set the first bit of truncatedHash to zero  //remove the most significant bit
  code := truncatedHash mod 1000000
  pad code with 0 until length of code is 6
  return code
  • 關於代碼中爲何會出現難懂的位運算 -> 追求運算效率
package main

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

func main() {
        key := []byte("MOJOTV_CN_IS_AWESOME_AND_AWESOME_SECRET_KEY")
        number := totp(key, time.Now(), 6)
        fmt.Println("2FA code: ",number)
}

func hotp(key []byte, counter uint64, digits int) int {
    //RFC 6238
    h := hmac.New(sha1.New, key)
    binary.Write(h, binary.BigEndian, counter)
    sum := h.Sum(nil)
    //取sha1的最後4byte
    //0x7FFFFFFF 是long int的最大值
    //math.MaxUint32 == 2^32-1
    //& 0x7FFFFFFF == 2^31  Set the first bit of truncatedHash to zero  //remove the most significant bit
    // len(sum)-1]&0x0F 最後 像登錄 (bytes.len-4)
    //取sha1 bytes的最後4byte 轉換成 uint32
    v := binary.BigEndian.Uint32(sum[sum[len(sum)-1]&0x0F:]) & 0x7FFFFFFF
    d := uint32(1)
    
    //取十進制的餘數
    for i := 0; i < digits && i < 8; i++ {
        d *= 10
    }
    return int(v % d)
}

func totp(key []byte, t time.Time, digits int) int {
    return hotp(key, uint64(t.Unix())/30, digits)
    //return hotp(key, uint64(t.UnixNano())/30e9, digits)
}

5. 參考

相關文章
相關標籤/搜索