From db6a614a88c21080588e338c38bf59e531d1d2de Mon Sep 17 00:00:00 2001 From: renzhiyuan <465386466@qq.com> Date: Tue, 21 Jul 2026 15:18:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=87=E4=BB=B6:=20ymt=5Fv?= =?UTF-8?q?3=5F2/crypto.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ymt_v3_2/crypto.go | 246 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 ymt_v3_2/crypto.go diff --git a/ymt_v3_2/crypto.go b/ymt_v3_2/crypto.go new file mode 100644 index 0000000..05c31ab --- /dev/null +++ b/ymt_v3_2/crypto.go @@ -0,0 +1,246 @@ +package ymt_v3_2 + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "sort" + "strings" +) + +func parsePrivateKey(pemData string) (*rsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(pemData)) + if block == nil { + return nil, errors.New("failed to decode PEM block") + } + key, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + key, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse private key: %w", err) + } + } + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("not an RSA private key") + } + return rsaKey, nil +} + +func parsePublicKey(pemData string) (*rsa.PublicKey, error) { + block, _ := pem.Decode([]byte(pemData)) + if block == nil { + return nil, errors.New("failed to decode PEM block") + } + key, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, fmt.Errorf("parse public key: %w", err) + } + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return nil, errors.New("not an RSA public key") + } + return rsaKey, nil +} + +func marshalSortedNoZero(v interface{}) ([]byte, error) { + data, err := json.Marshal(v) + if err != nil { + return nil, err + } + var raw map[string]interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return nil, err + } + + filtered := removeZeroValues(raw) + + keys := make([]string, 0, len(filtered)) + for k := range filtered { + 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(filtered[k]) + if err != nil { + return nil, err + } + buf.Write(valBytes) + } + buf.WriteByte('}') + return buf.Bytes(), nil +} + +func removeZeroValues(m map[string]interface{}) map[string]interface{} { + res := make(map[string]interface{}) + for k, v := range m { + if isZeroValue(v) { + continue + } + if sub, ok := v.(map[string]interface{}); ok { + subFiltered := removeZeroValues(sub) + if len(subFiltered) > 0 { + res[k] = subFiltered + } + } else { + res[k] = v + } + } + return res +} + +func isZeroValue(v interface{}) bool { + if v == nil { + return true + } + switch val := v.(type) { + case string: + return val == "" + case float64: + return val == 0 + case bool: + return !val + case []interface{}: + return len(val) == 0 + case map[string]interface{}: + return len(val) == 0 + default: + return false + } +} + +func aesEncryptECB(plaintext, key []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + blockSize := block.BlockSize() + plaintext = pkcs7Padding(plaintext, blockSize) + + ciphertext := make([]byte, len(plaintext)) + for i := 0; i < len(plaintext); i += blockSize { + block.Encrypt(ciphertext[i:i+blockSize], plaintext[i:i+blockSize]) + } + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +func aesDecryptECB(ciphertext string, key []byte) ([]byte, error) { + data, err := base64.StdEncoding.DecodeString(ciphertext) + if err != nil { + return nil, err + } + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + blockSize := block.BlockSize() + if len(data)%blockSize != 0 { + return nil, errors.New("ciphertext is not a multiple of the block size") + } + plaintext := make([]byte, len(data)) + for i := 0; i < len(data); i += blockSize { + block.Decrypt(plaintext[i:i+blockSize], data[i:i+blockSize]) + } + plaintext, err = pkcs7Unpadding(plaintext, blockSize) + if err != nil { + return nil, err + } + return plaintext, nil +} + +func pkcs7Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padtext...) +} + +func pkcs7Unpadding(data []byte, blockSize int) ([]byte, error) { + length := len(data) + if length == 0 { + return nil, errors.New("empty data") + } + padding := int(data[length-1]) + if padding > blockSize || padding == 0 { + return nil, errors.New("invalid padding") + } + for i := length - padding; i < length; i++ { + if data[i] != byte(padding) { + return nil, errors.New("invalid padding") + } + } + return data[:length-padding], nil +} + +func rsaSign(data []byte, privateKey *rsa.PrivateKey) (string, error) { + hashed := sha256.Sum256(data) + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:]) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(signature), nil +} + +func rsaVerify(data []byte, sign string, publicKey *rsa.PublicKey) error { + signBytes, err := base64.StdEncoding.DecodeString(sign) + if err != nil { + return err + } + hashed := sha256.Sum256(data) + return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], signBytes) +} + +func (c *Client) VerifyCallbackSign(appID, timestamp, sign string, callbackData map[string]interface{}) error { + filtered := removeZeroValues(callbackData) + keys := make([]string, 0, len(filtered)) + for k := range filtered { + 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(filtered[k]) + if err != nil { + return err + } + buf.Write(valBytes) + } + buf.WriteByte('}') + plaintext := buf.Bytes() + + ciphertext, err := aesEncryptECB(plaintext, c.aesKey) + if err != nil { + return err + } + + signStr := appID + timestamp + ciphertext + return rsaVerify([]byte(signStr), sign, c.publicKey) +} + +var _ = cipher.NewCBCEncrypter +var _ = strings.ReplaceAll \ No newline at end of file