Skip to content

接口加密

加密注解 @MssSafety

开启请求验签

  • 对于标注了 @MssSafety 注解的接口,(decryptRequest=true)请求参数需要进行加密。

开启响应加密

  • 对于标注了 @MssSafety 注解的接口,(encryptResponse=true)请求参数需要进行加密。

使用语法

  • 注解:@MssSafety
参数备注
isRepetitiontrue / false默认:true
decryptRequesttrue / false默认:false
encryptResponsetrue / false默认:false
encryptTypeAES / RAS默认:AES

使用案例

  1. 后端 接口开启请求解密,响应加密注解
java
@MssSafety(decryptRequest = true,encryptResponse= true)

  1. 前端接口开启加密

请求头添加一下标识

text
headers: {
    'Encrypt-Type': EncryptTypeEnum.AES
}

使用示例

  • 前端请求报文进行加密

  • 接口响应数据进行加密

注意事项

  • 前段请求加密只支持POSTPUT
  • 如果前端开启了请求加密(请求头需要添加下面代码),后端接口也要同时开始请求解密
text
headers: {
    'Encrypt-Type': EncryptTypeEnum.AES
}
java
@MssSafety(decryptRequest = true)
  • 如果后端的接口进行了加密处理,响应给前端,前端将自动识别到加密数据进行解密。

第三方接入 Demo(AES / RSA)

以下示例对应 后端接口使用 @MssSafety(decryptRequest = true) 的接口

例如 POST /v1/openapi/getGameList(RSA)与内部业务接口(AES)。

1) 获取 Token 与密钥

POST /v1/openapi/getToken(无需加签)返回:

  • token(后续放在请求头 Authorization
  • appId(16位,作为 AES IV)
  • secretKey(AES密钥,16位)
  • publicKey(RSA 公钥,服务端使用私钥验签)

2) 通用请求头

  • Content-Type: application/json
  • Authorization: <token>
  • App-Id: <appId>
  • Encrypt-Type: AESEncrypt-Type: RSA
  • X-Sign: <sign> 签名(也可以放在 body 中)
  • X-Timestamp: <timestamp> 时间戳(也可以放在 body 中)

decryptRequest = true 时,X-SignX-Timestamp 可不放在 header, 因为会从请求体 ApiSecurityParam 中读取并写入。

3) AES(JS,适用于浏览器/前端)

签名规则(必须与后端一致):

  1. data 进行参数排序(ASCII 升序)
  2. 过滤空值
  3. 拼接为 key=value&...,并对 key/value 做 encodeURIComponent
  4. 使用 AES/CBC/ZeroPadding 加密该字符串
  5. Base64 输出作为 sign

** 加签 JS Demo(crypto-js)**

typescript
import { AES, enc, mode, pad } from 'crypto-js';

const appId = '16位appId';
const secretKey = '16位secretKey';
const token = '登录接口返回的token';

const data = { pageNum: 1, pageSize: 10, userName: '' };

const paramsSort = (obj: Record<string, any>) => {
  const keys = Object.keys(obj).sort();
  const res: Record<string, any> = {};
  keys.forEach((k) => (res[k] = obj[k]));
  return res;
};

const tansParams = (params: Record<string, any>) => {
  params = paramsSort(params);
  let result = '';
  for (const propName of Object.keys(params)) {
    const value = params[propName];
    const part = encodeURIComponent(propName) + '=';
    if (value !== null && value !== '' && typeof value !== 'undefined') {
      if (typeof value === 'object') {
        for (const key of Object.keys(value)) {
          if (value[key] !== null && value[key] !== '' && typeof value[key] !== 'undefined') {
            const p = propName + '[' + key + ']';
            result += encodeURIComponent(p) + '=' + encodeURIComponent(value[key]) + '&';
          }
        }
      } else {
        result += part + encodeURIComponent(value) + '&';
      }
    }
  }
  return result.endsWith('&') ? result.slice(0, -1) : result;
};

const dataStr = tansParams(data);
const iv = enc.Utf8.parse(appId);
const key = enc.Utf8.parse(secretKey);
const encrypted = AES.encrypt(enc.Utf8.parse(dataStr), key, {
  iv,
  mode: mode.CBC,
  padding: pad.ZeroPadding,
});
const sign = enc.Base64.stringify(encrypted.ciphertext);

