198 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			198 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package esign
 | ||
| 
 | ||
| import (
 | ||
| 	"crypto/hmac"
 | ||
| 	"crypto/md5"
 | ||
| 	"crypto/sha256"
 | ||
| 	"encoding/base64"
 | ||
| 	"encoding/hex"
 | ||
| 	"encoding/json"
 | ||
| 	"fmt"
 | ||
| 	"sort"
 | ||
| 	"strconv"
 | ||
| 	"strings"
 | ||
| 	"time"
 | ||
| )
 | ||
| 
 | ||
| // appendSignDataString 拼接待签名字符串
 | ||
| func appendSignDataString(httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters string) string {
 | ||
| 	if accept == "" {
 | ||
| 		accept = "*/*"
 | ||
| 	}
 | ||
| 	if contentType == "" {
 | ||
| 		contentType = "application/json; charset=UTF-8"
 | ||
| 	}
 | ||
| 	// 前四项
 | ||
| 	signStr := httpMethod + "\n" + accept + "\n" + contentMD5 + "\n" + contentType + "\n"
 | ||
| 	// 处理 date
 | ||
| 	if date == "" {
 | ||
| 		signStr += "\n"
 | ||
| 	} else {
 | ||
| 		signStr += date + "\n"
 | ||
| 	}
 | ||
| 	// 处理 headers
 | ||
| 	if headers == "" {
 | ||
| 		signStr += pathAndParameters
 | ||
| 	} else {
 | ||
| 		signStr += headers + "\n" + pathAndParameters
 | ||
| 	}
 | ||
| 	return signStr
 | ||
| }
 | ||
| 
 | ||
| // generateSignature 生成e签宝API请求签名
 | ||
| // 使用HMAC-SHA256算法对请求参数进行签名
 | ||
| //
 | ||
| // 参数说明:
 | ||
| //   - appSecret: 应用密钥
 | ||
| //   - httpMethod: HTTP方法(GET、POST等)
 | ||
| //   - accept: Accept头值
 | ||
| //   - contentMD5: 请求体MD5值
 | ||
| //   - contentType: Content-Type头值
 | ||
| //   - date: Date头值
 | ||
| //   - headers: 自定义头部信息
 | ||
| //   - pathAndParameters: 请求路径和参数
 | ||
| //
 | ||
| // 返回: Base64编码的签名字符串
 | ||
| func generateSignature(appSecret, httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters string) string {
 | ||
| 	// 构建待签名字符串,按照e签宝API规范拼接(兼容Python实现细节)
 | ||
| 	signStr := appendSignDataString(httpMethod, accept, contentMD5, contentType, date, headers, pathAndParameters)
 | ||
| 
 | ||
| 	// 使用HMAC-SHA256计算签名
 | ||
| 	h := hmac.New(sha256.New, []byte(appSecret))
 | ||
| 	h.Write([]byte(signStr))
 | ||
| 	digestBytes := h.Sum(nil)
 | ||
| 
 | ||
| 	// 对摘要结果进行Base64编码
 | ||
| 	signature := base64.StdEncoding.EncodeToString(digestBytes)
 | ||
| 
 | ||
| 	return signature
 | ||
| }
 | ||
| 
 | ||
| // generateNonce 生成随机字符串
 | ||
| // 使用当前时间的纳秒数作为随机字符串
 | ||
| //
 | ||
| // 返回: 纳秒时间戳字符串
 | ||
| func generateNonce() string {
 | ||
| 	return strconv.FormatInt(time.Now().UnixNano(), 10)
 | ||
| }
 | ||
| 
 | ||
| // getContentMD5 计算请求体的MD5值
 | ||
| // 对请求体进行MD5哈希计算,然后进行Base64编码
 | ||
| //
 | ||
| // 参数:
 | ||
| //   - body: 请求体字节数组
 | ||
| //
 | ||
| // 返回: Base64编码的MD5值
 | ||
| func getContentMD5(body []byte) string {
 | ||
| 	md5Sum := md5.Sum(body)
 | ||
| 	return base64.StdEncoding.EncodeToString(md5Sum[:])
 | ||
| }
 | ||
| 
 | ||
| // getCurrentTimestamp 获取当前时间戳(毫秒)
 | ||
| //
 | ||
| // 返回: 毫秒级时间戳字符串
 | ||
| func getCurrentTimestamp() string {
 | ||
| 	return strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
 | ||
| }
 | ||
| 
 | ||
| // getCurrentDate 获取当前UTC时间字符串
 | ||
| // 格式: "Mon, 02 Jan 2006 15:04:05 GMT"
 | ||
| //
 | ||
