Skip to content

Golang示例代码

1. 核心安全组件 (sud/security.go)

go
package sud

import (
    "crypto"
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "crypto/x509"
    "encoding/base64"
    "encoding/json"
    "encoding/pem"
    "errors"
    "fmt"
    "io"
)

const (
    gcmStandardNonceSize = 12
)

// EncryptedBody 对应 HTTP 请求 Body 的 JSON 结构
type EncryptedBody struct {
    Iv      string `json:"iv"`
    Data    string `json:"data"`
    AuthTag string `json:"authtag"`
}

// EncryptRequest 执行 AES-256-GCM 请求数据加密
func EncryptRequest(urlPath, appId, keySn, symKeyBase64 string, reqData map[string]any, timestamp int64) (*EncryptedBody, error) {
    // 生成防重放 Nonce (16字节随机数,Base64 Raw 编码无等号填充)
    nonceBytes := make([]byte, 16)
    if _, err := io.ReadFull(rand.Reader, nonceBytes); err != nil {
        return nil, fmt.Errorf("generate nonce failed: %w", err)
    }
    nonce := base64.RawStdEncoding.EncodeToString(nonceBytes)

    // 补齐安全校验字段
    reqData["_n"] = nonce
    reqData["_appid"] = appId
    reqData["_timestamp"] = timestamp

    plaintext, err := json.Marshal(reqData)
    if err != nil {
        return nil, fmt.Errorf("marshal request data failed: %w", err)
    }

    // 拼接 AAD: urlpath|appid|timestamp|sn
    aadStr := fmt.Sprintf("%s|%s|%d|%s", urlPath, appId, timestamp, keySn)
    aad := []byte(aadStr)

    realKey, err := base64.StdEncoding.DecodeString(symKeyBase64)
    if err != nil {
        return nil, fmt.Errorf("decode symmetric key failed: %w", err)
    }

    realIv := make([]byte, gcmStandardNonceSize)
    if _, err := io.ReadFull(rand.Reader, realIv); err != nil {
        return nil, fmt.Errorf("generate gcm iv failed: %w", err)
    }

    block, err := aes.NewCipher(realKey)
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    // gcm.Seal 会将 AuthTag 自动追加在密文切片末尾
    sealedData := gcm.Seal(nil, realIv, plaintext, aad)
    
    // 分离密文与 AuthTag
    tagSize := gcm.Overhead()
    ciphertext := sealedData[:len(sealedData)-tagSize]
    authTag := sealedData[len(sealedData)-tagSize:]

    return &EncryptedBody{
        Iv:      base64.StdEncoding.EncodeToString(realIv),
        Data:    base64.StdEncoding.EncodeToString(ciphertext),
        AuthTag: base64.StdEncoding.EncodeToString(authTag),
    }, nil
}

// SignRequest 生成 RSA-PSS 请求签名
func SignRequest(urlPath, appId string, timestamp int64, reqDataJSON string, privateKeyPem string) (string, error) {
    // 严格按顺序拼接,不添加多余的结尾换行符
    payload := fmt.Sprintf("%s\n%s\n%d\n%s", urlPath, appId, timestamp, reqDataJSON)
    hashed := sha256.Sum256([]byte(payload))

    block, _ := pem.Decode([]byte(privateKeyPem))
    if block == nil {
        return "", errors.New("parse private key failed: invalid PEM format")
    }

    // 解析 PKCS#8 格式私钥
    privInterface, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    if err != nil {
        return "", fmt.Errorf("parse PKCS#8 private key failed: %w", err)
    }

    privKey, ok := privInterface.(*rsa.PrivateKey)
    if !ok {
        return "", errors.New("parsed key is not an RSA private key")
    }

    // 配置 PSS 参数,盐长度对齐 SHA256 (32字节)
    opts := &rsa.PSSOptions{
        SaltLength: rsa.PSSSaltLengthEqualsHash,
        Hash:       crypto.SHA256,
    }

    sigBytes, err := rsa.SignPSS(rand.Reader, privKey, crypto.SHA256, hashed[:], opts)
    if err != nil {
        return "", fmt.Errorf("sign PSS failed: %w", err)
    }

    return base64.StdEncoding.EncodeToString(sigBytes), nil
}

