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,对拼接串做 MD5,32 位小写十六进制; // 不足 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_security,AES 加密之后再进行 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:] } }