200 lines
5.0 KiB
Go
200 lines
5.0 KiB
Go
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:]
|
||
}
|
||
}
|