// VerifyResponse 验证平台响应的 RSA-PSS 签名
func VerifyResponse(urlPath, expectedAppId, localCertSn, platformCertPem string,
    respAppId string, respTs int64, respSn, respSig, respDeprecatedSn, respDeprecatedSig, respDataJSON string) error {

    if expectedAppId != respAppId {
        return errors.New("security check failed: appid mismatch")
    }

    // 证书序列号匹配与平滑轮换支持
    var signatureBase64 string
    if localCertSn == respSn {
        signatureBase64 = respSig
    } else if respDeprecatedSn != "" && localCertSn == respDeprecatedSn {
        signatureBase64 = respSig
        if respDeprecatedSig != "" {
            signatureBase64 = respDeprecatedSig
        }
    } else {
        return fmt.Errorf("verify failed: cert sn mismatch (local: %s, remote: %s)", localCertSn, respSn)
    }

    payload := fmt.Sprintf("%s\n%s\n%d\n%s", urlPath, respAppId, respTs, respDataJSON)
    hashed := sha256.Sum256([]byte(payload))

    block, _ := pem.Decode([]byte(platformCertPem))
    if block == nil {
        return errors.New("parse cert failed: invalid PEM format")
    }

    cert, err := x509.ParseCertificate(block.Bytes)
    if err != nil {
        return fmt.Errorf("parse x509 cert failed: %w", err)
    }

    pubKey, ok := cert.PublicKey.(*rsa.PublicKey)
    if !ok {
        return errors.New("cert public key is not RSA")
    }

    sigBytes, err := base64.StdEncoding.DecodeString(signatureBase64)
    if err != nil {
        return fmt.Errorf("decode signature base64 failed: %w", err)
    }

    opts := &rsa.PSSOptions{
        SaltLength: rsa.PSSSaltLengthEqualsHash,
        Hash:       crypto.SHA256,
    }

    if err := rsa.VerifyPSS(pubKey, crypto.SHA256, hashed[:], sigBytes, opts); err != nil {
        return fmt.Errorf("verify PSS signature failed: %w", err)
    }

    return nil
}

// DecryptResponse 执行 AES-256-GCM 响应解密并校验安全字段
func DecryptResponse(urlPath, appId string, respTs int64, keySn, symKeyBase64, respDataJSON string) (map[string]any, error) {
    var encBody EncryptedBody
    if err := json.Unmarshal([]byte(respDataJSON), &encBody); err != nil {
        return nil, fmt.Errorf("unmarshal encrypted body failed: %w", err)
    }

    aadStr := fmt.Sprintf("%s|%s|%d|%s", urlPath, appId, respTs, keySn)
    aad := []byte(aadStr)

    realKey, err := base64.StdEncoding.DecodeString(symKeyBase64)
    if err != nil {
        return nil, fmt.Errorf("decode symmetric key failed: %w", err)
    }

    ivBytes, _ := base64.StdEncoding.DecodeString(encBody.Iv)
    dataBytes, _ := base64.StdEncoding.DecodeString(encBody.Data)
    authTagBytes, _ := base64.StdEncoding.DecodeString(encBody.AuthTag)

    // Go 规范:将密文与 AuthTag 组合
    combinedData := append(dataBytes, authTagBytes...)

    block, err := aes.NewCipher(realKey)
    if err != nil {
        return nil, err
    }
    gcm, err := cipher.NewGCM(block)
    if err != nil {
        return nil, err
    }

    // 解密并验证 AuthTag
    decryptedBytes, err := gcm.Open(nil, ivBytes, combinedData, aad)
    if err != nil {
        return nil, fmt.Errorf("gcm open failed (invalid authtag or tampered data): %w", err)
    }

    var realResp map[string]any
    if err := json.Unmarshal(decryptedBytes, &realResp); err != nil {
        return nil, fmt.Errorf("unmarshal decrypted plaintext failed: %w", err)
    }

    // 校验防重放安全字段 (注意 Go 弱类型 JSON 数字默认解析为 float64)
    innerAppID, ok1 := realResp["_appid"].(string)
    innerTimestampFloat, ok2 := realResp["_timestamp"].(float64)

    if !ok1 || !ok2 || innerAppID != appId || int64(innerTimestampFloat) != respTs {
        return nil, errors.New("security field validation failed: internal _appid or _timestamp tampered")
    }

    return realResp, nil
}

