复制项目
This commit is contained in:
248
pkg/common/util/aes.go
Normal file
248
pkg/common/util/aes.go
Normal file
@@ -0,0 +1,248 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user