Skip to content

生成用户签名指引

本应用服务端主要负责生成用户鉴权签名(userSignature),并返回给客户端,该用户鉴权签名是客户端进行身份鉴权的核心凭证。

1.1 前置准备

在开始集成前,请确保完成以下准备工作:

  • 配置内容加密密钥:需在SUD OpenPaaS管理后台完成对称密钥的配置。支持两种加密算法:

    • AES-256-GCM:密钥为 32 字节(256 位),Base64 编码。
    • SM4-GCM:密钥为 16 字节(128 位),Base64 编码。

    请根据所选算法配置相应类型的密钥,并妥善保管。

1.2 生成鉴权签名

服务端需要构造特定的 JSON 数据并进行加密,以生成鉴权所需的 用户鉴权签名(userSignature)

1. 构造加密载荷 (Payload)

请构造包含以下字段的紧凑格式 JSON 字符串(无空格、无换行)

json
{"version":"1","user_id":"xxx","expires_at":1761638855}
参数名必选类型说明
versionstring协议版本号,当前固定值为 1(无多余空格)
user_idstring用户的唯一标识 ID(非空,建议与业务侧用户 ID 保持一致)
expires_atint签名过期的 Unix 时间戳(单位:秒,UTC 时区);建议有效期不超过 24 小时

参数校验规则

  • version 必须为字符串 "1",不允许修改;
  • user_id 不能为空字符串或空值;
  • expires_at 必须为整型正数,且需大于当前时间戳(避免签名生成即过期)。

2. 加密处理

使用在前置准备中配置的对称密钥,根据所选算法遵循以下规范对上述 JSON 字符串进行加密。加密结果格式统一为:初始化向量 (IV) + 密文 + 认证标签,并对该二进制数据进行 Base64 编码,最终返回 Base64 编码字符串。

AES-256-GCM 算法参数

  • 加密算法:AES-256-GCM
  • 密钥长度:256 位(32 字节)
  • 初始化向量(IV):每次加密必须随机生成,长度为 12 字节(96位)
  • 认证标签长度:16 字节(128 位)
  • 字符编码:UTF-8

SM4-GCM 算法参数

  • 加密算法:SM4-GCM(国密算法)
  • 密钥长度:128 位(16 字节)
  • 初始化向量(IV):每次加密必须随机生成,长度为 12 字节(96位)
  • 认证标签长度:16 字节(128 位)
  • 字符编码:UTF-8

1.3 示例代码

通用说明

  1. 示例代码均基于「Base64 编码的对称密钥」开发;
  2. 示例仅包含核心加密逻辑,生产环境需补充日志、异常兜底等工程化处理;
  3. 对于 SM4-GCM 算法,部分编程语言可能需要额外引入第三方库(如 Java 需 BouncyCastle,Go 需 tjfoc/gmsm,Python 需 gmssl,Node.js 需 sm-crypto 等),示例中会注明依赖。

