249 lines
6.4 KiB
Go
249 lines
6.4 KiB
Go
// 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", ×tamp)
|
||
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
|
||
}
|