获取密钥

下面提供了一个将字符串转为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。

  1. 它首先使用key初始化一个HMAC-SHA1算法的计算实例。
  2. 然后对value进行HMAC哈希计算,结果存储在hash中。
  3. 它取哈希值的最后一个字节的低4位作为偏移量offset。
  4. 使用偏移量取哈希值的4个字节作为密码的源数据hashParts。
  5. 对第一个字节去除高位后转成一个uint32数字。
  6. 最后对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
}