golang google authenticator

Google authenticator是基于TOTP(Time-based One-time Password Algorithm)原理实现的双因子认证方案。算法

经过 一致算法保持手机端和服务端相同,并每30秒改变认证码。服务器

主要对外调用方法说明:app

  1. GetSecret() :获取秘钥(32位字符串)网站

  2. GetCode() :获取动态码
  3. GetQrcode() :获取动态码二维码内容
  4. GetQrcodeUrl() :获取动态码二维码图片地址
  5. 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)
}
相关文章
相关标签/搜索