Golang实现Google-Authenticator

Author Avatar
Yangzzz 8月 05, 2020
653字 | 3分 |
  • 在其它设备中阅读本文章

什么是Google-Authenticator

Google身份验证器是一款基于时间与哈希的一次性密码算法的两步验证软件令牌。也就是我们署成的TOTP(Time-based One-time Password)

通俗的说就是:密钥+算法=code

通过控制变量法,这里我们只需要手机上也设置一样的密钥使用一样的算法,就可以生成一样的code,从而达到二次验证。

使用Go实现生成密码算法

/**
* @Author: myxy99 <myxy99@foxmail.com>
* @Date: 2020/8/4 16:56
 */
package utils

import (
    "crypto/hmac"
    "crypto/sha1"
    "encoding/base32"
    "fmt"
    "hash"
    "math/rand"
    "net/url"
    "strings"
    "time"
)

var (
    codes   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    codeLen = len(codes)
)

type GoogleAuth struct {
    user, issuer, secret string
}

func getCode(key []byte, value []byte) uint32 {
    var (
        hmacSha1         hash.Hash
        bytes, hashParts []byte
        offset           uint8
        number, pwd      uint32
    )
    hmacSha1 = hmac.New(sha1.New, key)
    hmacSha1.Write(value)
    bytes = hmacSha1.Sum(nil)
    offset = bytes[len(bytes)-1] & 0x0F
    hashParts = bytes[offset : offset+4]
    hashParts[0] = hashParts[0] & 0x7F
    number = toUint32(hashParts)
    pwd = number % 1000000
    return pwd
}

func toBytes(value int64) []byte {
    var (
        result []byte
        mask   int64
        shifts [8]uint16
    )
    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 toUint32(bytes []byte) uint32 {
    return (uint32(bytes[0]) << 24) + (uint32(bytes[1]) << 16) +
        (uint32(bytes[2]) << 8) + uint32(bytes[3])
}

func (g *GoogleAuth) getCode() (code string, err error) {
    var (
        key      []byte
        secret   string
        codeUI32 uint32
    )
    secret = strings.ToUpper(strings.Replace(g.secret, " ", "", -1))
    key, err = base32.StdEncoding.DecodeString(secret)
    if err != nil {
        return "", nil
    }
    codeUI32 = getCode(key, toBytes(time.Now().Unix()/30))
    code = fmt.Sprintf("%0*d", 6, codeUI32)
    return
}

//生成url
func (g *GoogleAuth) ProvisionURI() (codeUrl string) {
    auth := "totp/"
    q := make(url.Values)
    q.Add("secret", g.secret)
    if g.issuer != "" {
        q.Add("issuer", g.issuer)
        auth += g.issuer + ":"
    }
    return "otpauth://" + auth + g.user + "?" + q.Encode()
}

//生成验证code正确
func (g *GoogleAuth) Authenticate(code string) (ok bool, err error) {
    var (
        nowCode string
    )
    nowCode, err = g.getCode()
    return nowCode == code, err
}

//生成密钥
func (g *GoogleAuth) GenerateKey() string {
    data := make([]byte, 32)
    rand.Seed(time.Now().UnixNano())
    for i := 0; i < 32; i++ {
        idx := rand.Intn(codeLen)
        data[i] = byte(codes[idx])
    }
    g.secret = string(data)
    return string(data)
}

//设置密钥
func (g *GoogleAuth) SetKey(key string) {
    g.secret = key
    return
}

func NewGoogleAuth(user string, issuer ...string) *GoogleAuth {
    var iss string
    if len(issuer) == 1 {
        iss = issuer[0]
    }
    return &GoogleAuth{
        user:   user,
        issuer: iss,
    }
}

使用

服务端

func TestGoogle(t *testing.T) {
    auth := utils.NewGoogleAuth("admin@myxy99.cn")
    //生成密钥
    //key := auth.GenerateKey()
    //fmt.Println(key)

    //已经有密钥了,设置密钥
    auth.SetKey("TsJjVwEIcMDXuREaXuQgEMdpcRvJXfBV")
    //url生成
    fmt.Println(auth.ProvisionURI())

    //验证code
    ok, _ := auth.Authenticate("890456")
    fmt.Println(ok)
}

客户端

客户端只需要在手机上下载Authenticator添加服务端通过ProvisionURI方法生成的url,就可以达到验证效果:

这样就可以很方便的在你的网站中添加google验证器了。

本文使用CC BY-NC-SA 3.0 中国大陆协议许可
本文链接:https://myxy99.cn/posts/golang/ec61e4d8.html