Java

  • 环境依赖

  • JDK 8+(无需额外依赖,基于 JDK 内置加密库)

    • AES-256-GCM 示例(工具类 + 调用示例)
    java
      import javax.crypto.Cipher;
      import javax.crypto.spec.GCMParameterSpec;
      import javax.crypto.spec.SecretKeySpec;
      import java.nio.charset.StandardCharsets;
      import java.security.SecureRandom;
      import java.util.Base64;
    
      public class AESGCMEncryptUtil {
    
          private static final String AES_ALGORITHM = "AES";
          private static final String AES_TRANSFORMATION = "AES/GCM/NoPadding";
          private static final int IV_SIZE_BYTES = 12;
          private static final int TAG_SIZE_BITS = 128;
    
          public static String encrypt(String data, String base64Key) throws Exception {
              byte[] keyBytes = Base64.getDecoder().decode(base64Key);
              if (keyBytes.length != 32) {
                  throw new IllegalArgumentException("AES-256 密钥长度必须为 32 字节");
              }
              SecretKeySpec secretKey = new SecretKeySpec(keyBytes, AES_ALGORITHM);
    
              byte[] iv = new byte[IV_SIZE_BYTES];
              new SecureRandom().nextBytes(iv);
    
              Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
              cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_SIZE_BITS, iv));
    
              byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
              byte[] ciphertextWithTag = cipher.doFinal(dataBytes);
    
              byte[] result = new byte[iv.length + ciphertextWithTag.length];
              System.arraycopy(iv, 0, result, 0, iv.length);
              System.arraycopy(ciphertextWithTag, 0, result, iv.length, ciphertextWithTag.length);
    
              return Base64.getEncoder().encodeToString(result);
          }
    
          public static void main(String[] args) throws Exception {
              String payload = "{\"version\":\"1\",\"user_id\":\"user_123456\",\"expires_at\":1761638855}";
              String base64Key = "你的Base64编码的32字节AES密钥";
              String userSignature = encrypt(payload, base64Key);
              System.out.println("AES-256-GCM 签名:" + userSignature);
          }
      }
    • SM4-GCM 示例(需 BouncyCastle 库)
    java
      import org.bouncycastle.jce.provider.BouncyCastleProvider;
      import javax.crypto.Cipher;
      import javax.crypto.spec.GCMParameterSpec;
      import javax.crypto.spec.SecretKeySpec;
      import java.nio.charset.StandardCharsets;
      import java.security.SecureRandom;
      import java.security.Security;
      import java.util.Base64;
    
      public class SM4GCMEncryptUtil {
    
          static {
              Security.addProvider(new BouncyCastleProvider());
          }
    
          private static final String SM4_ALGORITHM = "SM4";
          private static final String SM4_TRANSFORMATION = "SM4/GCM/NoPadding";
          private static final int IV_SIZE_BYTES = 12;
          private static final int TAG_SIZE_BITS = 128;
    
          public static String encrypt(String data, String base64Key) throws Exception {
              byte[] keyBytes = Base64.getDecoder().decode(base64Key);
              if (keyBytes.length != 16) {
                  throw new IllegalArgumentException("SM4 密钥长度必须为 16 字节");
              }
              SecretKeySpec secretKey = new SecretKeySpec(keyBytes, SM4_ALGORITHM);
    
              byte[] iv = new byte[IV_SIZE_BYTES];
              new SecureRandom().nextBytes(iv);
    
              Cipher cipher = Cipher.getInstance(SM4_TRANSFORMATION, "BC");
              cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_SIZE_BITS, iv));
    
              byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
              byte[] ciphertextWithTag = cipher.doFinal(dataBytes);
    
              byte[] result = new byte[iv.length + ciphertextWithTag.length];
              System.arraycopy(iv, 0, result, 0, iv.length);
              System.arraycopy(ciphertextWithTag, 0, result, iv.length, ciphertextWithTag.length);
    
              return Base64.getEncoder().encodeToString(result);
          }
    
          public static void main(String[] args) throws Exception {
              String payload = "{\"version\":\"1\",\"user_id\":\"user_123456\",\"expires_at\":1761638855}";
              String base64Key = "你的Base64编码的16字节SM4密钥";
              String userSignature = encrypt(payload, base64Key);
              System.out.println("SM4-GCM 签名:" + userSignature);
          }
      }