2. 业务调用演示 (main.go)

go
package main

import (
    "encoding/json"
    "fmt"
    "time"

    "your_project/sud" // 引入上方的工具包
)

func main() {
    // 1. 全局应用配置
    const (
        appID           = "cp_10010"
        urlPath         = "https://api.sud.com/v1/game/login"
        symKeySn        = "key_v1"
        symKeyBase64    = "otUpngOjU+nVQaWJIC3D/yMLV17RKaP6t4Ot9tbnzLY="
        privateKeyPem   = "-----BEGIN PRIVATE KEY-----\nMIIEvQ... (应用私钥) ...W6w=\n-----END PRIVATE KEY-----"
        platformCertSn  = "sud_cert_v1"
        platformCertPem = "-----BEGIN CERTIFICATE-----\nMIID0j... (SUD平台公钥证书) ...Q==\n-----END CERTIFICATE-----"
    )

    fmt.Println("=== 1. 发起请求:业务数据加密与签名 ===")
    
    // 构造业务请求数据
    reqTs := time.Now().Unix()
    bizReq := map[string]any{
        "appid":     appID,
        "sud_uid":   "user_889900",
        "scene":     0,
        "client_ip": "127.0.0.1",
    }

    // 步骤 A:执行加密
    encBody, err := sud.EncryptRequest(urlPath, appID, symKeySn, symKeyBase64, bizReq, reqTs)
    if err != nil {
        panic(fmt.Sprintf("加密失败: %v", err))
    }
    
    reqBodyJSON, _ := json.Marshal(encBody)
    reqBodyStr := string(reqBodyJSON)

    // 步骤 B:生成签名
    reqSig, err := sud.SignRequest(urlPath, appID, reqTs, reqBodyStr, privateKeyPem)
    if err != nil {
        panic(fmt.Sprintf("签名失败: %v", err))
    }

    fmt.Printf("HTTP Header X-Sud-Timestamp: %d\n", reqTs)
    fmt.Printf("HTTP Header X-Sud-Signature: %s\n", reqSig)
    fmt.Printf("HTTP Body: %s\n", reqBodyStr)


    fmt.Println("\n=== 2. 收到响应:签名验证与密文解密 ===")

    // 模拟从 HTTP 响应拦截器中获取到的数据
    respTs := reqTs
    respHeaderAppId := appID
    respHeaderSn := platformCertSn
    respHeaderSig := reqSig // 这里仅用作演示,实际应为接收到的签名
    respBodyStr := `{"iv":"r2WDQt56rEAmMuoR","data":"HExs66...","authtag":"z2BF..."}`

    // 步骤 C:执行验签
    err = sud.VerifyResponse(urlPath, appID, platformCertSn, platformCertPem, 
        respHeaderAppId, respTs, respHeaderSn, respHeaderSig, "", "", respBodyStr)
    
    if err != nil {
        fmt.Printf("响应验签失败,丢弃报文: %v\n", err)
    } else {
        fmt.Println("响应验签成功!")
        
        // 步骤 D:执行解密
        realResp, err := sud.DecryptResponse(urlPath, appID, respTs, symKeySn, symKeyBase64, respBodyStr)
        if err != nil {
            fmt.Printf("响应解密失败: %v\n", err)
        } else {
            respJSON, _ := json.MarshalIndent(realResp, "", "  ")
            fmt.Printf("响应解密成功,业务明文数据:\n%s\n", string(respJSON))
        }
    }
}