323 lines
6.7 KiB
Go
323 lines
6.7 KiB
Go
package cryptoutil
|
||
|
||
import (
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/rand"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math/big"
|
||
"net/url"
|
||
"strings"
|
||
)
|
||
|
||
// EncData 实现与JavaScript版本相同的加密功能
|
||
// clientToken参数用于替代JavaScript中的cookie值
|
||
func EncData(data interface{}, clientToken string) map[string]string {
|
||
// 检查数据是否为空
|
||
if data == nil || data == "" || data == "{}" || data == " " || data == "null" {
|
||
return map[string]string{
|
||
"ciphertext": "",
|
||
}
|
||
}
|
||
|
||
// 获取axes_max
|
||
axesMax := len(clientToken)
|
||
if axesMax < 4 {
|
||
axesMax = 4
|
||
}
|
||
|
||
// 创建axes
|
||
axes := creatAxes(axesMax)
|
||
|
||
// 替换逗号为连字符
|
||
aesaxes := strings.ReplaceAll(axes, ",", "-")
|
||
|
||
// 生成AES密钥
|
||
aesKey := encryptKey(axes, clientToken)
|
||
|
||
// 将数据转换为JSON字符串
|
||
var dataStr string
|
||
switch v := data.(type) {
|
||
case string:
|
||
dataStr = v
|
||
default:
|
||
jsonBytes, err := json.Marshal(data)
|
||
if err != nil {
|
||
return map[string]string{
|
||
"ciphertext": "",
|
||
}
|
||
}
|
||
dataStr = string(jsonBytes)
|
||
}
|
||
|
||
// 加密数据
|
||
encdata := Encrypt(dataStr, aesKey)
|
||
|
||
// 构造密文
|
||
ciphertext := encdata + "," + aesaxes
|
||
|
||
return map[string]string{
|
||
"ciphertext": ciphertext,
|
||
}
|
||
}
|
||
|
||
// DescData 实现与JavaScript版本相同的解密功能
|
||
// clientToken参数用于替代JavaScript中的cookie值
|
||
func DescData(JR map[string]interface{}, clientToken string) map[string]interface{} {
|
||
result := map[string]interface{}{
|
||
"flag": false,
|
||
"data": "",
|
||
}
|
||
|
||
if JR == nil {
|
||
return result
|
||
}
|
||
|
||
// 获取flag
|
||
flag, ok := JR["flag"].(bool)
|
||
if !ok {
|
||
flag = false
|
||
}
|
||
|
||
result["flag"] = flag
|
||
|
||
// 如果flag为false,直接返回data
|
||
if !flag {
|
||
if data, ok := JR["data"]; ok {
|
||
result["data"] = data
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 检查key是否存在
|
||
key, keyExists := JR["key"].(string)
|
||
if !keyExists || key == "" {
|
||
if data, ok := JR["data"]; ok {
|
||
result["data"] = data
|
||
}
|
||
return result
|
||
}
|
||
|
||
// 处理解密
|
||
axes := key
|
||
aesaxes := strings.ReplaceAll(axes, "-", ",")
|
||
aesKey := encryptKey(aesaxes, clientToken)
|
||
|
||
// 获取加密数据
|
||
outdata, ok := JR["data"].(string)
|
||
if !ok {
|
||
result["data"] = ""
|
||
return result
|
||
}
|
||
|
||
// URL解码(与JS版本的decodeURIComponent一致)
|
||
decodedData, err := url.QueryUnescape(outdata)
|
||
if err != nil {
|
||
// 如果URL解码失败,使用原始数据
|
||
decodedData = outdata
|
||
}
|
||
|
||
// 解密数据
|
||
decryptedData := Decrypt(decodedData, aesKey)
|
||
result["data"] = decryptedData
|
||
|
||
return result
|
||
}
|
||
|
||
// encryptKey 根据axes生成加密密钥
|
||
func encryptKey(axes string, clientToken string) string {
|
||
aaxes := strings.Split(axes, ",")
|
||
key := ""
|
||
|
||
for _, axis := range aaxes {
|
||
axisInt := parseInt(axis)
|
||
// 确保axisInt不会导致越界
|
||
if axisInt >= 0 && axisInt < len(clientToken) {
|
||
// 与JavaScript版本一致:substring(aaxes[i], aaxes[i]*1+4)
|
||
end := axisInt + 4
|
||
if end <= len(clientToken) {
|
||
key += clientToken[axisInt:end]
|
||
} else {
|
||
// 如果end超出范围,只取到末尾
|
||
key += clientToken[axisInt:]
|
||
// 填充到4个字符
|
||
for len(key)%4 != 0 {
|
||
key += "A"
|
||
}
|
||
}
|
||
} else {
|
||
// 如果起始位置越界,使用默认值
|
||
key += "abcd"
|
||
}
|
||
}
|
||
|
||
// 确保密钥长度至少为16字节,如果超过则截断
|
||
if len(key) < 16 {
|
||
// 用'A'填充到16字节
|
||
for len(key) < 16 {
|
||
key += "A"
|
||
}
|
||
} else if len(key) > 16 {
|
||
// 截断到16字节
|
||
key = key[:16]
|
||
}
|
||
|
||
return key
|
||
}
|
||
|
||
// creatAxes 创建随机axes
|
||
func creatAxes(max int) string {
|
||
if max < 4 {
|
||
max = 4
|
||
}
|
||
|
||
axes := ""
|
||
for i := 0; i < 4; i++ {
|
||
// 生成0到max-4之间的随机数
|
||
n, err := rand.Int(rand.Reader, big.NewInt(int64(max-4)))
|
||
if err != nil {
|
||
// 出错时使用默认值
|
||
axes += "0,"
|
||
continue
|
||
}
|
||
axes += fmt.Sprintf("%d,", n.Int64())
|
||
}
|
||
|
||
// 移除末尾的逗号
|
||
if len(axes) > 0 {
|
||
axes = axes[:len(axes)-1]
|
||
}
|
||
|
||
return axes
|
||
}
|
||
|
||
// parseInt 简单的字符串转整数函数
|
||
func parseInt(s string) int {
|
||
var result int
|
||
fmt.Sscanf(s, "%d", &result)
|
||
return result
|
||
}
|
||
|
||
// Encrypt AES加密函数
|
||
func Encrypt(data, key string) string {
|
||
// 确保密钥长度为16字节
|
||
keyBytes := make([]byte, 16)
|
||
if len(key) >= 16 {
|
||
copy(keyBytes, []byte(key)[:16])
|
||
} else {
|
||
copy(keyBytes, []byte(key))
|
||
// 如果密钥不足16字节,用'A'填充
|
||
for i := len(key); i < 16; i++ {
|
||
keyBytes[i] = 'A'
|
||
}
|
||
}
|
||
|
||
// 创建AES密码块
|
||
block, err := aes.NewCipher(keyBytes)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
|
||
// PKCS7填充
|
||
blockSize := block.BlockSize()
|
||
padding := blockSize - len(data)%blockSize
|
||
padText := make([]byte, padding)
|
||
for i := range padText {
|
||
padText[i] = byte(padding)
|
||
}
|
||
|
||
plaintext := append([]byte(data), padText...)
|
||
|
||
// CBC模式加密
|
||
ciphertext := make([]byte, len(plaintext))
|
||
// 使用固定的IV(与JavaScript版本一致,使用相同的key作为IV)
|
||
iv := make([]byte, blockSize)
|
||
copy(iv, keyBytes[:blockSize])
|
||
|
||
mode := cipher.NewCBCEncrypter(block, iv)
|
||
mode.CryptBlocks(ciphertext, plaintext)
|
||
|
||
// Base64编码(使用URL编码兼容CryptoJS)
|
||
return base64.StdEncoding.EncodeToString(ciphertext)
|
||
}
|
||
|
||
// Decrypt AES解密函数
|
||
func Decrypt(data, key string) string {
|
||
// Base64解码
|
||
ciphertext, err := base64.StdEncoding.DecodeString(data)
|
||
if err != nil {
|
||
// 尝试URL解码
|
||
ciphertext, err = base64.URLEncoding.DecodeString(data)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
}
|
||
|
||
// 确保密钥长度为16字节
|
||
keyBytes := make([]byte, 16)
|
||
if len(key) >= 16 {
|
||
copy(keyBytes, []byte(key)[:16])
|
||
} else {
|
||
copy(keyBytes, []byte(key))
|
||
// 如果密钥不足16字节,用'A'填充
|
||
for i := len(key); i < 16; i++ {
|
||
keyBytes[i] = 'A'
|
||
}
|
||
}
|
||
|
||
// 创建AES密码块
|
||
block, err := aes.NewCipher(keyBytes)
|
||
if err != nil {
|
||
return ""
|
||
}
|
||
|
||
// CBC模式解密
|
||
blockSize := block.BlockSize()
|
||
if len(ciphertext)%blockSize != 0 {
|
||
return ""
|
||
}
|
||
|
||
// 使用固定的IV(与JavaScript版本一致,使用相同的key作为IV)
|
||
iv := make([]byte, blockSize)
|
||
copy(iv, keyBytes[:blockSize])
|
||
|
||
mode := cipher.NewCBCDecrypter(block, iv)
|
||
plaintext := make([]byte, len(ciphertext))
|
||
mode.CryptBlocks(plaintext, ciphertext)
|
||
|
||
// 去除PKCS7填充
|
||
if len(plaintext) == 0 {
|
||
return ""
|
||
}
|
||
|
||
padding := int(plaintext[len(plaintext)-1])
|
||
// 更宽松的填充检查
|
||
if padding <= 0 || padding > blockSize || padding > len(plaintext) {
|
||
// 如果填充不合法,返回原始数据
|
||
return string(plaintext)
|
||
}
|
||
|
||
// 验证填充是否正确(更宽松的检查)
|
||
validPadding := true
|
||
for i := len(plaintext) - padding; i < len(plaintext); i++ {
|
||
if plaintext[i] != byte(padding) {
|
||
validPadding = false
|
||
break
|
||
}
|
||
}
|
||
|
||
if !validPadding {
|
||
// 填充验证失败,返回尽可能多的有效数据
|
||
return string(plaintext)
|
||
}
|
||
|
||
// 正常去除填充
|
||
if len(plaintext) <= padding {
|
||
return ""
|
||
}
|
||
|
||
return string(plaintext[:len(plaintext)-padding])
|
||
}
|