Go

  • 环境依赖

  • Go 1.15+(无需额外依赖,基于标准库)

    • AES-256-GCM 示例
    go
      package main
    
      import (
          "crypto/aes"
          "crypto/cipher"
          "crypto/rand"
          "encoding/base64"
          "fmt"
          "io"
      )
    
      func EncryptAESGCM(data, base64Key string) (string, error) {
          key, err := base64.StdEncoding.DecodeString(base64Key)
          if err != nil {
              return "", fmt.Errorf("密钥解码失败: %w", err)
          }
          if len(key) != 32 {
              return "", fmt.Errorf("AES-256 密钥长度必须为 32 字节")
          }
    
          block, err := aes.NewCipher(key)
          if err != nil {
              return "", fmt.Errorf("创建 AES 密码块失败: %w", err)
          }
    
          iv := make([]byte, 12)
          if _, err := io.ReadFull(rand.Reader, iv); err != nil {
              return "", fmt.Errorf("生成 IV 失败: %w", err)
          }
    
          gcm, err := cipher.NewGCM(block)
          if err != nil {
              return "", fmt.Errorf("创建 GCM 失败: %w", err)
          }
    
          dataBytes := []byte(data)
          ciphertextWithTag := gcm.Seal(nil, iv, dataBytes, nil)
    
          result := append(iv, ciphertextWithTag...)
          return base64.StdEncoding.EncodeToString(result), nil
      }
    
      func main() {
          payload := `{"version":"1","user_id":"user_123456","expires_at":1761638855}`
          base64Key := "你的Base64编码的32字节AES密钥"
          sig, err := EncryptAESGCM(payload, base64Key)
          if err != nil {
              fmt.Printf("AES加密失败: %v\n", err)
              return
          }
          fmt.Println("AES-256-GCM 签名:", sig)
      }
    • SM4-GCM 示例(需 tjfoc/gmsm 库)
    • 依赖go get -u github.com/tjfoc/gmsm
    go
      package main
    
      import (
          "crypto/rand"
          "encoding/base64"
          "fmt"
          "io"
          "github.com/tjfoc/gmsm/sm4"
      )
    
      func EncryptSM4GCM(data, base64Key string) (string, error) {
          key, err := base64.StdEncoding.DecodeString(base64Key)
          if err != nil {
              return "", fmt.Errorf("密钥解码失败: %w", err)
          }
          if len(key) != 16 {
              return "", fmt.Errorf("SM4 密钥长度必须为 16 字节")
          }
    
          block, err := sm4.NewCipher(key)
          if err != nil {
              return "", fmt.Errorf("创建 SM4 密码块失败: %w", err)
          }
    
          iv := make([]byte, 12)
          if _, err := io.ReadFull(rand.Reader, iv); err != nil {
              return "", fmt.Errorf("生成 IV 失败: %w", err)
          }
    
          gcm, err := cipher.NewGCM(block) // 注意:gmsm 的 sm4.NewCipher 返回的 block 实现了 cipher.Block 接口,可以直接使用 cipher.NewGCM
          if err != nil {
              return "", fmt.Errorf("创建 GCM 失败: %w", err)
          }
    
          dataBytes := []byte(data)
          ciphertextWithTag := gcm.Seal(nil, iv, dataBytes, nil)
    
          result := append(iv, ciphertextWithTag...)
          return base64.StdEncoding.EncodeToString(result), nil
      }
    
      func main() {
          payload := `{"version":"1","user_id":"user_123456","expires_at":1761638855}`
          base64Key := "你的Base64编码的16字节SM4密钥"
          sig, err := EncryptSM4GCM(payload, base64Key)
          if err != nil {
              fmt.Printf("SM4加密失败: %v\n", err)
              return
          }
          fmt.Println("SM4-GCM 签名:", sig)
      }

