From 9023f2bf2a07a745d90d3ce27889668c59bf0f81 Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Thu, 23 Jul 2026 18:14:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6:=20ymt=5Fv?= =?UTF-8?q?3/crypto.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ymt_v3/crypto.go | 409 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 ymt_v3/crypto.go diff --git a/ymt_v3/crypto.go b/ymt_v3/crypto.go new file mode 100644 index 0000000..1c91b50 --- /dev/null +++ b/ymt_v3/crypto.go @@ -0,0 +1,409 @@ +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) +} \ No newline at end of file