go版本,google-authenticator动态口令算法,二次安全校验

go版本,google-authenticator动态口令算法,二次安全校验

登录安全二次校验,可以有效的提升账户安全等级,目前常用的方法:手机短信二次校验、动态口令

本文介绍google-authenticator动态口口令算法,以及加解密以及二维码生成

动态安全口令,秘钥一般通过二维码少吗形式自动获得
系统身份认证快速升级:
活体人脸实名认证H5版,让您的系统身份认证更人性化

一、生成和解密秘钥

随机生成字符串,加密转成base32,encoding.go

go 复制代码
package main

import (
	"crypto/rand"
	"fmt"
	"log"
	"strconv"
	"strings"
)

// Base32 字符表 33个字符
var Base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="

// SourceCreatSecret 生成秘钥:长度在16~128之间,从允许的base32字符中随机选择。
func SourceCreatSecret(secretLen int) string {
	// 有效的秘密长度是80到640位:16*5~128*5,base32每个5位一个字节
	if secretLen<16 || secretLen >128{
		log.Fatalln("随机字符长度异常:16~128")
	}
	randomBytes := make([]byte, secretLen)

	_, err := rand.Read(randomBytes)
	if err != nil {
		log.Fatalln("随机字符异常:", err.Error())
	}
	var secret string

	for _, i := range randomBytes {
		secret += string(Base32Chars[i&31])
	}

	return secret
}

// SourceDecodeSecret 解码 Base32 编码的字符串
func SourceDecodeSecret(secret string) string {

	padChar := Base32Chars[32:]
	paddingCharCount := strings.Count(secret, padChar)
	allowedValues := []int{6, 4, 3, 1, 0}

	if !InArray(paddingCharCount, allowedValues) {
		log.Fatalln("填充字符数量错误:", paddingCharCount)
	}

	// 校验秘钥带的填充字符数量,与实际应该填充的字符是否相同
	for i := 0; i < 4; i++ {
		if paddingCharCount == allowedValues[i] {
			if secret[strings.Index(secret, padChar):] != strings.Repeat(padChar, allowedValues[i]) {
				log.Fatalln("填充字符错误:", secret[strings.Index(secret, padChar):])
			}
		}
	}

	secret = strings.Replace(secret, "=", "", -1)
	// 每 8 个字符进行分组解码
	var strs []byte
	for i := 0; i < len(secret); i += 8 {
		var x string
		// 查看当前字符是否是合法字符
		if !strings.Contains(Base32Chars, string(secret[i])) {
			log.Fatalln("秘钥包含非法字符:", string(secret[i]))
		}

		// 将当前的8个字符转成二进制、拼接在一起,空值使用0
		for j := 0; j < 8; j++ {
			// 将字符转成二进制
			if len(secret) > (i + j) {
				x += fmt.Sprintf("%05b", strings.Index(Base32Chars, string(secret[i+j])))
			} else {
				x += fmt.Sprintf("%05b", 0)
			}
		}

		l := len(x)
		for i := 0; i < l; i += 8 {
			end := i + 8
			if end > l {
				end = l
			}
		}
		for i := 0; i < len(x)/8; i++ {
			it, _ := strconv.ParseInt(x[i*8:(i+1)*8], 2, 64)
			strs = append(strs, uint8(it))
		}
	}
	return string(strs)
}

2、生成动态口令和校验

动态口令生成和校验

go 复制代码
package main

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/binary"
	"fmt"
	"time"
)

func InArray(check int, origin []int) bool {
	var exits bool
	for _, i := range origin {
		if i == check {
			exits = true
			break
		}
	}

	return exits
}

// getCode 根据解密的秘钥,获取动态口令码
func GetCode(key string, timeSlice int) string {

	var code string

	time := make([]byte, 8)
	binary.BigEndian.PutUint64(time, uint64(timeSlice))

	h := hmac.New(sha1.New, []byte(key))
	h.Write(time)
	hm := h.Sum(nil)
	fmt.Println()

	// 计算偏移量
	offset := hm[len(hm)-1] & 0x0F
	hashPart := hm[offset : offset+4]

	// 将哈希值解包为整数
	value := binary.BigEndian.Uint32(hashPart) & 0x7FFFFFFF
	modulo := uint32(1000000)
	code = fmt.Sprintf("%06d", value%modulo)
	return code
}

