获取密钥
下面提供了一个将字符串转为16位大写字符串的函数,返回结果可作为Google验证器的密钥。
func StrTo16Upper(s string) string {
var alphabet = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
hash := md5.Sum([]byte(s))
var result string
for i := 0; i < 16; i++ {
index := int(hash[i]) % len(alphabet)
result += string(alphabet[index])
}
return result
}验证函数
传入密钥和验证码,通过getCode函数获取Google验证码,判断输入的验证码与Google验证码是否相同。
为减小误差,对前后30s的Google验证码都进行验证。
func VerifyCode(secret string, code int32) bool {
// 当前google验证码
if getCode(secret, 0) == code {
return true
}
// 前30秒google验证码
if getCode(secret, -30) == code {
return true
}
// 后30秒google验证码
if getCode(secret, 30) == code {
return true
}
return false
}获取验证码
通过密钥和时间的偏移量获取Google验证码。
func getCode(secret string, offset int64) int32 {
key, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
fmt.Println(err)
return 0
}
epochSeconds := time.Now().Unix() + offset
return int32(oneTimePassword(key, toBytes(epochSeconds/30)))
}其它函数
toBytes
把一个int64数字转换成字节数组。它通过位运算把数字拆分成8个字节并按大端序存储。
func 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
}toUint32
把一个4字节的字节数组转换成一个uint32数字。它使用位运算来组合字节。
func toUint32(bytes []byte) uint32 {
return (uint32(bytes[0]) << 24) + (uint32(bytes[1]) << 16) +
(uint32(bytes[2]) << 8) + uint32(bytes[3])
}oneTimePassword
生成器的核心。它的参数包含一个密钥key和一个要进行HMAC运算的值value。
- 它首先使用key初始化一个HMAC-SHA1算法的计算实例。
- 然后对value进行HMAC哈希计算,结果存储在hash中。
- 它取哈希值的最后一个字节的低4位作为偏移量offset。
- 使用偏移量取哈希值的4个字节作为密码的源数据hashParts。
- 对第一个字节去除高位后转成一个uint32数字。
- 最后对1,000,000求余生成6位数的OTP。
func oneTimePassword(key []byte, value []byte) uint32 {
hmacSha1 := hmac.New(sha1.New, key)
hmacSha1.Write(value)
hash := hmacSha1.Sum(nil)
offset := hash[len(hash)-1] & 0x0F
hashParts := hash[offset : offset+4]
hashParts[0] = hashParts[0] & 0x7F
number := toUint32(hashParts)
pwd := number % 1000000
return pwd
}