Python

  • 环境依赖

    • Python 3.6+
  • AES-256-GCM 示例

  • 依赖pip install cryptography

    python
     import base64
     import os
     from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
     from cryptography.hazmat.backends import default_backend
    
     def encrypt_aes_gcm(data: str, base64_key: str) -> str:
         key = base64.b64decode(base64_key)
         if len(key) != 32:
             raise ValueError("AES-256 密钥长度必须为 32 字节")
         iv = os.urandom(12)
         cipher = Cipher(algorithms.AES(key), modes.GCM(iv), backend=default_backend())
         encryptor = cipher.encryptor()
         ciphertext = encryptor.update(data.encode('utf-8')) + encryptor.finalize()
         tag = encryptor.tag
         result = iv + ciphertext + tag
         return base64.b64encode(result).decode('utf-8')
    
     if __name__ == "__main__":
         payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}'
         base64_key = "你的Base64编码的32字节AES密钥"
         sig = encrypt_aes_gcm(payload, base64_key)
         print("AES-256-GCM 签名:", sig)
  • SM4-GCM 示例(需 gmssl 库)

  • 依赖pip install gmssl

    python
     import base64
     import os
     from gmssl.sm4 import CryptSM4, SM4_GCM
    
     def encrypt_sm4_gcm(data: str, base64_key: str) -> str:
         key = base64.b64decode(base64_key)
         if len(key) != 16:
             raise ValueError("SM4 密钥长度必须为 16 字节")
         iv = os.urandom(12)
         cipher = CryptSM4()
         cipher.set_key(key, SM4_GCM)
         # gmssl 的 GCM 模式需要手动处理 IV 和 tag
         # 该库的 encrypt_gcm 返回 (ciphertext, tag)
         ciphertext, tag = cipher.encrypt_gcm(data.encode('utf-8'), iv)
         result = iv + ciphertext + tag
         return base64.b64encode(result).decode('utf-8')
    
     if __name__ == "__main__":
         payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}'
         base64_key = "你的Base64编码的16字节SM4密钥"
         sig = encrypt_sm4_gcm(payload, base64_key)
         print("SM4-GCM 签名:", sig)

PHP

  • 环境依赖

    • PHP 7.2+
    • 开启 OpenSSL 扩展(php.ini 中启用 extension=openssl
  • AES-256-GCM 示例

    php
      <?php
      function encryptAESGCM(string $data, string $base64Key): string {
          $key = base64_decode($base64Key, true);
          if ($key === false || strlen($key) !== 32) {
              throw new Exception("AES-256 密钥必须为 32 字节的 Base64 编码字符串");
          }
          $iv = random_bytes(12);
          $tag = '';
          $ciphertext = openssl_encrypt(
              $data,
              'aes-256-gcm',
              $key,
              OPENSSL_RAW_DATA,
              $iv,
              $tag
          );
          if ($ciphertext === false) {
              throw new Exception("加密失败: " . openssl_error_string());
          }
          $result = $iv . $ciphertext . $tag;
          return base64_encode($result);
      }
    
      // 调用示例
      $payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}';
      $base64Key = '你的Base64编码的32字节AES密钥';
      $signature = encryptAESGCM($payload, $base64Key);
      echo "AES-256-GCM 签名:" . $signature;
      ?>
  • SM4-GCM 示例(需第三方扩展,如 php-sm4 或 openssl 的 SM4 支持)

  • 注意:PHP 的 OpenSSL 扩展目前不支持 SM4-GCM,建议使用第三方 PHP 扩展(如 php-sm4)或通过 HTTP 调用加密服务。以下示例假设存在一个支持 SM4-GCM 的类 Sm4GcmEncryptor(需自行实现或使用第三方库)。

    php
      <?php
      // 假设已经引入支持 SM4-GCM 的库
      // 例如使用 php-sm4 扩展(https://github.com/lizhichao/sm4)
      // 该扩展提供了 \Sm4\Sm4 类,但需要自行实现 GCM 模式
      // 这里仅作伪代码示意
    
      function encryptSM4GCM(string $data, string $base64Key): string {
          $key = base64_decode($base64Key, true);
          if ($key === false || strlen($key) !== 16) {
              throw new Exception("SM4 密钥必须为 16 字节的 Base64 编码字符串");
          }
          $iv = random_bytes(12);
          // 调用第三方库的 GCM 加密方法,返回密文和标签
          // 假设 $ciphertextWithTag 已经是 密文+标签
          // 这里以伪代码表示,实际请根据所用库调整
          $cipher = new \Sm4\Sm4(); // 假设存在
          $ciphertextWithTag = $cipher->encryptGcm($data, $key, $iv); // 返回密文+标签
          $result = $iv . $ciphertextWithTag;
          return base64_encode($result);
      }
    
      // 调用示例
      $payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}';
      $base64Key = '你的Base64编码的16字节SM4密钥';
      $signature = encryptSM4GCM($payload, $base64Key);
      echo "SM4-GCM 签名:" . $signature;
      ?>

