Skip to content

会话密钥解密指引

SUD OP SVR 后台会加密用户的会话密钥 (sessionKey),并通过客户端以 encrypted_session_key 的形式传递给应用服务端(APP SVR)。服务端需先对其进行解密,获取真实的 sessionKey,随后方可使用该真实密钥对用户数据进行签名和加密。

1.1 前置准备

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

  • 配置内容加密密钥:需在 SUD OpenPaaS 管理后台完成对称密钥的配置。当前支持两种加密算法:
    • AES-256-GCM:密钥为 32 字节(256 位),配置为 Base64 编码。
    • SM4-GCM:密钥为 16 字节(128 位),配置为 Base64 编码。

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

1.2 解密与校验流程

1. 对数据进行Base64解码

将获取到的 encrypted_session_key(Base64 字符串)解码为二进制数据 (byte array / Buffer)。

2. 拆分数据

解码后的二进制密文数据结构固定为以下拼接格式:

text
+----------------+-------------------+-------------------+
|   IV (12 字节)  |   密文 (变长)     |  认证标签 (16 字节) |
+----------------+-------------------+-------------------+
  • IV:初始化向量,12 字节

  • 密文:加密后的载荷数据,长度可变

  • 认证标签:GCM 模式的认证信息,16 字节

3. 解密与认证

根据所使用的算法(AES-256-GCM 或 SM4-GCM),使用相同的密钥、IV、认证标签对密文进行解密。

  • 解密成功时,返回原始 JSON 字符串载荷。

  • 解密失败时,说明数据无效或被篡改。

4. 校验载荷内容

解密后的 JSON 字符串,并包含以下字段:

json
{"user_id":"xxx","session_key":"xxx"}
参数名必选类型说明
user_idstring用户的唯一标识 ID
session_keystring会话密钥

参数校验规则

  • user_id 校验用户是否相同;

1.3 示例代码

通用说明

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

Java

  • 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.util.Base64;
    
      public class AESGCMDecryptUtil {
    
          private static final int IV_SIZE_BYTES = 12;
          private static final int TAG_SIZE_BITS = 128;
    
          public static String decrypt(String encryptedData, 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");
    
              byte[] data = Base64.getDecoder().decode(encryptedData);
              if (data.length < IV_SIZE_BYTES + TAG_SIZE_BITS / 8) {
                  throw new IllegalArgumentException("数据长度不足,无法拆分 IV 和标签");
              }
    
              byte[] iv = new byte[IV_SIZE_BYTES];
              System.arraycopy(data, 0, iv, 0, IV_SIZE_BYTES);
              byte[] ciphertextWithTag = new byte[data.length - IV_SIZE_BYTES];
              System.arraycopy(data, IV_SIZE_BYTES, ciphertextWithTag, 0, ciphertextWithTag.length);
    
              Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
              GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_SIZE_BITS, iv);
              cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec);
    
              byte[] plaintext = cipher.doFinal(ciphertextWithTag);
              return new String(plaintext, StandardCharsets.UTF_8);
          }
    
          public static void main(String[] args) throws Exception {
              String encryptedData = "Base64编码的内容";
              String base64Key = "你的Base64编码的32字节AES密钥";
              String payload = decrypt(encryptedData, base64Key);
              System.out.println("解密载荷:" + payload);
          }
      }
  • 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.Security;
      import java.util.Base64;
    
      public class SM4GCMDecryptUtil {
    
          static {
              Security.addProvider(new BouncyCastleProvider());
          }
    
          private static final int IV_SIZE_BYTES = 12;
          private static final int TAG_SIZE_BITS = 128;
    
          public static String decrypt(String encryptedData, 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");
    
              byte[] data = Base64.getDecoder().decode(encryptedData);
              if (data.length < IV_SIZE_BYTES + TAG_SIZE_BITS / 8) {
                  throw new IllegalArgumentException("数据长度不足,无法拆分 IV 和标签");
              }
    
              byte[] iv = new byte[IV_SIZE_BYTES];
              System.arraycopy(data, 0, iv, 0, IV_SIZE_BYTES);
              byte[] ciphertextWithTag = new byte[data.length - IV_SIZE_BYTES];
              System.arraycopy(data, IV_SIZE_BYTES, ciphertextWithTag, 0, ciphertextWithTag.length);
    
              Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
              GCMParameterSpec gcmSpec = new GCMParameterSpec(TAG_SIZE_BITS, iv);
              cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmSpec);
    
              byte[] plaintext = cipher.doFinal(ciphertextWithTag);
              return new String(plaintext, StandardCharsets.UTF_8);
          }
      }