| // 返回: RFC1123格式的UTC时间字符串
 | ||
| func getCurrentDate() string {
 | ||
| 	return time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT")
 | ||
| }
 | ||
| 
 | ||
| // formatDateForTemplate 格式化日期用于模板填写
 | ||
| // 格式: "2006年01月02日"
 | ||
| //
 | ||
| // 返回: 中文格式的日期字符串
 | ||
| func formatDateForTemplate() string {
 | ||
| 	return time.Now().Format("2006年01月02日")
 | ||
| }
 | ||
| 
 | ||
| // generateFileName 生成带时间戳的文件名
 | ||
| //
 | ||
| // 参数:
 | ||
| //   - baseName: 基础文件名
 | ||
| //   - extension: 文件扩展名
 | ||
| //
 | ||
| // 返回: 带时间戳的文件名
 | ||
| func generateFileName(baseName, extension string) string {
 | ||
| 	timestamp := time.Now().Format("20060102_150405")
 | ||
| 	return baseName + "_" + timestamp + "." + extension
 | ||
| }
 | ||
| 
 | ||
| // calculateExpireTime 计算过期时间戳
 | ||
| //
 | ||
| // 参数:
 | ||
| //   - days: 过期天数
 | ||
| //
 | ||
| // 返回: 毫秒级时间戳
 | ||
| func calculateExpireTime(days int) int64 {
 | ||
| 	return time.Now().AddDate(0, 0, days).UnixMilli()
 | ||
| }
 | ||
| 
 | ||
| // verifySignature 验证e签宝回调签名
 | ||
| func VerifySignature(callbackData interface{}, headers map[string]string, queryParams map[string]string, appSecret string) error {
 | ||
| 	// 1. 获取签名相关参数
 | ||
| 	signature, ok := headers["X-Tsign-Open-Signature"]
 | ||
| 	if !ok {
 | ||
| 		return fmt.Errorf("缺少签名头: X-Tsign-Open-Signature")
 | ||
| 	}
 | ||
| 
 | ||
| 	timestamp, ok := headers["X-Tsign-Open-Timestamp"]
 | ||
| 	if !ok {
 | ||
| 		return fmt.Errorf("缺少时间戳头: X-Tsign-Open-Timestamp")
 | ||
| 	}
 | ||
| 
 | ||
| 	// 2. 构建查询参数字符串
 | ||
| 	var queryKeys []string
 | ||
| 	for key := range queryParams {
 | ||
| 		queryKeys = append(queryKeys, key)
 | ||
| 	}
 | ||
| 	sort.Strings(queryKeys) // 按ASCII码升序排序
 | ||
| 
 | ||
| 	var queryValues []string
 | ||
| 	for _, key := range queryKeys {
 | ||
| 		queryValues = append(queryValues, queryParams[key])
 | ||
| 	}
 | ||
| 	queryString := strings.Join(queryValues, "")
 | ||
| 
 | ||
| 	// 3. 获取请求体数据
 | ||
| 	bodyData, err := getRequestBodyString(callbackData)
 | ||
| 	if err != nil {
 | ||
| 		return fmt.Errorf("获取请求体数据失败: %w", err)
 | ||
| 	}
 | ||
| 
 | ||
| 	// 4. 构建验签数据
 | ||
| 	data := timestamp + queryString + bodyData
 | ||
| 
 | ||
| 	// 5. 计算签名
 | ||
| 	expectedSignature := calculateSignature(data, appSecret)
 | ||
| 
 | ||
| 	// 6. 比较签名
 | ||
| 	if strings.ToLower(expectedSignature) != strings.ToLower(signature) {
 | ||
| 		return fmt.Errorf("签名验证失败")
 | ||
| 	}
 | ||
| 
 | ||
| 	return nil
 | ||
| }
 | ||
| 
 | ||
| // calculateSignature 计算HMAC-SHA256签名
 | ||
| func calculateSignature(data, secret string) string {
 | ||
| 	h := hmac.New(sha256.New, []byte(secret))
 | ||
| 	h.Write([]byte(data))
 | ||
| 	return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
 | ||
| }
 | ||
| 
 | ||
| // getRequestBodyString 获取请求体字符串
 | ||
| func getRequestBodyString(callbackData interface{}) (string, error) {
 | ||
| 	// 将map转换为JSON字符串
 | ||
| 	jsonBytes, err := json.Marshal(callbackData)
 | ||
| 	if err != nil {
 | ||
| 		return "", fmt.Errorf("JSON序列化失败: %w", err)
 | ||
| 	}
 | ||
| 	return string(jsonBytes), nil
 | ||
| }
 |