409 lines
10 KiB
Go
409 lines
10 KiB
Go
package ymt_v3
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto"
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/rand"
|
||
"crypto/rsa"
|
||
"crypto/sha256"
|
||
"crypto/x509"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"encoding/pem"
|
||
"fmt"
|
||
"io"
|
||
"reflect"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/tjfoc/gmsm/sm4"
|
||
)
|
||
|
||
// EncryptType 加密类型
|
||
type EncryptType string
|
||
|
||
const (
|
||
// EncryptTypeAES AES-ECB加密
|
||
EncryptTypeAES EncryptType = "aes"
|
||
// EncryptTypeSM4 SM4-CBC加密
|
||
EncryptTypeSM4 EncryptType = "sm4"
|
||
)
|
||
|
||
// ==================== RSA签名 ====================
|
||
|
||
// SignWithRSA 使用RSA私钥对数据进行签名
|
||
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
|
||
block, _ := pem.Decode([]byte(privateKeyPEM))
|
||
if block == nil {
|
||
return "", fmt.Errorf("failed to decode PEM private key")
|
||
}
|
||
|
||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||
if err != nil {
|
||
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
|
||
if err != nil {
|
||
return "", fmt.Errorf("failed to parse private key: %v", err)
|
||
}
|
||
}
|
||
|
||
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
|
||
if !ok {
|
||
return "", fmt.Errorf("not a RSA private key")
|
||
}
|
||
|
||
h := sha256.New()
|
||
h.Write([]byte(signStr))
|
||
hashed := h.Sum(nil)
|
||
|
||
signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, crypto.SHA256, hashed)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return base64.StdEncoding.EncodeToString(signature), nil
|
||
}
|
||
|
||
// VerifyWithRSA 使用RSA公钥验证签名
|
||
func VerifyWithRSA(signStr, signature string, publicKeyPEM string) error {
|
||
block, _ := pem.Decode([]byte(publicKeyPEM))
|
||
if block == nil {
|
||
return fmt.Errorf("failed to decode PEM public key")
|
||
}
|
||
|
||
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to parse public key: %v", err)
|
||
}
|
||
|
||
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
|
||
if !ok {
|
||
return fmt.Errorf("not a RSA public key")
|
||
}
|
||
|
||
h := sha256.New()
|
||
h.Write([]byte(signStr))
|
||
hashed := h.Sum(nil)
|
||
|
||
sigBytes, err := base64.StdEncoding.DecodeString(signature)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to decode signature: %v", err)
|
||
}
|
||
|
||
return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, sigBytes)
|
||
}
|
||
|
||
// ==================== AES ECB加密 ====================
|
||
|
||
// AESECBEncrypt AES-ECB模式加密
|
||
func AESECBEncrypt(plaintext []byte, key []byte) (string, error) {
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建AES cipher失败: %v", err)
|
||
}
|
||
|
||
padded := pkcs7Padding(plaintext, aes.BlockSize)
|
||
|
||
ciphertext := make([]byte, len(padded))
|
||
for i := 0; i < len(padded); i += aes.BlockSize {
|
||
block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])
|
||
}
|
||
|
||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||
}
|
||
|
||
// AESECBDecrypt AES-ECB模式解密
|
||
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
|
||
ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解码失败: %v", err)
|
||
}
|
||
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建AES cipher失败: %v", err)
|
||
}
|
||
|
||
if len(ciphertext)%aes.BlockSize != 0 {
|
||
return nil, fmt.Errorf("密文长度不是块大小的整数倍")
|
||
}
|
||
|
||
plaintext := make([]byte, len(ciphertext))
|
||
for i := 0; i < len(ciphertext); i += aes.BlockSize {
|
||
block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])
|
||
}
|
||
|
||
plaintext, err = pkcs7UnPadding(plaintext)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("去除填充失败: %v", err)
|
||
}
|
||
|
||
return plaintext, nil
|
||
}
|
||
|
||
// ==================== SM4 CBC加密 ====================
|
||
|
||
// SM4CBCEncrypt SM4-CBC模式加密
|
||
// IV会前置到密文中,解密时自动提取
|
||
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
|
||
if len(key) != 16 {
|
||
return "", fmt.Errorf("SM4密钥长度必须为16字节")
|
||
}
|
||
|
||
block, err := sm4.NewCipher(key)
|
||
if err != nil {
|
||
return "", fmt.Errorf("创建SM4 cipher失败: %v", err)
|
||
}
|
||
|
||
padded := pkcs7Padding(plaintext, block.BlockSize())
|
||
|
||
iv := make([]byte, block.BlockSize())
|
||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||
return "", fmt.Errorf("生成IV失败: %v", err)
|
||
}
|
||
|
||
mode := cipher.NewCBCEncrypter(block, iv)
|
||
ciphertext := make([]byte, len(padded))
|
||
mode.CryptBlocks(ciphertext, padded)
|
||
|
||
// IV前置到密文
|
||
result := append(iv, ciphertext...)
|
||
return base64.StdEncoding.EncodeToString(result), nil
|
||
}
|
||
|
||
// SM4CBCDecrypt SM4-CBC模式解密
|
||
// 从密文中提取前置的IV进行解密
|
||
func SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {
|
||
if len(key) != 16 {
|
||
return nil, fmt.Errorf("SM4密钥长度必须为16字节")
|
||
}
|
||
|
||
data, err := base64.StdEncoding.DecodeString(encryptedData)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("解码失败: %v", err)
|
||
}
|
||
|
||
block, err := sm4.NewCipher(key)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
|
||
}
|
||
|
||
blockSize := block.BlockSize()
|
||
if len(data) < blockSize*2 {
|
||
return nil, fmt.Errorf("数据长度不足")
|
||
}
|
||
iv := data[:blockSize]
|
||
ciphertext := data[blockSize:]
|
||
|
||
mode := cipher.NewCBCDecrypter(block, iv)
|
||
plaintext := make([]byte, len(ciphertext))
|
||
mode.CryptBlocks(plaintext, ciphertext)
|
||
|
||
plaintext, err = pkcs7UnPadding(plaintext)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("去除填充失败: %v", err)
|
||
}
|
||
|
||
return plaintext, nil
|
||
}
|
||
|
||
// ==================== PKCS7填充 ====================
|
||
|
||
// pkcs7Padding PKCS7填充
|
||
func pkcs7Padding(data []byte, blockSize int) []byte {
|
||
padding := blockSize - len(data)%blockSize
|
||
padText := bytes.Repeat([]byte{byte(padding)}, padding)
|
||
return append(data, padText...)
|
||
}
|
||
|
||
// pkcs7UnPadding 去除PKCS7填充
|
||
func pkcs7UnPadding(data []byte) ([]byte, error) {
|
||
length := len(data)
|
||
if length == 0 {
|
||
return nil, fmt.Errorf("数据为空")
|
||
}
|
||
padding := int(data[length-1])
|
||
if padding > length || padding == 0 {
|
||
return nil, fmt.Errorf("无效的填充")
|
||
}
|
||
for i := length - padding; i < length; i++ {
|
||
if data[i] != byte(padding) {
|
||
return nil, fmt.Errorf("无效的填充")
|
||
}
|
||
}
|
||
return data[:length-padding], nil
|
||
}
|
||
|
||
// ==================== 业务参数加密/解密 ====================
|
||
|
||
// EncryptBizParams 加密业务参数
|
||
// 1. 将业务参数去掉"零"值的参数,再由小到大按照字母排序,转成json字符串得到plaintext
|
||
// 2. 使用应用key将plaintext字符串加密,得到ciphertext
|
||
func EncryptBizParams(params interface{}, key []byte, encType EncryptType) (string, error) {
|
||
plaintext, err := marshalWithoutZero(params)
|
||
if err != nil {
|
||
return "", fmt.Errorf("序列化业务参数失败: %v", err)
|
||
}
|
||
|
||
switch encType {
|
||
case EncryptTypeAES:
|
||
return AESECBEncrypt([]byte(plaintext), key)
|
||
case EncryptTypeSM4:
|
||
return SM4CBCEncrypt([]byte(plaintext), key)
|
||
default:
|
||
return "", fmt.Errorf("不支持的加密类型: %s", encType)
|
||
}
|
||
}
|
||
|
||
// DecryptBizParams 解密业务参数
|
||
func DecryptBizParams(ciphertext string, key []byte, encType EncryptType) ([]byte, error) {
|
||
switch encType {
|
||
case EncryptTypeAES:
|
||
return AESECBDecrypt(ciphertext, key)
|
||
case EncryptTypeSM4:
|
||
return SM4CBCDecrypt(ciphertext, key)
|
||
default:
|
||
return nil, fmt.Errorf("不支持的加密类型: %s", encType)
|
||
}
|
||
}
|
||
|
||
// EncryptBizParamsRaw 直接加密字节数据(用于回调验签)
|
||
func EncryptBizParamsRaw(plaintext []byte, key []byte, encType EncryptType) (string, error) {
|
||
switch encType {
|
||
case EncryptTypeAES:
|
||
return AESECBEncrypt(plaintext, key)
|
||
case EncryptTypeSM4:
|
||
return SM4CBCEncrypt(plaintext, key)
|
||
default:
|
||
return "", fmt.Errorf("不支持的加密类型: %s", encType)
|
||
}
|
||
}
|
||
|
||
// marshalWithoutZero 将结构体序列化为JSON,去掉零值字段并按字母排序
|
||
func marshalWithoutZero(v interface{}) (string, error) {
|
||
val := reflect.ValueOf(v)
|
||
if val.Kind() == reflect.Ptr {
|
||
val = val.Elem()
|
||
}
|
||
|
||
if val.Kind() != reflect.Struct {
|
||
if val.Kind() == reflect.Map {
|
||
// 类型断言为 map[string]interface{}
|
||
m, ok := v.(map[string]interface{})
|
||
if !ok {
|
||
// 尝试从 reflect 转换
|
||
m = make(map[string]interface{})
|
||
for _, key := range val.MapKeys() {
|
||
m[fmt.Sprintf("%v", key.Interface())] = val.MapIndex(key).Interface()
|
||
}
|
||
}
|
||
return marshalMapSorted(m)
|
||
}
|
||
data, err := json.Marshal(v)
|
||
return string(data), err
|
||
}
|
||
|
||
result := make(map[string]interface{})
|
||
typ := val.Type()
|
||
|
||
for i := 0; i < val.NumField(); i++ {
|
||
field := val.Field(i)
|
||
fieldType := typ.Field(i)
|
||
|
||
jsonTag := fieldType.Tag.Get("json")
|
||
if jsonTag == "" || jsonTag == "-" {
|
||
continue
|
||
}
|
||
name := strings.Split(jsonTag, ",")[0]
|
||
|
||
if isZeroValue(field) {
|
||
continue
|
||
}
|
||
|
||
result[name] = field.Interface()
|
||
}
|
||
|
||
return marshalMapSorted(result)
|
||
}
|
||
|
||
// marshalMapSorted 将map按键排序后序列化为JSON
|
||
func marshalMapSorted(m map[string]interface{}) (string, error) {
|
||
keys := make([]string, 0, len(m))
|
||
for k := range m {
|
||
keys = append(keys, k)
|
||
}
|
||
sort.Strings(keys)
|
||
|
||
var buf bytes.Buffer
|
||
buf.WriteByte('{')
|
||
for i, k := range keys {
|
||
if i > 0 {
|
||
buf.WriteByte(',')
|
||
}
|
||
keyBytes, _ := json.Marshal(k)
|
||
buf.Write(keyBytes)
|
||
buf.WriteByte(':')
|
||
valBytes, err := json.Marshal(m[k])
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
buf.Write(valBytes)
|
||
}
|
||
buf.WriteByte('}')
|
||
|
||
return buf.String(), nil
|
||
}
|
||
|
||
// isZeroValue 判断reflect.Value是否为零值
|
||
func isZeroValue(v reflect.Value) bool {
|
||
switch v.Kind() {
|
||
case reflect.String:
|
||
return v.String() == ""
|
||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||
return v.Int() == 0
|
||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||
return v.Uint() == 0
|
||
case reflect.Float32, reflect.Float64:
|
||
return v.Float() == 0
|
||
case reflect.Bool:
|
||
return !v.Bool()
|
||
case reflect.Slice, reflect.Map, reflect.Ptr:
|
||
return v.IsNil()
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// ==================== 签名相关 ====================
|
||
|
||
// BuildSignStr 构建签名字符串
|
||
// 规则:app_id + timestamp + ciphertext
|
||
func BuildSignStr(appID, timestamp, ciphertext string) string {
|
||
return appID + timestamp + ciphertext
|
||
}
|
||
|
||
// GenerateTimestamp 生成时间戳,格式 yyyy-MM-dd HH:mm:ss
|
||
func GenerateTimestamp() string {
|
||
return time.Now().Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
// ==================== 回调验签 ====================
|
||
|
||
// VerifyNotifySign 验证回调通知签名
|
||
func VerifyNotifySign(header *NotifyHeader, data interface{}, key []byte, publicKeyPEM string, encType EncryptType) error {
|
||
plaintext, err := marshalWithoutZero(data)
|
||
if err != nil {
|
||
return fmt.Errorf("序列化回调数据失败: %v", err)
|
||
}
|
||
|
||
ciphertext, err := EncryptBizParamsRaw([]byte(plaintext), key, encType)
|
||
if err != nil {
|
||
return fmt.Errorf("加密回调数据失败: %v", err)
|
||
}
|
||
|
||
signStr := BuildSignStr(header.Appid, header.Timestamp, ciphertext)
|
||
|
||
return VerifyWithRSA(signStr, header.Sign, publicKeyPEM)
|
||
} |