Skip to content

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();