// VerifyCode 动态码校验,其实就是生成当前时间戳,前后30秒的动态码,与输入的动态码进行对比;discrepancy:1:前后误差30秒;2:前后误差1分钟
func VerifyCode(secret, code string, discrepancy, currentTimeSlice int) bool {
	if currentTimeSlice <= 0 {
		currentTimeSlice = int(time.Now().Unix() / 30)
	}

	if discrepancy < 1 {
		discrepancy = 1
	}

	if len(code) != 6 {
		return false
	}

	for i := -discrepancy; i <= discrepancy; i++ {
		calculatedCode := GetCode(secret, currentTimeSlice+i)
		if calculatedCode == code {
			return true
		}
	}
	return false
}

// GetQrCode 生成二维码:secret(秘钥) ,accountName(用户名:必填), issuer(系统名称),appKey(聚合数据接口key)
func GetQrCode(secret, accountName, issuer, appKey string) string {
	// otpauth://totp/Issuer:AccountName?secret=Base32Secret&issuer=IssuerName
	urlEncode := url.QueryEscape(fmt.Sprintf("otpauth://totp/%s?secret=%s", accountName, secret))
	if issuer != "" {
		urlEncode += url.QueryEscape("&issuer=" + url.QueryEscape(issuer))
	}
	return fmt.Sprintf("https://apis.juhe.cn/qrcode/api?key=%s&text=%s&w=200&h=200&el=m&type=2", appKey, urlEncode)
}

三、使用

使用二维码生成,需要申请二维码生成

go 复制代码
package main

import (
	"fmt"
	"math"
	"time"
)

var (
	secret string
	key    string
	times  int
	code   string
	checked bool
	appKey string
)

func main() {
	times = int(math.Floor(float64(time.Now().Unix() / 30)))
	// 生成秘钥
	secret = google2fa.SourceCreatSecret(101)
	fmt.Println(GetQrCode(secret, "juhe007", "聚合数据", appKey))
	// https://apis.juhe.cn/qrcode/api?key=883d38*******a6955df906ca&text=otpauth%3A%2F%2Ftotp%2Fjuhe007%3Fsecret%3D7GAKYUQRJDBZBAPTZO6RV7JHQBDWBBGHLGYDD5EFG3UOOGTWKWQYK5UOBPZCA2IS7B64MME5KBFT2W63ERZ6GJCNN23K67TNWBTWB%26issuer%3D%25E8%2581%259A%25E5%2590%2588%25E6%2595%25B0%25E6%258D%25AE&w=200&h=200&el=m&type=2
	fmt.Println(secret)
	// 解密生成的秘钥
	key = google2fa.SourceDecodeSecret(secret)
	// 根据秘钥和当前时间片生成动态码
	code = google2fa.GetCode(key, times)
	fmt.Println(key)
	fmt.Println("========================:动态口令验证=202:", code, "==", times, "==")
	// 校验一分钟(2)误差的动态码
	checked=google2fa.VerifyCode(key, code, 2, 0)
	fmt.Println(checked)
	fmt.Println()

}
相关推荐
ai小鬼头13 分钟前
AIStarter最新版怎么卸载AI项目?一键删除操作指南(附路径设置技巧)
前端·后端·github
Touper.19 分钟前
SpringBoot -- 自动配置原理
java·spring boot·后端
水木兰亭41 分钟前
数据结构之——树及树的存储
数据结构·c++·学习·算法
一只叫煤球的猫1 小时前
普通程序员,从开发到管理岗,为什么我越升职越痛苦?
前端·后端·全栈
一只鹿鹿鹿1 小时前
信息化项目验收,软件工程评审和检查表单
大数据·人工智能·后端·智慧城市·软件工程
专注VB编程开发20年1 小时前
开机自动后台运行,在Windows服务中托管ASP.NET Core
windows·后端·asp.net
程序员岳焱1 小时前
Java 与 MySQL 性能优化:MySQL全文检索查询优化实践
后端·mysql·性能优化
Jess072 小时前
插入排序的简单介绍
数据结构·算法·排序算法
老一岁2 小时前
选择排序算法详解
数据结构·算法·排序算法
xindafu2 小时前
代码随想录算法训练营第四十二天|动态规划part9
算法·动态规划