Files
tyapi-server/internal/infrastructure/external/shumai/crypto.go
2026-01-23 17:53:11 +08:00

200 lines
5.0 KiB
Go
Raw 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.

package shumai
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
"strings"
)
// SignMethod 签名方法
type SignMethod string
const (
SignMethodMD5 SignMethod = "md5"
SignMethodHMACMD5 SignMethod = "hmac"
)
// GenerateSignForm 生成表单接口签名appid & timestamp & app_security
// 拼接规则appid + "&" + timestamp + "&" + app_security对拼接串做 MD532 位小写十六进制;
// 不足 32 位左侧补 0。
func GenerateSignForm(appid, timestamp, appSecret string) string {
str := appid + "&" + timestamp + "&" + appSecret
hash := md5.Sum([]byte(str))
sign := strings.ToLower(hex.EncodeToString(hash[:]))
if n := 32 - len(sign); n > 0 {
sign = strings.Repeat("0", n) + sign
}
return sign
}
// app_secret: "BnJWo61hUgNEa5fqBCueiT1IZ1e0DxPU"
// Encrypt 使用 AES/ECB/PKCS5Padding 加密数据
// 加密算法AES工作模式ECB无初始向量填充方式PKCS5Padding
// 加密 key 是服务商分配的 app_securityAES 加密之后再进行 base64 编码
func Encrypt(data, appSecurity string) (string, error) {
key := prepareAESKey([]byte(appSecurity))
ciphertext, err := aesEncryptECB([]byte(data), key)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(ciphertext), nil
}
// Decrypt 解密 base64 编码的 AES/ECB/PKCS5Padding 加密数据
func Decrypt(encodedData, appSecurity string) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encodedData)
if err != nil {
return nil, err
}
key := prepareAESKey([]byte(appSecurity))
plaintext, err := aesDecryptECB(ciphertext, key)
if err != nil {
return nil, err
}
return plaintext, nil
}
// prepareAESKey 准备 AES 密钥,确保长度为 16/24/32 字节
// 如果 key 长度不足,用 0 填充;如果过长,截取前 32 字节
func prepareAESKey(key []byte) []byte {
keyLen := len(key)
if keyLen == 16 || keyLen == 24 || keyLen == 32 {
return key
}
if keyLen < 16 {
// 不足 16 字节,用 0 填充到 16 字节AES-128
padded := make([]byte, 16)
copy(padded, key)
return padded
}
if keyLen < 24 {
// 不足 24 字节,用 0 填充到 24 字节AES-192
padded := make([]byte, 24)
copy(padded, key)
return padded
}
if keyLen < 32 {
// 不足 32 字节,用 0 填充到 32 字节AES-256
padded := make([]byte, 32)
copy(padded, key)
return padded
}
// 超过 32 字节,截取前 32 字节AES-256
return key[:32]
}
// aesEncryptECB 使用 AES ECB 模式加密PKCS5 填充
func aesEncryptECB(plaintext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
paddedPlaintext := pkcs5Padding(plaintext, block.BlockSize())
ciphertext := make([]byte, len(paddedPlaintext))
mode := newECBEncrypter(block)
mode.CryptBlocks(ciphertext, paddedPlaintext)
return ciphertext, nil
}
// aesDecryptECB 使用 AES ECB 模式解密PKCS5 去填充
func aesDecryptECB(ciphertext, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
if len(ciphertext)%block.BlockSize() != 0 {
return nil, errors.New("ciphertext length is not a multiple of block size")
}
plaintext := make([]byte, len(ciphertext))
mode := newECBDecrypter(block)
mode.CryptBlocks(plaintext, ciphertext)
return pkcs5Unpadding(plaintext), nil
}
// pkcs5Padding PKCS5 填充
func pkcs5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// pkcs5Unpadding 去除 PKCS5 填充
func pkcs5Unpadding(src []byte) []byte {
length := len(src)
if length == 0 {
return src
}
unpadding := int(src[length-1])
if unpadding > length {
return src
}
return src[:length-unpadding]
}
// ECB 模式加密/解密实现
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (x *ecbEncrypter) BlockSize() int {
return x.blockSize
}
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
type ecbDecrypter ecb
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (x *ecbDecrypter) BlockSize() int {
return x.blockSize
}
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}