Nodejs示例代码
1. 核心安全组件 (sudSecurityUtil.js)
javascript
const crypto = require('crypto');
/**
* SUD OpenPaaS API 安全通信核心工具类
*/
class SudSecurityUtil {
/**
* 1. 请求业务数据加密 (AES-256-GCM)
*/
static encryptRequest(urlPath, appId, keySn, symKeyBase64, reqData) {
const timestamp = Math.floor(Date.now() / 1000);
// base64url 编码天生不包含等号 '=',且符合 URL 安全标准
const nonce = crypto.randomBytes(16).toString('base64url');
// 补齐安全校验字段
const payloadObj = {
_n: nonce,
_appid: appId,
_timestamp: timestamp,
...reqData
};
const plaintext = JSON.stringify(payloadObj);
// 拼接 AAD: urlpath|appid|timestamp|sn
const aad = `${urlPath}|${appId}|${timestamp}|${keySn}`;
const realKey = Buffer.from(symKeyBase64, 'base64');
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv('aes-256-gcm', realKey, iv);
cipher.setAAD(Buffer.from(aad, 'utf-8'));
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
const authTag = cipher.getAuthTag();
const httpBody = {
iv: iv.toString('base64'),
data: ciphertext.toString('base64'),
authtag: authTag.toString('base64')
};
return {
reqTs: timestamp,
reqDataJSON: JSON.stringify(httpBody)
};
}
/**
* 2. 请求报文签名 (RSA-PSS)
*/
static signRequest(urlPath, appId, timestamp, reqDataJSON, privateKeyPem) {
// 严格按顺序拼接
const payload = `${urlPath}\n${appId}\n${timestamp}\n${reqDataJSON}`;
const sign = crypto.createSign('RSA-SHA256');
sign.update(payload, 'utf-8');
sign.end();
// PSS 模式,salt 长度需对齐 SHA256(32字节)
return sign.sign({
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
}, 'base64');
}
/**
* 3. 响应报文验签 (RSA-PSS)
*/
static verifyResponseSignature(urlPath, expectedAppId, localCertSn, platformCertPem,
respAppId, respTs, respSn, respSig,
respDeprecatedSn, respDeprecatedSig, respDataJSON) {
// 基础安全校验
if (expectedAppId !== respAppId) {
throw new Error("安全校验失败:响应的 AppId 与本地不匹配");
}
// 证书序列号匹配与平滑轮换支持
let signatureBase64 = '';
if (localCertSn === respSn) {
signatureBase64 = respSig;
} else if (respDeprecatedSn && localCertSn === respDeprecatedSn) {
signatureBase64 = respDeprecatedSig || respSig;
} else {
throw new Error(`验签失败:本地证书编号(${localCertSn})与平台当前编号(${respSn})不匹配`);
}
const payload = `${urlPath}\n${respAppId}\n${respTs}\n${respDataJSON}`;
const verify = crypto.createVerify('RSA-SHA256');
verify.update(payload, 'utf-8');
verify.end();
return verify.verify({
key: platformCertPem,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST
}, signatureBase64, 'base64');
}
/**
* 4. 响应密文解密 (AES-256-GCM)
*/
static decryptResponse(urlPath, appId, respTs, keySn, symKeyBase64, respDataJSON) {
const encBody = JSON.parse(respDataJSON);
const iv = Buffer.from(encBody.iv, 'base64');
const data = Buffer.from(encBody.data, 'base64');
const authTag = Buffer.from(encBody.authtag, 'base64');
const aad = `${urlPath}|${appId}|${respTs}|${keySn}`;
const realKey = Buffer.from(symKeyBase64, 'base64');
const decipher = crypto.createDecipheriv('aes-256-gcm', realKey, iv);
decipher.setAAD(Buffer.from(aad, 'utf-8'));
decipher.setAuthTag(authTag);
try {
// final() 阶段会自动验证 AuthTag 的合法性
const plaintext = Buffer.concat([decipher.update(data), decipher.final()]);
const realResp = JSON.parse(plaintext.toString('utf-8'));
// 内部安全字段强校验
if (realResp._appid !== appId || realResp._timestamp !== respTs) {
throw new Error("安全字段校验失败:响应明文内部的 _appid 或 _timestamp 被非法篡改");
}
return realResp;
} catch (error) {
throw new Error(`解密失败: AuthTag 验证未通过或明文解析异常 - ${error.message}`);
}
}
}
module.exports = SudSecurityUtil;2. 业务调用演示 (demo.js)
javascript
const SudSecurityUtil = require('./sudSecurityUtil');
// 模拟应用全局配置
const CONFIG = {
appId: "cp_10010",
urlPath: "https://api.sud.com/v1/game/login",
symKeySn: "key_v1",
symKey: "otUpngOjU+nVQaWJIC3D/yMLV17RKaP6t4Ot9tbnzLY=",
privateKeyPem: "-----BEGIN PRIVATE KEY-----\nMIIEvQIB... (应用私钥) ...W6w=\n-----END PRIVATE KEY-----",
platformCertSn: "sud_cert_v1",
platformCertPem: "-----BEGIN CERTIFICATE-----\nMIID0jCC... (SUD平台公钥证书) ...Q==\n-----END CERTIFICATE-----"
};
function main() {
try {
console.log("=== 1. 发起请求:业务数据加密与签名 ===");
// 原始业务请求参数
const bizReq = {
appid: CONFIG.appId,
sud_uid: "user_889900",
scene: 0,
client_ip: "127.0.0.1"
};
// 步骤 A:加密
const { reqTs, reqDataJSON } = SudSecurityUtil.encryptRequest(
CONFIG.urlPath, CONFIG.appId, CONFIG.symKeySn, CONFIG.symKey, bizReq
);
// 步骤 B:签名
const reqSig = SudSecurityUtil.signRequest(
CONFIG.urlPath, CONFIG.appId, reqTs, reqDataJSON, CONFIG.privateKeyPem
);
console.log("HTTP Header X-Sud-Timestamp:", reqTs);
console.log("HTTP Header X-Sud-Signature:", reqSig);
console.log("HTTP Body:", reqDataJSON);
console.log("\n=== 2. 收到响应:签名验证与密文解密 ===");
// 模拟从 HTTP 响应中获取的数据
const respTs = reqTs;
const respHeaderAppId = CONFIG.appId;
const respHeaderSn = CONFIG.platformCertSn;
const respHeaderSig = reqSig; // 演示用,实际为接收的签名
const respBodyJSON = `{"iv":"r2WDQt56rEAmMuoR","data":"HExs66...","authtag":"z2BF..."}`;
// 步骤 C:验签
const isSigValid = SudSecurityUtil.verifyResponseSignature(
CONFIG.urlPath, CONFIG.appId, CONFIG.platformCertSn, CONFIG.platformCertPem,
respHeaderAppId, respTs, respHeaderSn, respHeaderSig, null, null, respBodyJSON
);
if (isSigValid) {
console.log("响应验签成功!");
// 步骤 D:解密
const decryptedBizData = SudSecurityUtil.decryptResponse(
CONFIG.urlPath, CONFIG.appId, respTs, CONFIG.symKeySn, CONFIG.symKey, respBodyJSON
);
console.log("响应解密成功,业务数据:", decryptedBizData);
}
} catch (error) {
console.error("流程中断:", error.message);
}
}
main();