90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
// Package gateway 提供 Gateway API 加密和签名工具函数
|
||
package gateway
|
||
|
||
import (
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/md5"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
||
)
|
||
|
||
const aesBlockSize = 16
|
||
|
||
// 注意:AES-ECB 存在严重安全缺陷(相同明文块会产生相同密文块),
|
||
// 这是 Gateway 强制要求无法改变,生产环境必须使用 HTTPS 保障传输层安全。
|
||
func aesEncrypt(data []byte, appSecret string) (string, error) {
|
||
key := md5.Sum([]byte(appSecret))
|
||
block, err := aes.NewCipher(key[:])
|
||
if err != nil {
|
||
return "", errors.Wrap(errors.CodeGatewayEncryptError, err, "数据加密失败")
|
||
}
|
||
|
||
// 使用 PKCS5 进行填充,确保明文长度为 16 的整数倍
|
||
padded := pkcs5Padding(data, aesBlockSize)
|
||
encrypted := make([]byte, len(padded))
|
||
newECBEncrypter(block).CryptBlocks(encrypted, padded)
|
||
return base64.StdEncoding.EncodeToString(encrypted), nil
|
||
}
|
||
|
||
// generateSign 生成 Gateway 签名(appId、data、timestamp、key 字母序)
|
||
func generateSign(appID, encryptedData string, timestamp int64, appSecret string) string {
|
||
var builder strings.Builder
|
||
builder.WriteString("appId=")
|
||
builder.WriteString(appID)
|
||
builder.WriteString("&data=")
|
||
builder.WriteString(encryptedData)
|
||
builder.WriteString("×tamp=")
|
||
builder.WriteString(strconv.FormatInt(timestamp, 10))
|
||
builder.WriteString("&key=")
|
||
builder.WriteString(appSecret)
|
||
|
||
sum := md5.Sum([]byte(builder.String()))
|
||
return strings.ToUpper(hex.EncodeToString(sum[:]))
|
||
}
|
||
|
||
// ecb 表示 AES-ECB 加密模式的基础结构
|
||
type ecb struct {
|
||
b cipher.Block
|
||
blockSize int
|
||
}
|
||
|
||
type ecbEncrypter ecb
|
||
|
||
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
|
||
if b == nil {
|
||
panic("crypto/cipher: 传入的加密块为空")
|
||
}
|
||
return &ecbEncrypter{b: b, blockSize: b.BlockSize()}
|
||
}
|
||
|
||
func (x *ecbEncrypter) BlockSize() int {
|
||
return x.blockSize
|
||
}
|
||
|
||
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
|
||
if len(src)%x.blockSize != 0 {
|
||
panic("crypto/cipher: 输入数据不是完整块")
|
||
}
|
||
for len(src) > 0 {
|
||
x.b.Encrypt(dst, src[:x.blockSize])
|
||
src = src[x.blockSize:]
|
||
dst = dst[x.blockSize:]
|
||
}
|
||
}
|
||
|
||
// pkcs5Padding 对明文进行 PKCS5 填充
|
||
func pkcs5Padding(data []byte, blockSize int) []byte {
|
||
padding := blockSize - len(data)%blockSize
|
||
padded := make([]byte, len(data)+padding)
|
||
copy(padded, data)
|
||
for i := len(data); i < len(padded); i++ {
|
||
padded[i] = byte(padding)
|
||
}
|
||
return padded
|
||
}
|