生成用户签名
本应用服务端主要负责生成用户鉴权签名(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}| 参数名 | 必选 | 类型 | 说明 |
|---|---|---|---|
| version | 是 | string | 协议版本号,当前固定值为 1(无多余空格) |
| user_id | 是 | string | 用户的唯一标识 ID(非空,建议与业务侧用户 ID 保持一致) |
| expires_at | 是 | int | 签名过期的 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 示例代码
通用说明
- 示例代码均基于「Base64 编码的对称密钥」开发;
- 示例仅包含核心加密逻辑,生产环境需补充日志、异常兜底等工程化处理;
- 对于 SM4-GCM 算法,部分编程语言可能需要额外引入第三方库(如 Java 需 BouncyCastle,Go 需 tjfoc/gmsm,Python 需 gmssl,Node.js 需 sm-crypto 等),示例中会注明依赖。
Java
环境依赖
JDK 8+(无需额外依赖,基于 JDK 内置加密库)
- AES-256-GCM 示例(工具类 + 调用示例)
javaimport 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 库)
javaimport 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 示例
gopackage 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
gopackage 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 cryptographypythonimport 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 gmsslpythonimport 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 示例
javascriptconst 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 常见问题排查
加密失败常见原因
- 密钥格式错误:密钥不是合法的 Base64 编码,或解码后长度与所选算法不匹配(AES-256 需 32 字节,SM4 需 16 字节)。
- Payload 格式错误:使用格式化 JSON(含空格/换行)、字段类型错误(如 expires_at 为字符串);
- 字符编码不一致:未使用 UTF-8 编码加密字符串;
- IV 生成不安全:使用了固定 IV 或弱随机数生成器(必须使用密码学安全的随机数)。
- 跨语言拼接顺序不一致:加密结果必须严格遵循 IV(12字节) + 密文 + 认证标签(16字节) 的顺序。
- expires_at 异常:时间戳为非整型、已过期、时区错误(非 UTC)。
签名鉴权失败补充检查
- 确认
version固定为字符串 "1",非数字 1; - 确认
expires_at时间戳未过期,且为 UTC 时区; - 确认加密前的 JSON 字符串无任何格式修改(如多余逗号、字段顺序变更);
- 若使用 SM4-GCM,确保所用加密库与解密方兼容(如标签长度、IV 长度一致)。