ymt_v3-20260723170706/ymt_v3/generate.md

1701 lines
56 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

现在我来生成完整的 SDK 代码。根据文档,我需要:
1. 签名规则:拼接 app_id + timestamp + ciphertext然后用 RSA 私钥签名
2. 加密规则:业务参数去掉零值,按字母排序,转 JSON然后用 AES-ECB 或 SM4-CBC 加密
3. 时间戳格式yyyy-MM-dd HH:mm:ss
让我生成所有 6 个文件。
// File: ymt_v3/go.mod
```
module ymt_v3
go 1.21
require github.com/tjfoc/gmsm v1.4.1
```
// File: ymt_v3/errors.go
```go
package ymt_v3
import "fmt"
// APIError 表示API返回的错误
type APIError struct {
Code int32 `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
}
func (e *APIError) Error() string {
if e.Reason != "" {
return fmt.Sprintf("code=%d, message=%s, reason=%s", e.Code, e.Message, e.Reason)
}
return fmt.Sprintf("code=%d, message=%s", e.Code, e.Message)
}
```
// File: ymt_v3/types.go
```go
package ymt_v3
// 请求/响应结构体
// OrderRequest 获取券码请求(业务明文)
type OrderRequest struct {
OutBizNo string `json:"out_biz_no"`
ActivityNo string `json:"activity_no"`
Account string `json:"account,omitempty"`
NotifyURL string `json:"notify_url,omitempty"`
}
// OrderResponse 获取券码/券码查询响应(解密后)
type OrderResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Key string `json:"key,omitempty"`
URL string `json:"url,omitempty"`
ValidBeginTime string `json:"valid_begin_time,omitempty"`
ValidEndTime string `json:"valid_end_time,omitempty"`
UsableNum uint32 `json:"usable_num"`
UsageNum uint32 `json:"usage_num"`
Status uint32 `json:"status"`
SettlementPrice float64 `json:"settlement_price,omitempty"`
Account string `json:"account,omitempty"`
}
// QueryRequest 券码查询请求(业务明文)
type QueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// DiscardRequest 券码作废请求(业务明文)
type DiscardRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// DiscardResponse 券码作废响应(解密后)
type DiscardResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status uint32 `json:"status"`
}
// BatchOrderRequest 批量发卡请求(业务明文)
type BatchOrderRequest struct {
OutBizNo string `json:"out_biz_no"`
ActivityNo string `json:"activity_no"`
Number int32 `json:"number"`
NotifyURL string `json:"notify_url,omitempty"`
}
// BatchOrderResponse 批量发卡响应(解密后)
type BatchOrderResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status string `json:"status"`
}
// BatchQueryRequest 批量查询请求(业务明文)
type BatchQueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// BatchQueryResponse 批量查询响应(解密后)
type BatchQueryResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status string `json:"status"`
DownloadURL string `json:"download_url,omitempty"`
ZipPassword string `json:"zip_password,omitempty"`
}
// 公共请求体
type EncryptedRequest struct {
Ciphertext string `json:"ciphertext"`
}
// 公共响应体
type EncryptedResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
Data *EncryptedData `json:"data,omitempty"`
}
type EncryptedData struct {
Ciphertext string `json:"ciphertext"`
}
// 回调通知结构
type CallbackRequest struct {
Appid string `json:"appid"`
Timestamp string `json:"timestamp"`
Sign string `json:"sign"`
Data string `json:"data"`
}
```
// File: ymt_v3/crypto.go
```go
package ymt_v3
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"reflect"
"sort"
"strings"
"time"
)
// ============================================================
// 时间戳工具
// ============================================================
// GenerateTimestamp 生成格式为 yyyy-MM-dd HH:mm:ss 的时间戳
func GenerateTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05")
}
// ============================================================
// PKCS7 填充/去填充
// ============================================================
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) ([]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
}
// ============================================================
// AES-ECB 加密/解密
// ============================================================
// AESECBEncrypt AES-ECB模式加密返回base64编码
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模式解密输入base64编码
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, fmt.Errorf("base64解码失败: %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模式加密返回base64编码IV前置
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
if len(key) != 16 {
return "", fmt.Errorf("SM4密钥长度必须为16字节")
}
block, err := newSM4Cipher(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 := newCBCEncrypter(block, iv)
ciphertext := make([]byte, len(padded))
mode.CryptBlocks(ciphertext, padded)
result := append(iv, ciphertext...)
return base64.StdEncoding.EncodeToString(result), nil
}
// SM4CBCDecrypt SM4-CBC模式解密输入base64编码
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("base64解码失败: %v", err)
}
block, err := newSM4Cipher(key)
if err != nil {
return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
}
blockSize := block.BlockSize()
if len(data) < blockSize {
return nil, fmt.Errorf("数据长度不足")
}
iv := data[:blockSize]
ciphertext := data[blockSize:]
mode := 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
}
// ============================================================
// SM4 纯Go实现不依赖外部包
// ============================================================
// sm4Cipher SM4密码结构
type sm4Cipher struct {
sk [32]uint32
}
// newSM4Cipher 创建SM4密码
func newSM4Cipher(key []byte) (*sm4Cipher, error) {
if len(key) != 16 {
return nil, fmt.Errorf("SM4密钥长度必须为16字节")
}
c := &sm4Cipher{}
c.sm4KeyInit(key)
return c, nil
}
func (c *sm4Cipher) BlockSize() int { return 16 }
func (c *sm4Cipher) Encrypt(dst, src []byte) {
c.sm4OneRound(dst, src, c.sk)
}
func (c *sm4Cipher) Decrypt(dst, src []byte) {
var rk [32]uint32
for i := 0; i < 32; i++ {
rk[i] = c.sk[31-i]
}
c.sm4OneRound(dst, src, rk)
}
// sm4CBCEncrypter CBC加密器
type sm4CBCEncrypter struct {
b *sm4Cipher
iv []byte
}
func newCBCEncrypter(b *sm4Cipher, iv []byte) *sm4CBCEncrypter {
return &sm4CBCEncrypter{b: b, iv: append([]byte{}, iv...)}
}
func (c *sm4CBCEncrypter) CryptBlocks(dst, src []byte) {
blockSize := c.b.BlockSize()
iv := make([]byte, blockSize)
copy(iv, c.iv)
for i := 0; i < len(src); i += blockSize {
for j := 0; j < blockSize; j++ {
dst[i+j] = src[i+j] ^ iv[j]
}
c.b.Encrypt(dst[i:i+blockSize], dst[i:i+blockSize])
copy(iv, dst[i:i+blockSize])
}
}
// sm4CBCDecrypter CBC解密器
type sm4CBCDecrypter struct {
b *sm4Cipher
iv []byte
}
func newCBCDecrypter(b *sm4Cipher, iv []byte) *sm4CBCDecrypter {
return &sm4CBCDecrypter{b: b, iv: append([]byte{}, iv...)}
}
func (c *sm4CBCDecrypter) CryptBlocks(dst, src []byte) {
blockSize := c.b.BlockSize()
iv := make([]byte, blockSize)
copy(iv, c.iv)
for i := 0; i < len(src); i += blockSize {
c.b.Decrypt(dst[i:i+blockSize], src[i:i+blockSize])
for j := 0; j < blockSize; j++ {
dst[i+j] ^= iv[j]
}
copy(iv, src[i:i+blockSize])
}
}
// SM4 S盒
var sm4Sbox = [256]byte{
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
}
// SM4 FK常量
var sm4FK = [4]uint32{0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc}
// SM4 CK常量
var sm4CK = [32]uint32{
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
}
func (c *sm4Cipher) sm4KeyInit(key []byte) {
var mk [4]uint32
for i := 0; i < 4; i++ {
mk[i] = uint32(key[4*i])<<24 | uint32(key[4*i+1])<<16 | uint32(key[4*i+2])<<8 | uint32(key[4*i+3])
}
var k [36]uint32
for i := 0; i < 4; i++ {
k[i] = mk[i] ^ sm4FK[i]
}
for i := 0; i < 32; i++ {
k[i+4] = k[i] ^ sm4L1(k[i+1]^k[i+2]^k[i+3]^sm4CK[i])
c.sk[i] = k[i+4]
}
}
func (c *sm4Cipher) sm4OneRound(dst, src []byte, sk [32]uint32) {
var x [36]uint32
for i := 0; i < 4; i++ {
x[i] = uint32(src[4*i])<<24 | uint32(src[4*i+1])<<16 | uint32(src[4*i+2])<<8 | uint32(src[4*i+3])
}
for i := 0; i < 32; i++ {
x[i+4] = x[i] ^ sm4L2(x[i+1]^x[i+2]^x[i+3]^sk[i])
}
for i := 0; i < 4; i++ {
dst[4*i] = byte(x[35-i] >> 24)
dst[4*i+1] = byte(x[35-i] >> 16)
dst[4*i+2] = byte(x[35-i] >> 8)
dst[4*i+3] = byte(x[35-i])
}
}
func sm4L1(b uint32) uint32 {
return b ^ sm4Rotl(b, 2) ^ sm4Rotl(b, 10) ^ sm4Rotl(b, 18) ^ sm4Rotl(b, 24)
}
func sm4L2(b uint32) uint32 {
return b ^ sm4Rotl(b, 13) ^ sm4Rotl(b, 23)
}
func sm4Rotl(x uint32, n uint32) uint32 {
return (x << n) | (x >> (32 - n))
}
func sm4Tau(a uint32) uint32 {
var b [4]byte
b[0] = sm4Sbox[byte(a>>24)]
b[1] = sm4Sbox[byte(a>>16)]
b[2] = sm4Sbox[byte(a>>8)]
b[3] = sm4Sbox[byte(a)]
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
}
// ============================================================
// RSA 签名
// ============================================================
// SignWithRSA 使用RSA私钥对字符串进行签名返回base64编码
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("解析PEM私钥失败")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("解析私钥失败: %v", err)
}
}
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("不是RSA私钥")
}
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 "", fmt.Errorf("签名失败: %v", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// VerifyWithRSA 使用RSA公钥验证签名
func VerifyWithRSA(signStr string, sign string, publicKeyPEM string) error {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return fmt.Errorf("解析PEM公钥失败")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
publicKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("解析公钥失败: %v", err)
}
}
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("不是RSA公钥")
}
signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return fmt.Errorf("base64解码签名失败: %v", err)
}
h := sha256.New()
h.Write([]byte(signStr))
hashed := h.Sum(nil)
return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, signBytes)
}
// ============================================================
// 业务参数处理
// ============================================================
// EncryptBizParams 加密业务参数
// 将业务参数去掉零值按字母排序转JSON然后用指定算法加密
func EncryptBizParams(params interface{}, key []byte, encryptType string) (string, error) {
// 将结构体转为map去掉零值
m, err := structToMap(params)
if err != nil {
return "", fmt.Errorf("转换参数失败: %v", err)
}
// 按key排序
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
// 构建有序map
orderedMap := make(map[string]interface{})
for _, k := range keys {
orderedMap[k] = m[k]
}
// 转JSON
plaintext, err := json.Marshal(orderedMap)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
// 加密
switch encryptType {
case "aes":
return AESECBEncrypt(plaintext, key)
case "sm4":
return SM4CBCEncrypt(plaintext, key)
default:
return "", fmt.Errorf("不支持的加密类型: %s", encryptType)
}
}
// DecryptBizParams 解密业务参数
func DecryptBizParams(ciphertext string, key []byte, encryptType string) ([]byte, error) {
switch encryptType {
case "aes":
return AESECBDecrypt(ciphertext, key)
case "sm4":
return SM4CBCDecrypt(ciphertext, key)
default:
return nil, fmt.Errorf("不支持的加密类型: %s", encryptType)
}
}
// structToMap 将结构体转为map去掉零值
func structToMap(obj interface{}) (map[string]interface{}, error) {
result := make(map[string]interface{})
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("不是结构体")
}
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
// 获取json tag
jsonTag := field.Tag.Get("json")
if jsonTag == "" || jsonTag == "-" {
continue
}
name := strings.Split(jsonTag, ",")[0]
// 检查omitempty
opts := strings.Split(jsonTag, ",")
omitempty := false
for _, opt := range opts[1:] {
if opt == "omitempty" {
omitempty = true
break
}
}
// 获取实际值
var val interface{}
switch value.Kind() {
case reflect.String:
val = value.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val = value.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val = value.Uint()
case reflect.Float32, reflect.Float64:
val = value.Float()
case reflect.Bool:
val = value.Bool()
case reflect.Slice, reflect.Map:
if value.IsNil() {
if omitempty {
continue
}
val = value.Interface()
} else {
val = value.Interface()
}
case reflect.Ptr, reflect.Interface:
if value.IsNil() {
if omitempty {
continue
}
val = nil
} else {
val = value.Elem().Interface()
}
default:
val = value.Interface()
}
// 检查零值
if omitempty && isZeroValue(val) {
continue
}
result[name] = val
}
return result, nil
}
// isZeroValue 判断值是否为零值
func isZeroValue(v interface{}) bool {
if v == nil {
return true
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.String:
return rv.String() == ""
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return rv.Uint() == 0
case reflect.Float32, reflect.Float64:
return rv.Float() == 0
case reflect.Bool:
return !rv.Bool()
default:
return false
}
}
```
// File: ymt_v3/client.go
```go
package ymt_v3
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// Config 客户端配置
type Config struct {
AppID string // 应用ID
PrivateKey string // 应用私钥PEM格式用于请求签名
PublicKey string // 平台公钥PEM格式用于响应验签
Key string // 业务参数加密密钥
EncryptType string // 加密类型aes 或 sm4
BaseURL string // 接口地址
HTTPClient *http.Client
}
// Client SDK客户端
type Client struct {
config *Config
client *http.Client
}
// NewClient 创建新的SDK客户端
func NewClient(config *Config) *Client {
if config.HTTPClient == nil {
config.HTTPClient = http.DefaultClient
}
return &Client{
config: config,
client: config.HTTPClient,
}
}
// doRequest 发送请求并处理响应
func (c *Client) doRequest(ctx context.Context, path string, bizParams interface{}, result interface{}) error {
// 1. 加密业务参数
ciphertext, err := EncryptBizParams(bizParams, []byte(c.config.Key), c.config.EncryptType)
if err != nil {
return fmt.Errorf("加密业务参数失败: %v", err)
}
// 2. 生成时间戳
timestamp := GenerateTimestamp()
// 3. 拼接签名字符串app_id + timestamp + ciphertext
signStr := c.config.AppID + timestamp + ciphertext
// 4. 使用应用私钥签名
sign, err := SignWithRSA(signStr, c.config.PrivateKey)
if err != nil {
return fmt.Errorf("签名失败: %v", err)
}
// 5. 构建请求体
reqBody := EncryptedRequest{
Ciphertext: ciphertext,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return fmt.Errorf("序列化请求体失败: %v", err)
}
// 6. 创建HTTP请求
url := strings.TrimRight(c.config.BaseURL, "/") + path
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
if err != nil {
return fmt.Errorf("创建请求失败: %v", err)
}
// 7. 设置Header
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Appid", c.config.AppID)
req.Header.Set("Timestamp", timestamp)
req.Header.Set("Sign", sign)
// 8. 发送请求
resp, err := c.client.Do(req)
if err != nil {
return fmt.Errorf("发送请求失败: %v", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("读取响应失败: %v", err)
}
// 9. 解析响应
var encryptedResp EncryptedResponse
if err := json.Unmarshal(respBody, &encryptedResp); err != nil {
return fmt.Errorf("解析响应失败: %v", err)
}
// 10. 检查错误码
if encryptedResp.Code != 200 {
return &APIError{
Code: encryptedResp.Code,
Message: encryptedResp.Message,
Reason: encryptedResp.Reason,
}
}
// 11. 解密响应数据
if encryptedResp.Data == nil || encryptedResp.Data.Ciphertext == "" {
return fmt.Errorf("响应数据为空")
}
plaintext, err := DecryptBizParams(encryptedResp.Data.Ciphertext, []byte(c.config.Key), c.config.EncryptType)
if err != nil {
return fmt.Errorf("解密响应数据失败: %v", err)
}
// 12. 解析解密后的数据到结果结构体
if err := json.Unmarshal(plaintext, result); err != nil {
return fmt.Errorf("解析解密数据失败: %v", err)
}
return nil
}
// ============================================================
// API 方法
// ============================================================
// Order 获取券码
func (c *Client) Order(ctx context.Context, req *OrderRequest) (*OrderResponse, error) {
result := &OrderResponse{}
if err := c.doRequest(ctx, "/openapi/v1/key/order", req, result); err != nil {
return nil, err
}
return result, nil
}
// Query 券码查询
func (c *Client) Query(ctx context.Context, req *QueryRequest) (*OrderResponse, error) {
result := &OrderResponse{}
if err := c.doRequest(ctx, "/openapi/v1/key/query", req, result); err != nil {
return nil, err
}
return result, nil
}
// Discard 券码作废
func (c *Client) Discard(ctx context.Context, req *DiscardRequest) (*DiscardResponse, error) {
result := &DiscardResponse{}
if err := c.doRequest(ctx, "/openapi/v1/key/discard", req, result); err != nil {
return nil, err
}
return result, nil
}
// BatchOrder 批量发卡
func (c *Client) BatchOrder(ctx context.Context, req *BatchOrderRequest) (*BatchOrderResponse, error) {
result := &BatchOrderResponse{}
if err := c.doRequest(ctx, "/openapi/v1/key/batch_order", req, result); err != nil {
return nil, err
}
return result, nil
}
// BatchQuery 批量查询
func (c *Client) BatchQuery(ctx context.Context, req *BatchQueryRequest) (*BatchQueryResponse, error) {
result := &BatchQueryResponse{}
if err := c.doRequest(ctx, "/openapi/v1/key/batch_query", req, result); err != nil {
return nil, err
}
return result, nil
}
// ============================================================
// 回调验签
// ============================================================
// VerifyCallback 验证回调通知的签名
// 参数:
// - appID: 应用ID
// - timestamp: 回调请求中的时间戳
// - sign: 回调请求中的签名
// - data: 回调请求中的业务数据明文JSON字符串
// - publicKeyPEM: 平台公钥PEM格式
// - key: 业务参数加密密钥
// - encryptType: 加密类型aes 或 sm4
func VerifyCallback(appID, timestamp, sign, data, publicKeyPEM string, key []byte, encryptType string) error {
// 1. 使用key将data加密得到ciphertext
ciphertext, err := EncryptBizParamsFromJSON(data, key, encryptType)
if err != nil {
return fmt.Errorf("加密回调数据失败: %v", err)
}
// 2. 拼接签名字符串app_id + timestamp + ciphertext
signStr := appID + timestamp + ciphertext
// 3. 使用平台公钥验签
return VerifyWithRSA(signStr, sign, publicKeyPEM)
}
// EncryptBizParamsFromJSON 将JSON字符串按规则加密
func EncryptBizParamsFromJSON(jsonStr string, key []byte, encryptType string) (string, error) {
// 解析JSON
var data map[string]interface{}
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
return "", fmt.Errorf("解析JSON失败: %v", err)
}
// 去掉零值
cleaned := make(map[string]interface{})
for k, v := range data {
if !isZeroValue(v) {
cleaned[k] = v
}
}
// 按key排序
keys := make([]string, 0, len(cleaned))
for k := range cleaned {
keys = append(keys, k)
}
sort.Strings(keys)
// 构建有序map
orderedMap := make(map[string]interface{})
for _, k := range keys {
orderedMap[k] = cleaned[k]
}
// 转JSON
plaintext, err := json.Marshal(orderedMap)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
// 加密
switch encryptType {
case "aes":
return AESECBEncrypt(plaintext, key)
case "sm4":
return SM4CBCEncrypt(plaintext, key)
default:
return "", fmt.Errorf("不支持的加密类型: %s", encryptType)
}
}
```
// File: ymt_v3/example_test.go
```go
package ymt_v3
import (
"context"
"fmt"
"testing"
)
// 测试参数(请替换为实际值)
const (
testAppID = "xxx"
testPrivateKey = `-----BEGIN PRIVATE KEY-----
xxx
-----END PRIVATE KEY-----`
testPublicKey = `-----BEGIN PUBLIC KEY-----
xxx
-----END PUBLIC KEY-----`
testKey = "xxxx"
testEncryptType = "aes"
testBaseURL = "https://gateway.dev.cdlsxd.cn"
testActivityNo = "xxxx"
)
func TestClient_Order(t *testing.T) {
client := NewClient(&Config{
AppID: testAppID,
PrivateKey: testPrivateKey,
PublicKey: testPublicKey,
Key: testKey,
EncryptType: testEncryptType,
BaseURL: testBaseURL,
})
resp, err := client.Order(context.Background(), &OrderRequest{
OutBizNo: "order_001",
ActivityNo: testActivityNo,
Account: "18666666666",
NotifyURL: "https://notify.example.com/openapi",
})
if err != nil {
t.Fatalf("Order failed: %v", err)
}
fmt.Printf("Order response: %+v\n", resp)
}
func TestClient_Query(t *testing.T) {
client := NewClient(&Config{
AppID: testAppID,
PrivateKey: testPrivateKey,
PublicKey: testPublicKey,
Key: testKey,
EncryptType: testEncryptType,
BaseURL: testBaseURL,
})
resp, err := client.Query(context.Background(), &QueryRequest{
OutBizNo: "order_001",
})
if err != nil {
t.Fatalf("Query failed: %v", err)
}
fmt.Printf("Query response: %+v\n", resp)
}
func TestClient_Discard(t *testing.T) {
client := NewClient(&Config{
AppID: testAppID,
PrivateKey: testPrivateKey,
PublicKey: testPublicKey,
Key: testKey,
EncryptType: testEncryptType,
BaseURL: testBaseURL,
})
resp, err := client.Discard(context.Background(), &DiscardRequest{
TradeNo: "7251449503000383488",
})
if err != nil {
t.Fatalf("Discard failed: %v", err)
}
fmt.Printf("Discard response: %+v\n", resp)
}
func TestClient_BatchOrder(t *testing.T) {
client := NewClient(&Config{
AppID: testAppID,
PrivateKey: testPrivateKey,
PublicKey: testPublicKey,
Key: testKey,
EncryptType: testEncryptType,
BaseURL: testBaseURL,
})
resp, err := client.BatchOrder(context.Background(), &BatchOrderRequest{
OutBizNo: "batch_001",
ActivityNo: testActivityNo,
Number: 10,
NotifyURL: "https://notify.example.com/openapi",
})
if err != nil {
t.Fatalf("BatchOrder failed: %v", err)
}
fmt.Printf("BatchOrder response: %+v\n", resp)
}
func TestClient_BatchQuery(t *testing.T) {
client := NewClient(&Config{
AppID: testAppID,
PrivateKey: testPrivateKey,
PublicKey: testPublicKey,
Key: testKey,
EncryptType: testEncryptType,
BaseURL: testBaseURL,
})
resp, err := client.BatchQuery(context.Background(), &BatchQueryRequest{
TradeNo: "7251449503000383499",
})
if err != nil {
t.Fatalf("BatchQuery failed: %v", err)
}
fmt.Printf("BatchQuery response: %+v\n", resp)
}
func TestVerifyCallback(t *testing.T) {
// 模拟回调参数
appID := "123456"
timestamp := "2026-06-22 15:30:00"
sign := "base64_encoded_signature"
data := `{"out_biz_no":"batch_001","trade_no":"7251449503000383499","status":"success","download_url":"https://oss.example.com/openapi_7251449503000383499.zip","zip_password":"123456"}`
err := VerifyCallback(appID, timestamp, sign, data, testPublicKey, []byte(testKey), testEncryptType)
if err != nil {
t.Logf("VerifyCallback result: %v (expected if sign is invalid)", err)
} else {
t.Log("VerifyCallback success")
}
}
```
现在让我重新整理一下,确保所有文件都正确输出。
实际上,让我重新检查一下文档中的签名规则:
> 1. 拼接签名字符串分配给开发者的应用ID + 发送请求的时间 + 加密业务参数
> 2. 使用应用私钥将拼接待签名字符串生成签名字符串
所以签名串是app_id + timestamp + ciphertext直接拼接没有分隔符
回调验签规则:
> 1. 获取header头里面的签名信息
> 2. 获取body里面的业务参数data
> 3. 将业务参数data去掉"零"值的参数再由小到大按照字母排序转成json字符串得到plaintext
> 4. 使用应用key将plaintext字符串加密[aes/sm4]得到加密得到ciphertext
> 5. 拼接签名字符串分配给开发者的应用ID + 发送请求的时间 + ciphertext
> 6. 使用应用公钥验签
好的,我的实现已经覆盖了这些规则。
现在让我输出所有文件。</think>现在我来生成完整的 SDK 代码,包含所有 6 个文件。
// File: ymt_v3/go.mod
```go
module ymt_v3
go 1.21
```
// File: ymt_v3/errors.go
```go
package ymt_v3
import "fmt"
// APIError 表示API返回的错误信息
type APIError struct {
Code int32 `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
}
func (e *APIError) Error() string {
if e.Reason != "" {
return fmt.Sprintf("code=%d, message=%s, reason=%s", e.Code, e.Message, e.Reason)
}
return fmt.Sprintf("code=%d, message=%s", e.Code, e.Message)
}
```
// File: ymt_v3/types.go
```go
package ymt_v3
// OrderRequest 获取券码请求(业务明文参数)
type OrderRequest struct {
OutBizNo string `json:"out_biz_no"`
ActivityNo string `json:"activity_no"`
Account string `json:"account,omitempty"`
NotifyURL string `json:"notify_url,omitempty"`
}
// OrderResponse 获取券码/券码查询响应(解密后)
type OrderResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Key string `json:"key,omitempty"`
URL string `json:"url,omitempty"`
ValidBeginTime string `json:"valid_begin_time,omitempty"`
ValidEndTime string `json:"valid_end_time,omitempty"`
UsableNum uint32 `json:"usable_num"`
UsageNum uint32 `json:"usage_num"`
Status uint32 `json:"status"`
SettlementPrice float64 `json:"settlement_price,omitempty"`
Account string `json:"account,omitempty"`
}
// QueryRequest 券码查询请求(业务明文参数)
type QueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// DiscardRequest 券码作废请求(业务明文参数)
type DiscardRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// DiscardResponse 券码作废响应(解密后)
type DiscardResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status uint32 `json:"status"`
}
// BatchOrderRequest 批量发卡请求(业务明文参数)
type BatchOrderRequest struct {
OutBizNo string `json:"out_biz_no"`
ActivityNo string `json:"activity_no"`
Number int32 `json:"number"`
NotifyURL string `json:"notify_url,omitempty"`
}
// BatchOrderResponse 批量发卡响应(解密后)
type BatchOrderResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status string `json:"status"`
}
// BatchQueryRequest 批量查询请求(业务明文参数)
type BatchQueryRequest struct {
OutBizNo string `json:"out_biz_no,omitempty"`
TradeNo string `json:"trade_no,omitempty"`
}
// BatchQueryResponse 批量查询响应(解密后)
type BatchQueryResponse struct {
OutBizNo string `json:"out_biz_no"`
TradeNo string `json:"trade_no"`
Status string `json:"status"`
DownloadURL string `json:"download_url,omitempty"`
ZipPassword string `json:"zip_password,omitempty"`
}
// EncryptedRequest 公共加密请求体
type EncryptedRequest struct {
Ciphertext string `json:"ciphertext"`
}
// EncryptedResponse 公共加密响应体
type EncryptedResponse struct {
Code int32 `json:"code"`
Message string `json:"message"`
Reason string `json:"reason,omitempty"`
Data *EncryptedData `json:"data,omitempty"`
}
// EncryptedData 加密数据包装
type EncryptedData struct {
Ciphertext string `json:"ciphertext"`
}
```
// File: ymt_v3/crypto.go
```go
package ymt_v3
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"reflect"
"sort"
"strings"
"time"
)
// ============================================================
// 时间戳工具
// ============================================================
// GenerateTimestamp 生成格式为 yyyy-MM-dd HH:mm:ss 的时间戳
func GenerateTimestamp() string {
return time.Now().Format("2006-01-02 15:04:05")
}
// ============================================================
// PKCS7 填充/去填充
// ============================================================
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) ([]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
}
// ============================================================
// AES-ECB 加密/解密
// ============================================================
// AESECBEncrypt AES-ECB模式加密返回base64编码
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模式解密输入base64编码
func AESECBDecrypt(encryptedData string, key []byte) ([]byte, error) {
ciphertext, err := base64.StdEncoding.DecodeString(encryptedData)
if err != nil {
return nil, fmt.Errorf("base64解码失败: %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 加密/解密纯Go实现无外部依赖
// ============================================================
// SM4CBCEncrypt SM4-CBC模式加密返回base64编码IV前置
func SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {
if len(key) != 16 {
return "", fmt.Errorf("SM4密钥长度必须为16字节")
}
block, err := newSM4Cipher(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 := newCBCEncrypter(block, iv)
ciphertext := make([]byte, len(padded))
mode.CryptBlocks(ciphertext, padded)
result := append(iv, ciphertext...)
return base64.StdEncoding.EncodeToString(result), nil
}
// SM4CBCDecrypt SM4-CBC模式解密输入base64编码
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("base64解码失败: %v", err)
}
block, err := newSM4Cipher(key)
if err != nil {
return nil, fmt.Errorf("创建SM4 cipher失败: %v", err)
}
blockSize := block.BlockSize()
if len(data) < blockSize {
return nil, fmt.Errorf("数据长度不足")
}
iv := data[:blockSize]
ciphertext := data[blockSize:]
mode := 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
}
// ============================================================
// SM4 纯Go实现
// ============================================================
type sm4Cipher struct {
sk [32]uint32
}
func newSM4Cipher(key []byte) (*sm4Cipher, error) {
if len(key) != 16 {
return nil, fmt.Errorf("SM4密钥长度必须为16字节")
}
c := &sm4Cipher{}
c.sm4KeyInit(key)
return c, nil
}
func (c *sm4Cipher) BlockSize() int { return 16 }
func (c *sm4Cipher) Encrypt(dst, src []byte) {
c.sm4OneRound(dst, src, c.sk)
}
func (c *sm4Cipher) Decrypt(dst, src []byte) {
var rk [32]uint32
for i := 0; i < 32; i++ {
rk[i] = c.sk[31-i]
}
c.sm4OneRound(dst, src, rk)
}
type sm4CBCEncrypter struct {
b *sm4Cipher
iv []byte
}
func newCBCEncrypter(b *sm4Cipher, iv []byte) *sm4CBCEncrypter {
return &sm4CBCEncrypter{b: b, iv: append([]byte{}, iv...)}
}
func (c *sm4CBCEncrypter) CryptBlocks(dst, src []byte) {
blockSize := c.b.BlockSize()
iv := make([]byte, blockSize)
copy(iv, c.iv)
for i := 0; i < len(src); i += blockSize {
for j := 0; j < blockSize; j++ {
dst[i+j] = src[i+j] ^ iv[j]
}
c.b.Encrypt(dst[i:i+blockSize], dst[i:i+blockSize])
copy(iv, dst[i:i+blockSize])
}
}
type sm4CBCDecrypter struct {
b *sm4Cipher
iv []byte
}
func newCBCDecrypter(b *sm4Cipher, iv []byte) *sm4CBCDecrypter {
return &sm4CBCDecrypter{b: b, iv: append([]byte{}, iv...)}
}
func (c *sm4CBCDecrypter) CryptBlocks(dst, src []byte) {
blockSize := c.b.BlockSize()
iv := make([]byte, blockSize)
copy(iv, c.iv)
for i := 0; i < len(src); i += blockSize {
c.b.Decrypt(dst[i:i+blockSize], src[i:i+blockSize])
for j := 0; j < blockSize; j++ {
dst[i+j] ^= iv[j]
}
copy(iv, src[i:i+blockSize])
}
}
var sm4Sbox = [256]byte{
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48,
}
var sm4FK = [4]uint32{0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc}
var sm4CK = [32]uint32{
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279,
}
func (c *sm4Cipher) sm4KeyInit(key []byte) {
var mk [4]uint32
for i := 0; i < 4; i++ {
mk[i] = uint32(key[4*i])<<24 | uint32(key[4*i+1])<<16 | uint32(key[4*i+2])<<8 | uint32(key[4*i+3])
}
var k [36]uint32
for i := 0; i < 4; i++ {
k[i] = mk[i] ^ sm4FK[i]
}
for i := 0; i < 32; i++ {
k[i+4] = k[i] ^ sm4L1(k[i+1]^k[i+2]^k[i+3]^sm4CK[i])
c.sk[i] = k[i+4]
}
}
func (c *sm4Cipher) sm4OneRound(dst, src []byte, sk [32]uint32) {
var x [36]uint32
for i := 0; i < 4; i++ {
x[i] = uint32(src[4*i])<<24 | uint32(src[4*i+1])<<16 | uint32(src[4*i+2])<<8 | uint32(src[4*i+3])
}
for i := 0; i < 32; i++ {
x[i+4] = x[i] ^ sm4L2(x[i+1]^x[i+2]^x[i+3]^sk[i])
}
for i := 0; i < 4; i++ {
dst[4*i] = byte(x[35-i] >> 24)
dst[4*i+1] = byte(x[35-i] >> 16)
dst[4*i+2] = byte(x[35-i] >> 8)
dst[4*i+3] = byte(x[35-i])
}
}
func sm4L1(b uint32) uint32 {
return b ^ sm4Rotl(b, 2) ^ sm4Rotl(b, 10) ^ sm4Rotl(b, 18) ^ sm4Rotl(b, 24)
}
func sm4L2(b uint32) uint32 {
return b ^ sm4Rotl(b, 13) ^ sm4Rotl(b, 23)
}
func sm4Rotl(x uint32, n uint32) uint32 {
return (x << n) | (x >> (32 - n))
}
// ============================================================
// RSA 签名与验签
// ============================================================
// SignWithRSA 使用RSA私钥对字符串进行签名返回base64编码
func SignWithRSA(signStr string, privateKeyPEM string) (string, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return "", fmt.Errorf("解析PEM私钥失败")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return "", fmt.Errorf("解析私钥失败: %v", err)
}
}
rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
if !ok {
return "", fmt.Errorf("不是RSA私钥")
}
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 "", fmt.Errorf("签名失败: %v", err)
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// VerifyWithRSA 使用RSA公钥验证签名
func VerifyWithRSA(signStr string, sign string, publicKeyPEM string) error {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return fmt.Errorf("解析PEM公钥失败")
}
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
publicKey, err = x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return fmt.Errorf("解析公钥失败: %v", err)
}
}
rsaPublicKey, ok := publicKey.(*rsa.PublicKey)
if !ok {
return fmt.Errorf("不是RSA公钥")
}
signBytes, err := base64.StdEncoding.DecodeString(sign)
if err != nil {
return fmt.Errorf("base64解码签名失败: %v", err)
}
h := sha256.New()
h.Write([]byte(signStr))
hashed := h.Sum(nil)
return rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed, signBytes)
}
// ============================================================
// 业务参数加密/解密
// ============================================================
// EncryptBizParams 加密业务参数
// 将业务参数去掉零值按字母排序转JSON然后用指定算法加密
func EncryptBizParams(params interface{}, key []byte,
## 加密实现
以下是从加密工具获取的完整实现:
### 加密实现 1
{
"success": true,
"data": "\n### 时间戳和随机数生成规范\n\n**代码模板**\n\n```go\npackage crypto\n\nimport (\n \"crypto/rand\"\n \"fmt\"\n \"math/big\"\n \"time\"\n)\n\n// GenerateTimestamp 生成秒级时间戳\nfunc GenerateTimestamp() string {\n return fmt.Sprintf(\"%d\", time.Now().Unix())\n}\n\n// GenerateTimestampMillis 生成毫秒级时间戳\nfunc GenerateTimestampMillis() string {\n return fmt.Sprintf(\"%d\", time.Now().UnixMilli())\n}\n\n// GenerateNonce 生成指定长度的随机字符串(加密安全)\nfunc GenerateNonce(length int) (string, error) {\n const charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n b := make([]byte, length)\n for i := range b {\n num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))\n if err != nil {\n return \"\", err\n }\n b[i] = charset[num.Int64()]\n }\n return string(b), nil\n}\n```\n\n**注意事项**\n- 确认文档要求的是秒级还是毫秒级时间戳\n- 确认nonce的长度要求通常16-32位\n- 生产环境建议使用加密安全的随机数生成器\n",
"tool": "nonce_timestamp"
}
### 加密实现 2
{
"success": true,
"data": "\n### 参数拼接规范\n\n**常见拼接方式**\n\n1. **字典序排序**按key字典序排序后拼接 `key=value\u0026key2=value2`\n2. **固定顺序**:按文档指定顺序拼接\n3. **JSON字符串**:整个请求体作为签名串\n\n**代码模板**\n\n```go\npackage crypto\n\nimport (\n \"fmt\"\n \"sort\"\n \"strings\"\n)\n\n// BuildSignString 方式1字典序排序拼接\nfunc BuildSignString(params map[string]string) string {\n keys := make([]string, 0, len(params))\n for k, v := range params {\n if v != \"\" \u0026\u0026 k != \"sign\" \u0026\u0026 k != \"signature\" {\n keys = append(keys, k)\n }\n }\n sort.Strings(keys)\n \n var parts []string\n for _, k := range keys {\n parts = append(parts, fmt.Sprintf(\"%s=%s\", k, params[k]))\n }\n return strings.Join(parts, \"\u0026\")\n}\n\n// BuildSignStringOrdered 方式2固定顺序拼接\nfunc BuildSignStringOrdered(params map[string]string, orderedKeys []string) string {\n var parts []string\n for _, k := range orderedKeys {\n if v, ok := params[k]; ok \u0026\u0026 v != \"\" {\n parts = append(parts, fmt.Sprintf(\"%s=%s\", k, v))\n }\n }\n return strings.Join(parts, \"\u0026\")\n}\n```\n\n**注意事项**\n- 确认文档指定的排序规则(字典序/固定顺序)\n- 确认空值是否要包含(通常排除空值)\n- 确认是否需要URL编码\n- 注意排除签名字段本身\n",
"tool": "param_concat"
}
### 加密实现 3
{
"success": true,
"data": "\n### AES-ECB 加密完整实现指南\n\n**配置参数**\n- 密钥长度: 16 字节\n- 编码方式: base64\n\n**完整代码模板**\n\n```go\npackage crypto\n\nimport (\n \"crypto/aes\"\n \"encoding/base64\"\n \"encoding/hex\"\n \"fmt\"\n \"bytes\"\n)\n\n// AESECBEncrypt AES-ECB模式加密\nfunc AESECBEncrypt(plaintext []byte, key []byte, encoding string) (string, error) {\n block, err := aes.NewCipher(key)\n if err != nil {\n return \"\", fmt.Errorf(\"创建AES cipher失败: %v\", err)\n }\n\n padded := pkcs7Padding(plaintext, aes.BlockSize)\n\n ciphertext := make([]byte, len(padded))\n for i := 0; i \u003c len(padded); i += aes.BlockSize {\n block.Encrypt(ciphertext[i:i+aes.BlockSize], padded[i:i+aes.BlockSize])\n }\n\n if encoding == \"hex\" {\n return hex.EncodeToString(ciphertext), nil\n }\n return base64.StdEncoding.EncodeToString(ciphertext), nil\n}\n\n// AESECBDecrypt AES-ECB模式解密\nfunc AESECBDecrypt(encryptedData string, key []byte, encoding string) ([]byte, error) {\n var ciphertext []byte\n var err error\n \n if encoding == \"hex\" {\n ciphertext, err = hex.DecodeString(encryptedData)\n } else {\n ciphertext, err = base64.StdEncoding.DecodeString(encryptedData)\n }\n if err != nil {\n return nil, fmt.Errorf(\"解码失败: %v\", err)\n }\n\n block, err := aes.NewCipher(key)\n if err != nil {\n return nil, fmt.Errorf(\"创建AES cipher失败: %v\", err)\n }\n\n if len(ciphertext)%aes.BlockSize != 0 {\n return nil, fmt.Errorf(\"密文长度不是块大小的整数倍\")\n }\n\n plaintext := make([]byte, len(ciphertext))\n for i := 0; i \u003c len(ciphertext); i += aes.BlockSize {\n block.Decrypt(plaintext[i:i+aes.BlockSize], ciphertext[i:i+aes.BlockSize])\n }\n\n plaintext, err = pkcs7UnPadding(plaintext)\n if err != nil {\n return nil, fmt.Errorf(\"去除填充失败: %v\", err)\n }\n\n return plaintext, nil\n}\n```\n\n%!(EXTRA string=16)",
"tool": "aes_ecb_encrypt"
}
### 加密实现 4
{
"success": true,
"data": "\n### SM4-CBC 国密对称加密完整实现指南\n\n**前置要求**\n```bash\ngo get github.com/tjfoc/gmsm\n```\n\n**配置参数**\n- 编码方式: base64\n\n**完整代码模板**\n\n```go\npackage crypto\n\nimport (\n \"crypto/cipher\"\n \"crypto/rand\"\n \"encoding/base64\"\n \"encoding/hex\"\n \"fmt\"\n \"io\"\n \"bytes\"\n \"github.com/tjfoc/gmsm/sm4\"\n)\n\n// SM4CBCEncrypt SM4-CBC模式加密\nfunc SM4CBCEncrypt(plaintext []byte, key []byte) (string, error) {\n if len(key) != 16 {\n return \"\", fmt.Errorf(\"SM4密钥长度必须为16字节\")\n }\n\n block, err := sm4.NewCipher(key)\n if err != nil {\n return \"\", fmt.Errorf(\"创建SM4 cipher失败: %v\", err)\n }\n\n padded := pkcs7Padding(plaintext, block.BlockSize())\n\n iv := make([]byte, block.BlockSize())\n if _, err := io.ReadFull(rand.Reader, iv); err != nil {\n return \"\", fmt.Errorf(\"生成IV失败: %v\", err)\n }\n\n mode := cipher.NewCBCEncrypter(block, iv)\n ciphertext := make([]byte, len(padded))\n mode.CryptBlocks(ciphertext, padded)\n\n result := append(iv, ciphertext...)\n if \"base64\" == \"hex\" {\n return hex.EncodeToString(result), nil\n }\n return base64.StdEncoding.EncodeToString(result), nil\n}\n\n// SM4CBCDecrypt SM4-CBC模式解密\nfunc SM4CBCDecrypt(encryptedData string, key []byte) ([]byte, error) {\n if len(key) != 16 {\n return nil, fmt.Errorf(\"SM4密钥长度必须为16字节\")\n }\n\n var data []byte\n var err error\n if \"base64\" == \"hex\" {\n data, err = hex.DecodeString(encryptedData)\n } else {\n data, err = base64.StdEncoding.DecodeString(encryptedData)\n }\n if err != nil {\n return nil, fmt.Errorf(\"解码失败: %v\", err)\n }\n\n block, err := sm4.NewCipher(key)\n if err != nil {\n return nil, fmt.Errorf(\"创建SM4 cipher失败: %v\", err)\n }\n\n blockSize := block.BlockSize()\n if len(data) \u003c blockSize {\n return nil, fmt.Errorf(\"数据长度不足\")\n }\n iv := data[:blockSize]\n ciphertext := data[blockSize:]\n\n mode := cipher.NewCBCDecrypter(block, iv)\n plaintext := make([]byte, len(ciphertext))\n mode.CryptBlocks(plaintext, ciphertext)\n\n plaintext, err = pkcs7UnPadding(plaintext)\n if err != nil {\n return nil, fmt.Errorf(\"去除填充失败: %v\", err)\n }\n\n return plaintext, nil\n}\n```\n\n**注意事项**\n- SM4 密钥固定为 16 字节\n- IV 必须随机生成且每次不同\n",
"tool": "sm4_cbc_encrypt"
}
### 加密实现 5
{
"success": true,
"data": "\n### RSA签名完整实现指南\n\n**适用场景**文档要求使用RSA私钥对请求参数进行签名\n\n**配置参数**\n- 哈希算法: SHA256\n- 编码方式: base64\n\n**完整代码模板**\n```go\npackage crypto\n\nimport (\n \"crypto\"\n \"crypto/rand\"\n \"crypto/rsa\"\n \"crypto/sha256\"\n \"crypto/sha512\"\n \"crypto/x509\"\n \"encoding/base64\"\n \"encoding/hex\"\n \"encoding/pem\"\n \"fmt\"\n \"sort\"\n \"strings\"\n)\n\n// RSAConfig RSA签名配置\ntype RSAConfig struct {\n PrivateKeyPEM string // PEM格式的私钥\n KeyID string // 密钥ID如文档要求携带\n}\n\n// SignWithRSA 使用RSA私钥对请求进行签名\nfunc SignWithRSA(params map[string]string, config *RSAConfig) (string, error) {\n // 1. 拼接参数(按字典序排序)\n keys := make([]string, 0, len(params))\n for k := range params {\n if k != \"sign\" \u0026\u0026 k != \"signature\" {\n keys = append(keys, k)\n }\n }\n sort.Strings(keys)\n \n var sb strings.Builder\n for _, k := range keys {\n if sb.Len() \u003e 0 {\n sb.WriteString(\"\u0026\")\n }\n sb.WriteString(k)\n sb.WriteString(\"=\")\n sb.WriteString(params[k])\n }\n signStr := sb.String()\n \n // 2. 加载私钥\n block, _ := pem.Decode([]byte(config.PrivateKeyPEM))\n if block == nil {\n return \"\", fmt.Errorf(\"failed to decode PEM private key\")\n }\n \n privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n if err != nil {\n // 尝试PKCS1格式\n privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)\n if err != nil {\n return \"\", fmt.Errorf(\"failed to parse private key: %v\", err)\n }\n }\n \n rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)\n if !ok {\n return \"\", fmt.Errorf(\"not a RSA private key\")\n }\n \n // 3. 计算哈希\n var hashed []byte\n var hashFunc crypto.Hash\n \n switch \"SHA256\" {\n case \"SHA384\":\n h := sha512.New384()\n h.Write([]byte(signStr))\n hashed = h.Sum(nil)\n hashFunc = crypto.SHA384\n case \"SHA512\":\n h := sha512.New()\n h.Write([]byte(signStr))\n hashed = h.Sum(nil)\n hashFunc = crypto.SHA512\n default: // SHA256\n h := sha256.New()\n h.Write([]byte(signStr))\n hashed = h.Sum(nil)\n hashFunc = crypto.SHA256\n }\n \n // 4. RSA签名\n signature, err := rsa.SignPKCS1v15(rand.Reader, rsaPrivateKey, hashFunc, hashed)\n if err != nil {\n return \"\", err\n }\n \n // 5. 编码\n if \"base64\" == \"hex\" {\n return hex.EncodeToString(signature), nil\n }\n return base64.StdEncoding.EncodeToString(signature), nil\n}\n```\n\n**注意事项**\n- 确认文档使用的是 PKCS1 还是 PKCS8 格式\n- 确认是否需要添加额外的固定盐值\n- 确认编码是 Base64 还是 Hex\n- 注意排除签名字段本身sign/signature\n",
"tool": "rsa_sign"
}