Node.js

  • 环境依赖

    • Node.js 12+(无需额外依赖,基于内置 crypto 模块)
  • AES-256-GCM 示例

    javascript
      const crypto = require('crypto');
    
      function encryptAESGCM(data, base64Key) {
          const key = Buffer.from(base64Key, 'base64');
          if (key.length !== 32) throw new Error('AES-256 密钥长度必须为 32 字节');
          const iv = crypto.randomBytes(12);
          const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
          const ciphertext = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
          const tag = cipher.getAuthTag();
          const result = Buffer.concat([iv, ciphertext, tag]);
          return result.toString('base64');
      }
    
      // 调用示例
      const payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}';
      const base64Key = '你的Base64编码的32字节AES密钥';
      const signature = encryptAESGCM(payload, base64Key);
      console.log('AES-256-GCM 签名:', signature);
  • SM4-GCM 示例(需 sm-crypto 库)

  • 依赖npm install sm-crypto

javascript
   const sm4 = require('sm-crypto').sm4;

   function encryptSM4GCM(data, base64Key) {
       const key = Buffer.from(base64Key, 'base64');
       if (key.length !== 16) throw new Error('SM4 密钥长度必须为 16 字节');
       const iv = crypto.randomBytes(12); // 注意:sm-crypto 的 sm4.gcm 需要 Buffer 或 Uint8Array
       // sm-crypto 的 GCM 加密方法返回 { ciphertext, tag }
       // 注意:该库的 sm4.gcm.encrypt 需要传入 key, iv, plaintext, aad? 具体查阅文档
       // 这里假设用法如下:
       const encrypted = sm4.gcm.encrypt(data, key, iv, null); // 返回 { ciphertext, tag }
       const ciphertext = Buffer.from(encrypted.ciphertext, 'hex');
       const tag = Buffer.from(encrypted.tag, 'hex');
       const result = Buffer.concat([iv, ciphertext, tag]);
       return result.toString('base64');
   }

   // 调用示例
   const payload = '{"version":"1","user_id":"user_123456","expires_at":1761638855}';
   const base64Key = '你的Base64编码的16字节SM4密钥';
   const signature = encryptSM4GCM(payload, base64Key);
   console.log('SM4-GCM 签名:', signature);

1.4 常见问题排查

加密失败常见原因

  1. 密钥格式错误:密钥不是合法的 Base64 编码,或解码后长度与所选算法不匹配(AES-256 需 32 字节,SM4 需 16 字节)。
  2. Payload 格式错误:使用格式化 JSON(含空格/换行)、字段类型错误(如 expires_at 为字符串);
  3. 字符编码不一致:未使用 UTF-8 编码加密字符串;
  4. IV 生成不安全:使用了固定 IV 或弱随机数生成器(必须使用密码学安全的随机数)。
  5. 跨语言拼接顺序不一致:加密结果必须严格遵循 IV(12字节) + 密文 + 认证标签(16字节) 的顺序。
  6. expires_at 异常:时间戳为非整型、已过期、时区错误(非 UTC)。

签名鉴权失败补充检查

  1. 确认 version 固定为字符串 "1",非数字 1;
  2. 确认 expires_at 时间戳未过期,且为 UTC 时区;
  3. 确认加密前的 JSON 字符串无任何格式修改(如多余逗号、字段顺序变更);
  4. 若使用 SM4-GCM,确保所用加密库与解密方兼容(如标签长度、IV 长度一致)。