Go

  • AES-256-GCM 示例

    go
      package main
    
      import (
          "crypto/aes"
          "crypto/cipher"
          "encoding/base64"
          "fmt"
      )
    
      func DecryptAESGCM(encryptedData, 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 字节")
          }
    
          data, err := base64.StdEncoding.DecodeString(encryptedData)
          if err != nil {
              return "", fmt.Errorf("签名解码失败: %w", err)
          }
          if len(data) < 12+16 {
              return "", fmt.Errorf("数据长度不足")
          }
    
          iv := data[:12]
          ciphertextWithTag := data[12:]
    
          block, err := aes.NewCipher(key)
          if err != nil {
              return "", fmt.Errorf("创建 AES 密码块失败: %w", err)
          }
    
          gcm, err := cipher.NewGCM(block)
          if err != nil {
              return "", fmt.Errorf("创建 GCM 失败: %w", err)
          }
    
          plaintext, err := gcm.Open(nil, iv, ciphertextWithTag, nil)
          if err != nil {
              return "", fmt.Errorf("解密失败: %w", err)
          }
          return string(plaintext), nil
      }
  • SM4-GCM 示例(需 tjfoc/gmsm 库)

  • 依赖go get -u github.com/tjfoc/gmsm

    go
      import (
          "crypto/cipher"
          "encoding/base64"
          "fmt"
          "github.com/tjfoc/gmsm/sm4"
      )
    
      func DecryptSM4GCM(encryptedData, 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 字节")
          }
    
          data, err := base64.StdEncoding.DecodeString(encryptedData)
          if err != nil {
              return "", fmt.Errorf("签名解码失败: %w", err)
          }
          if len(data) < 12+16 {
              return "", fmt.Errorf("数据长度不足")
          }
    
          iv := data[:12]
          ciphertextWithTag := data[12:]
    
          block, err := sm4.NewCipher(key)
          if err != nil {
              return "", fmt.Errorf("创建 SM4 密码块失败: %w", err)
          }
    
          gcm, err := cipher.NewGCM(block)
          if err != nil {
              return "", fmt.Errorf("创建 GCM 失败: %w", err)
          }
    
          plaintext, err := gcm.Open(nil, iv, ciphertextWithTag, nil)
          if err != nil {
              return "", fmt.Errorf("解密失败: %w", err)
          }
          return string(plaintext), nil
      }

Python

  • AES-256-GCM 示例

  • 依赖pip install cryptography

    python
     import base64
     from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
     from cryptography.hazmat.backends import default_backend
    
     def decrypt_aes_gcm(encrypted_data: str, base64_key: str) -> str:
         key = base64.b64decode(base64_key)
         if len(key) != 32:
             raise ValueError("AES-256 密钥长度必须为 32 字节")
         data = base64.b64decode(encrypted_data)
         iv = data[:12]
         ciphertext_with_tag = data[12:]
         cipher = Cipher(algorithms.AES(key), modes.GCM(iv, tag=ciphertext_with_tag[-16:]), backend=default_backend())
         decryptor = cipher.decryptor()
         plaintext = decryptor.update(ciphertext_with_tag[:-16]) + decryptor.finalize()
         return plaintext.decode('utf-8')
  • SM4-GCM 示例(需 gmssl 库)

  • 依赖pip install gmssl

    python
     import base64
     from gmssl.sm4 import CryptSM4, SM4_GCM
    
     def decrypt_sm4_gcm(encrypted_data: str, base64_key: str) -> str:
         key = base64.b64decode(base64_key)
         if len(key) != 16:
             raise ValueError("SM4 密钥长度必须为 16 字节")
         data = base64.b64decode(encrypted_data)
         iv = data[:12]
         ciphertext = data[12:-16]
         tag = data[-16:]
         crypt = CryptSM4()
         crypt.set_key(key, SM4_GCM)
         plaintext = crypt.decrypt_gcm(ciphertext, iv, tag)
         return plaintext.decode('utf-8')

