Files
kim.dev.6789 b7f8db7d08 复制项目
2026-01-14 22:35:45 +08:00

249 lines
6.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"io"
"time"
"github.com/openimsdk/tools/errs"
)
const (
// AES密钥字符串
aesKeyString = "F9cLjK3FgkvcR6vaWZKEj3DYJfwrIHMULLwpmDAj553mpmlvMYoLNIqajLnhIsWq"
)
// DecryptVerifyCode 解密验证码
// encryptedText: Base64编码的加密字符串
// 返回: 解密后的明文(格式:验证码-时间戳)
func DecryptVerifyCode(encryptedText string) (string, error) {
// 解码Base64
encrypted, err := base64.StdEncoding.DecodeString(encryptedText)
if err != nil {
return "", errs.WrapMsg(err, "base64解码失败")
}
// 使用SHA-256哈希密钥字符串
keyBytes := sha256.Sum256([]byte(aesKeyString))
key := keyBytes[:]
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 检查密文长度
if len(encrypted) < aes.BlockSize {
return "", errs.New("密文长度不足")
}
// 使用CBC模式固定IV全零
iv := make([]byte, aes.BlockSize) // 全零IV
mode := cipher.NewCBCDecrypter(block, iv)
// 解密
decrypted := make([]byte, len(encrypted))
mode.CryptBlocks(decrypted, encrypted)
// 去除PKCS7填充
decrypted = unpadPKCS7(decrypted)
return string(decrypted), nil
}
// unpadPKCS7 去除PKCS7填充
func unpadPKCS7(data []byte) []byte {
if len(data) == 0 {
return data
}
padLen := int(data[len(data)-1])
if padLen > len(data) || padLen == 0 {
return data
}
// 验证填充
for i := len(data) - padLen; i < len(data); i++ {
if data[i] != byte(padLen) {
return data
}
}
return data[:len(data)-padLen]
}
// ParseVerifyCodeWithTimestamp 解析验证码和时间戳
// format: "验证码-时间戳"
// 返回: 验证码字符串和时间戳(毫秒)
func ParseVerifyCodeWithTimestamp(decryptedText string) (string, int64, error) {
var code string
var timestamp int64
// 查找最后一个 "-" 分隔符
lastDashIndex := -1
for i := len(decryptedText) - 1; i >= 0; i-- {
if decryptedText[i] == '-' {
lastDashIndex = i
break
}
}
if lastDashIndex == -1 {
return "", 0, errs.New("格式错误:未找到时间戳分隔符")
}
code = decryptedText[:lastDashIndex]
timestampStr := decryptedText[lastDashIndex+1:]
// 解析时间戳
_, err := fmt.Sscanf(timestampStr, "%d", &timestamp)
if err != nil {
return "", 0, errs.WrapMsg(err, "解析时间戳失败")
}
return code, timestamp, nil
}
// ValidateTimestamp 验证时间戳是否在有效期内
// timestamp: 毫秒级时间戳
// maxAgeSeconds: 最大允许的秒数允许的时间差默认30秒
// 返回: 是否有效
func ValidateTimestamp(timestamp int64, maxAgeSeconds int64) bool {
if timestamp <= 0 {
return false
}
currentTime := getCurrentTimestamp()
diff := currentTime - timestamp
// 转换为毫秒
maxAgeMillis := maxAgeSeconds * 1000
// 时间戳不能是未来的允许5秒的时钟偏差
if diff < -5000 {
return false
}
// 时间差不能超过maxAgeSeconds秒
if diff > maxAgeMillis {
return false
}
return true
}
// getCurrentTimestamp 获取当前时间戳(毫秒)
func getCurrentTimestamp() int64 {
return time.Now().UnixMilli()
}
// padPKCS7 添加PKCS7填充
func padPKCS7(data []byte, blockSize int) []byte {
padLen := blockSize - len(data)%blockSize
pad := make([]byte, padLen)
for i := range pad {
pad[i] = byte(padLen)
}
return append(data, pad...)
}
// EncryptPhone 加密手机号
// phoneNumber: 手机号明文
// 返回: Base64编码的加密字符串
func EncryptPhone(phoneNumber string) (string, error) {
if phoneNumber == "" {
return "", errs.New("手机号不能为空")
}
// 使用SHA-256哈希密钥字符串
keyBytes := sha256.Sum256([]byte(aesKeyString))
key := keyBytes[:]
// 创建AES cipher
block, err := aes.NewCipher(key)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 使用CBC模式固定IV全零
iv := make([]byte, aes.BlockSize) // 全零IV
// 添加PKCS7填充
plaintext := padPKCS7([]byte(phoneNumber), aes.BlockSize)
// 加密
mode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(plaintext))
mode.CryptBlocks(encrypted, plaintext)
// 编码为Base64
return base64.StdEncoding.EncodeToString(encrypted), nil
}
// DecryptPhone 解密手机号
// encryptedText: Base64编码的加密字符串
// 返回: 解密后的手机号明文
func DecryptPhone(encryptedText string) (string, error) {
// 复用DecryptVerifyCode的解密逻辑手机号没有时间戳格式直接解密
return DecryptVerifyCode(encryptedText)
}
// EncryptRealNameAuthData 加密实名认证数据(使用 AES-GCM 模式)
// data: JSON字符串例如: {"cardNo":"330329199001021122","realName":"李四"}
// key: AES密钥字符串与服务端相同的默认密钥
// 返回: Base64编码的加密字符串
func EncryptRealNameAuthData(data string, key string) (string, error) {
if data == "" {
return "", errs.New("数据不能为空")
}
if key == "" {
return "", errs.New("密钥不能为空")
}
// 使用 SHA256 哈希密钥字符串(与服务端保持一致)
// 注意:直接对密钥字符串进行哈希,而不是先转换为字节
hash := sha256.Sum256([]byte(key))
aesKey := hash[:]
// 创建AES cipher
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", errs.WrapMsg(err, "创建AES cipher失败")
}
// 使用 GCM 模式
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", errs.WrapMsg(err, "创建 GCM 失败")
}
// 生成随机 nonce
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return "", errs.WrapMsg(err, "生成 nonce 失败")
}
// 加密
ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil)
// 编码为Base64
return base64.StdEncoding.EncodeToString(ciphertext), nil
}