// Package apisign 提供API签名计算与验证功能 package apisign import ( "crypto/hmac" "crypto/md5" "crypto/sha1" "crypto/sha256" "encoding/hex" "fmt" "net/url" "sort" "strconv" "strings" "time" ) // SignatureAlgorithm 签名算法类型 type SignatureAlgorithm int const ( // MD5算法(默认、最快) MD5 SignatureAlgorithm = iota // SHA1算法 SHA1 // SHA256算法 SHA256 // HMACSHA256算法(最安全) HMACSHA256 ) // String 返回算法的字符串表示 func (a SignatureAlgorithm) String() string { switch a { case MD5: return "MD5" case SHA1: return "SHA1" case SHA256: return "SHA256" case HMACSHA256: return "HMAC-SHA256" default: return "UNKNOWN" } } // ParseAlgorithm 从字符串解析算法类型 func ParseAlgorithm(s string) (SignatureAlgorithm, error) { switch strings.ToUpper(s) { case "MD5": return MD5, nil case "SHA1": return SHA1, nil case "SHA256": return SHA256, nil case "HMAC_SHA256", "HMACSHA256", "HMAC-SHA256": return HMACSHA256, nil default: return MD5, fmt.Errorf("unknown algorithm: %s", s) } } // SignOptions 签名选项 type SignOptions struct { // 签名算法 Algorithm SignatureAlgorithm // AccessKeyId参数名 KeyName string // ChannelId参数名 ChannelIdName string // 时间戳参数名 TimestampName string // 随机字符串参数名 NonceName string // 签名参数名 SignatureName string } // NewDefaultSignOptions 创建默认签名选项 func NewDefaultSignOptions() *SignOptions { return &SignOptions{ Algorithm: MD5, KeyName: "AccessKeyId", ChannelIdName: "channelId", TimestampName: "timestamp", NonceName: "nonce", SignatureName: "signature", } } // APISigner API签名工具 type APISigner struct { options *SignOptions } // NewAPISigner 创建API签名工具 // // 参数: // - options: 签名选项,如为nil则使用默认选项 // // 返回: // - 签名工具实例 func NewAPISigner(options *SignOptions) *APISigner { if options == nil { options = NewDefaultSignOptions() } return &APISigner{options: options} } // GenerateNonce 生成随机字符串 // // 返回: // - 一个基于当前时间的随机字符串 func (s *APISigner) GenerateNonce() string { return fmt.Sprintf("%d%d", time.Now().UnixNano(), time.Now().Unix()%1000) } // GetTimestamp 获取当前时间戳(毫秒) // // 返回: // - 当前的Unix时间戳(毫秒) func (s *APISigner) GetTimestamp() int64 { return time.Now().UnixMilli() } // SignRequest 对请求进行签名 // // 参数: // - params: 请求参数 // - accessKeyId: 访问密钥ID // - secretKey: 密钥 // - channelId: 渠道ID,如果为空则使用params中已有的channelId // // 返回: // - 添加了签名的完整参数 func (s *APISigner) SignRequest(params map[string]string, accessKeyId, secretKey string, channelId ...string) map[string]string { signParams := make(map[string]string) for k, v := range params { signParams[k] = v } timestamp := s.GetTimestamp() signParams[s.options.KeyName] = accessKeyId if len(channelId) > 0 && channelId[0] != "" { signParams[s.options.ChannelIdName] = channelId[0] } signParams[s.options.TimestampName] = strconv.FormatInt(timestamp, 10) signParams[s.options.NonceName] = s.GenerateNonce() signature := s.CalculateSignature(signParams, secretKey) signParams[s.options.SignatureName] = signature return signParams } // SignURL 对URL进行签名 // // 参数: // - baseURL: 基础URL地址 // - params: 请求参数 // - accessKeyId: 访问密钥ID // - secretKey: 密钥 // - channelId: 渠道ID,如果为空则使用params中已有的channelId // // 返回: // - 添加了签名的完整URL func (s *APISigner) SignURL(baseURL string, params map[string]string, accessKeyId, secretKey string, channelId ...string) string { signedParams := s.SignRequest(params, accessKeyId, secretKey, channelId...) values := url.Values{} for k, v := range signedParams { values.Add(k, v) } if strings.Contains(baseURL, "?") { return baseURL + "&" + values.Encode() } return baseURL + "?" + values.Encode() } // CalculateSignature 计算签名 // // 参数: // - params: 请求参数 // - secretKey: 密钥 // // 返回: // - 签名字符串 func (s *APISigner) CalculateSignature(params map[string]string, secretKey string) string { signingString := s.CreateSigningString(params) signingString = signingString + "&key=" + secretKey switch s.options.Algorithm { case SHA1: hasher := sha1.New() hasher.Write([]byte(signingString)) return hex.EncodeToString(hasher.Sum(nil)) case SHA256: hasher := sha256.New() hasher.Write([]byte(signingString)) return hex.EncodeToString(hasher.Sum(nil)) case HMACSHA256: hasher := hmac.New(sha256.New, []byte(secretKey)) hasher.Write([]byte(signingString)) return hex.EncodeToString(hasher.Sum(nil)) default: hasher := md5.New() hasher.Write([]byte(signingString)) return hex.EncodeToString(hasher.Sum(nil)) } } // CreateSigningString 创建用于签名的规范化字符串 // // 参数: // - params: 请求参数 // // 返回: // - 按键名排序并拼接的字符串 func (s *APISigner) CreateSigningString(params map[string]string) string { sortedParams := make(map[string]string) for k, v := range params { if k != s.options.SignatureName { sortedParams[k] = v } } keys := make([]string, 0, len(sortedParams)) for k := range sortedParams { keys = append(keys, k) } sort.Strings(keys) parts := make([]string, 0, len(keys)) for _, k := range keys { v := sortedParams[k] if needsURLEncode(v) { v = url.QueryEscape(v) } parts = append(parts, k+"="+v) } return strings.Join(parts, "&") } // needsURLEncode 判断是否需要对字符串进行URL编码 func needsURLEncode(s string) bool { for _, c := range s { if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) { return true } } return false } // VerifySignature 验证签名 // // 参数: // - params: 所有请求参数,包括签名 // - secretKey: 密钥 // - maxAgeMs: 允许的最大时间差(毫秒) // // 返回: // - 验证结果和错误信息 func (s *APISigner) VerifySignature(params map[string]string, secretKey string, maxAgeMs int64) (bool, error) { _, ok := params[s.options.KeyName] if !ok { return false, fmt.Errorf("missing %s", s.options.KeyName) } _, ok = params[s.options.ChannelIdName] if !ok { return false, fmt.Errorf("missing %s", s.options.ChannelIdName) } timestampStr, ok := params[s.options.TimestampName] if !ok { return false, fmt.Errorf("missing %s", s.options.TimestampName) } timestamp, err := strconv.ParseInt(timestampStr, 10, 64) if err != nil { return false, fmt.Errorf("invalid timestamp: %v", err) } now := time.Now().UnixMilli() if now-timestamp > maxAgeMs || timestamp-now > maxAgeMs { return false, fmt.Errorf("timestamp expired") } _, ok = params[s.options.NonceName] if !ok { return false, fmt.Errorf("missing %s", s.options.NonceName) } providedSignature, ok := params[s.options.SignatureName] if !ok { return false, fmt.Errorf("missing %s", s.options.SignatureName) } expectedSignature := s.CalculateSignature(params, secretKey) return expectedSignature == providedSignature, nil }