const body = {
  appId,
  sign,
  timestamp: Date.now(),
  data,
};

fetch('/v1/openapi/getGameList', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: token,
    'App-Id': appId,
    'Encrypt-Type': 'AES',
  },
  body: JSON.stringify(body),
});

4) RSA(服务端请求:Java / PHP / Python / C#)

签名规则

  1. data 转成 JSON 字符串(保持字段顺序与实际序列化一致)
  2. 使用 公钥 做 RSA 加密(PKCS1 Padding)
  3. 将密文输出为 十六进制字符串 作为 sign

Java Demo(Hutool)

java
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.core.util.HexUtil;
import com.alibaba.fastjson.JSON;

String appId = "16位appId";
String publicKey = "获取Token接口返回的publicKey";
String token = "登录接口返回的token";

Map<String, Object> data = new HashMap<>();
data.put("pageNum", 1);
data.put("pageSize", 10);
data.put("userName", "");

String dataStr = JSON.toJSONString(data);
RSA rsa = new RSA(null, publicKey);
byte[] encrypted = rsa.encrypt(dataStr.getBytes(StandardCharsets.UTF_8), KeyType.PublicKey);
String sign = HexUtil.encodeHexStr(encrypted).toUpperCase();

Map<String, Object> body = new HashMap<>();
body.put("appId", appId);
body.put("sign", sign);
body.put("timestamp", System.currentTimeMillis());
body.put("data", dataStr);

Python Demo(PyCryptodome)

python
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
import binascii, json, time

app_id = "16位appId"
public_key = """-----BEGIN PUBLIC KEY-----
...your key...
-----END PUBLIC KEY-----"""

data = {"pageNum": 1, "pageSize": 10, "userName": ""}
data_str = json.dumps(data, separators=(',', ':'), ensure_ascii=False)

key = RSA.import_key(public_key)
cipher = PKCS1_v1_5.new(key)
encrypted = cipher.encrypt(data_str.encode('utf-8'))
sign = binascii.hexlify(encrypted).decode('utf-8').upper()

body = {
  "appId": app_id,
  "sign": sign,
  "timestamp": int(time.time() * 1000),
  "data": data_str
}

PHP Demo(openssl)

php
<?php
$appId = "16位appId";
$publicKey = "-----BEGIN PUBLIC KEY-----\n...your key...\n-----END PUBLIC KEY-----";

$data = [
  "pageNum" => 1,
  "pageSize" => 10,
  "userName" => ""
];
$dataStr = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

$pubKey = openssl_pkey_get_public($publicKey);
openssl_public_encrypt($dataStr, $encrypted, $pubKey, OPENSSL_PKCS1_PADDING);
$sign = strtoupper(bin2hex($encrypted));

$body = [
  "appId" => $appId,
  "sign" => $sign,
  "timestamp" => round(microtime(true) * 1000),
  "data" => $dataStr
];

C# Demo(RSA)

csharp
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;

string appId = "16位appId";
string publicKeyPem = @"-----BEGIN PUBLIC KEY-----
...your key...
-----END PUBLIC KEY-----";

var data = new Dictionary<string, object> {
  { "pageNum", 1 },
  { "pageSize", 10 },
  { "userName", "" }
};
string dataStr = JsonConvert.SerializeObject(data);

using var rsa = RSA.Create();
rsa.ImportFromPem(publicKeyPem.ToCharArray());
byte[] encrypted = rsa.Encrypt(Encoding.UTF8.GetBytes(dataStr), RSAEncryptionPadding.Pkcs1);
string sign = BitConverter.ToString(encrypted).Replace("-", "").ToUpperInvariant();

var body = new Dictionary<string, object> {
  { "appId", appId },
  { "sign", sign },
  { "timestamp", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() },
  { "data", dataStr }
};

5) 绑定到具体接口

GameApiController#getGameList 为例:

  • 注解:@MssSafety(decryptRequest = true, encryptType = SafetyTypeEnum.RSA)
  • Header:Encrypt-Type: RSA
  • Body:{appId, sign, timestamp, data}data 为 JSON 字符串)

Released under the MIT License.