f
This commit is contained in:
199
internal/infrastructure/external/shumai/crypto.go
vendored
Normal file
199
internal/infrastructure/external/shumai/crypto.go
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
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:]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user