PHP

  • AES-256-GCM 示例

    php
      function decryptAESGCM(string $encryptedData, string $base64Key): string {
          $key = base64_decode($base64Key, true);
          if ($key === false || strlen($key) !== 32) {
              throw new Exception("AES-256 密钥长度必须为 32 字节");
          }
          $data = base64_decode($encryptedData, true);
          if ($data === false) {
              throw new Exception("签名解码失败");
          }
          $iv = substr($data, 0, 12);
          $ciphertextWithTag = substr($data, 12);
          $tag = substr($ciphertextWithTag, -16);
          $ciphertext = substr($ciphertextWithTag, 0, -16);
          $plaintext = openssl_decrypt(
              $ciphertext,
              'aes-256-gcm',
              $key,
              OPENSSL_RAW_DATA,
              $iv,
              $tag
          );
          if ($plaintext === false) {
              throw new Exception("解密失败: " . openssl_error_string());
          }
          return $plaintext;
      }
  • SM4-GCM 示例(需第三方扩展,如 php-sm4 或 openssl 的 SM4 支持)

    php
      function decryptSM4GCM(string $encryptedData, string $base64Key): string {
          // 1. 解码密钥
          $key = base64_decode($base64Key, true);
          if ($key === false || strlen($key) !== 16) {
              throw new Exception("SM4 密钥必须是16字节的Base64字符串");
          }
    
          // 2. 解码签名数据
          $data = base64_decode($encryptedData, true);
          if ($data === false) {
              throw new Exception("签名Base64解码失败");
          }
          if (strlen($data) < 12 + 16) {
              throw new Exception("签名数据长度不足,无法拆分IV和标签");
          }
    
          // 3. 拆分 IV、密文、标签
          $iv = substr($data, 0, 12);                     // 12字节IV
          $ciphertextWithTag = substr($data, 12);        // 剩余部分 = 密文 + 标签
          $tag = substr($ciphertextWithTag, -16);        // 最后16字节是标签
          $ciphertext = substr($ciphertextWithTag, 0, -16); // 前面的部分为密文
    
          // 4. 创建SM4实例并解密
          $sm4 = new \Sm4\Sm4();
          $sm4->setKey($key);
    
          // php-sm4 扩展的 GCM 解密方法:decryptGcm($ciphertext, $iv, $tag, $aad = '')
          $plaintext = $sm4->decryptGcm($ciphertext, $iv, $tag);
          if ($plaintext === false) {
              throw new Exception("SM4-GCM解密失败,可能密钥错误或数据被篡改");
          }
    
          return $plaintext;
      }

Node.js

  • AES-256-GCM 示例

    javascript
      const crypto = require('crypto');
    
      function decryptAESGCM(encryptedData, base64Key) {
          const key = Buffer.from(base64Key, 'base64');
          if (key.length !== 32) throw new Error('AES-256 密钥长度必须为 32 字节');
          const data = Buffer.from(encryptedData, 'base64');
          const iv = data.subarray(0, 12);
          const ciphertext = data.subarray(12, -16);
          const tag = data.subarray(-16);
          const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
          decipher.setAuthTag(tag);
          const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
          return plaintext.toString('utf8');
      }
  • SM4-GCM 示例(需 sm-crypto 库)

  • 依赖npm install sm-crypto

    javascript
      const sm4 = require('sm-crypto').sm4;
      const crypto = require('crypto'); // 仅用于随机IV,解密无需
    
      function decryptSM4GCM(encryptedData, base64Key) {
          // 1. 解码密钥
          const key = Buffer.from(base64Key, 'base64');
          if (key.length !== 16) {
              throw new Error('SM4 密钥必须是16字节的Base64字符串');
          }
    
          // 2. 解码签名数据
          const data = Buffer.from(encryptedData, 'base64');
          if (data.length < 12 + 16) {
              throw new Error('签名数据长度不足,无法拆分IV和标签');
          }
    
          // 3. 拆分 IV、密文、标签
          const iv = data.subarray(0, 12);                 // 12字节IV
          const ciphertextWithTag = data.subarray(12);     // 剩余部分 = 密文 + 标签
          const tag = ciphertextWithTag.subarray(-16);     // 最后16字节是标签
          const ciphertext = ciphertextWithTag.subarray(0, -16); // 前面的部分为密文
    
          // 4. 调用 sm-crypto 的 GCM 解密
          // 注意:sm4.gcm.decrypt 需要传入 hex 字符串或 Buffer?根据文档,支持 Buffer/ArrayBuffer
          // 这里直接传入 Buffer,返回 Buffer 或 string(需查看实际返回)
          // 确保参数顺序:sm4.gcm.decrypt(ciphertext, key, iv, tag, aad?)
          const plaintext = sm4.gcm.decrypt(ciphertext, key, iv, tag);
          // 如果返回的是 Buffer,转换为字符串
          return Buffer.isBuffer(plaintext) ? plaintext.toString('utf8') : plaintext;
      }

1.4 常见问题排查

解密失败常见原因

  1. 密钥不匹配:解密使用的密钥与加密时不一致,或算法类型错误。
  2. 数据格式错误:Base64 解码失败,或数据长度不足 12+16 字节。
  3. 认证标签错误:数据被篡改、标签提取位置错误或 GCM 认证失败。
  4. IV 长度不一致:未按 12 字节拆分。
  5. 跨语言兼容性问题:拼接顺序必须严格为 IV + 密文 + 标签。