Google authenticator是基於TOTP(Time-based One-time Password Algorithm)原理實現的雙因子認證方案。算法
經過 一致算法保持手機端和服務端相同,並每30秒改變認證碼。服務器
主要對外調用方法說明:app
GetSecret() :獲取祕鑰(32位字符串)網站
- GetCode() :獲取動態碼
- GetQrcode() :獲取動態碼二維碼內容
- GetQrcodeUrl() :獲取動態碼二維碼圖片地址
- VerifyCode() :驗證動態碼
otp是什麼知道麼? 是一次性密碼,簡單的說,totp是基於時間的,htop是基於次數的。ui
操做過程:this
一、下載google 身份驗證器google
二、添加帳號、祕鑰(祕鑰須要經過如下程序生成:NewGoogleAuth().GetSecret())編碼
三、登陸app、網站時,填寫」google身份驗證器「顯示的6位數字code
四、服務端驗證6位數字是否正確:NewGoogleAuth().VerifyCode(secret, code)圖片
祕鑰生成原理(基於時間):
一、時間戳,精確到微秒,除以1000,除以30(動態6位數字每30秒變化一次)
二、對時間戳餘數 hmac_sha1 編碼
三、而後 base32 encode 標準編碼
四、輸出大寫字符串,即祕鑰
動態6位數字驗證:
Google Authenticator會基於密鑰和時間計算一個HMAC-SHA1的hash值,這個hash是160 bit的,而後將這個hash值隨機取連續的4個字節生成32位整數,最後將整數取31位,再取模獲得一個的整數。
這個就是Google Authenticator顯示的數字。
在服務器端驗證的時候,一樣的方法來計算出數字,而後比較計算出來的結果和用戶輸入的是否一致。
googleAuth.go
package main import ( "bytes" "crypto/hmac" "crypto/sha1" "encoding/base32" "encoding/binary" "fmt" "strings" "time" ) type GoogleAuth struct { } func NewGoogleAuth() *GoogleAuth { return &GoogleAuth{} } func (this *GoogleAuth) un() int64 { return time.Now().UnixNano() / 1000 / 30 } func (this *GoogleAuth) hmacSha1(key, data []byte) []byte { h := hmac.New(sha1.New, key) if total := len(data); total > 0 { h.Write(data) } return h.Sum(nil) } func (this *GoogleAuth) base32encode(src []byte) string { return base32.StdEncoding.EncodeToString(src) } func (this *GoogleAuth) base32decode(s string) ([]byte, error) { return base32.StdEncoding.DecodeString(s) } func (this *GoogleAuth) toBytes(value int64) []byte { var result []byte mask := int64(0xFF) shifts := [8]uint16{56, 48, 40, 32, 24, 16, 8, 0} for _, shift := range shifts { result = append(result, byte((value>>shift)&mask)) } return result } func (this *GoogleAuth) toUint32(bts []byte) uint32 { return (uint32(bts[0]) << 24) + (uint32(bts[1]) << 16) + (uint32(bts[2]) << 8) + uint32(bts[3]) } func (this *GoogleAuth) oneTimePassword(key []byte, data []byte) uint32 { hash := this.hmacSha1(key, data) offset := hash[len(hash)-1] & 0x0F hashParts := hash[offset : offset+4] hashParts[0] = hashParts[0] & 0x7F number := this.toUint32(hashParts) return number % 1000000 } func (this *GoogleAuth) GetSecret() string { var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, this.un()) return strings.ToUpper(this.base32encode(this.hmacSha1(buf.Bytes(), nil))) } func (this *GoogleAuth) GetCode(secret string) (string, error) { secretUpper := strings.ToUpper(secret) secretKey, err := this.base32decode(secretUpper) if err != nil { return "", err } number := this.oneTimePassword(secretKey, this.toBytes(time.Now().Unix()/30)) return fmt.Sprintf("%06d", number), nil } func (this *GoogleAuth) GetQrcode(user, secret string) string { return fmt.Sprintf("otpauth://totp/%s?secret=%s", user, secret) } func (this *GoogleAuth) GetQrcodeUrl(user, secret string) string { qrcode := this.GetQrcode(user, secret) return fmt.Sprintf("http://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=%s", qrcode) } func (this *GoogleAuth) VerifyCode(secret, code string) (bool, error) { _code, err := this.GetCode(secret) fmt.Println(_code, code, err) if err != nil { return false, err } return _code == code, nil }
main.go
package main import ( "fmt" ) func main() { secret := NewGoogleAuth().GetSecret() code, err := NewGoogleAuth().GetCode(secret) fmt.Println(secret, code, err) }