PHP示例代码
1. 核心安全组件 (SudSecurityUtil.php)
php
<?php
namespace Sud\Security;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
use Exception;
use RuntimeException;
class SudSecurityUtil
{
/**
* 1. 请求业务数据加密 (AES-256-GCM)
*
* @param string $urlPath 包含域名的全路径
* @param string $appId 平台应用 AppId
* @param string $keySn 对称密钥编号
* @param string $symKeyBase64 对称密钥 Base64 字符串
* @param array $reqData 原始业务请求数据数组
* @return array 包含请求时间戳 (req_ts) 和 JSON 密文 (req_data) 的关联数组
*/
public static function encryptRequest(string $urlPath, string $appId, string $keySn, string $symKeyBase64, array $reqData): array
{
$timestamp = time();
// 生成 16 字节随机数,并转为 URL 安全的 Base64(无等号填充)
$nonceBytes = random_bytes(16);
$nonce = rtrim(strtr(base64_encode($nonceBytes), '+/', '-_'), '=');
// 补齐安全校验字段
$reqData['_n'] = $nonce;
$reqData['_appid'] = $appId;
$reqData['_timestamp'] = $timestamp;
// 使用 JSON_UNESCAPED_UNICODE 和 JSON_UNESCAPED_SLASHES 保证格式紧凑且不转义中文/斜杠
$plaintext = json_encode($reqData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
// 拼接 AAD: urlpath|appid|timestamp|sn
$aad = "{$urlPath}|{$appId}|{$timestamp}|{$keySn}";
$realKey = base64_decode($symKeyBase64);
$iv = random_bytes(12); // GCM 推荐 12 字节 IV
$authTag = ''; // 将由 openssl_encrypt 引用赋值
// 执行 AES-256-GCM 加密
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$realKey,
OPENSSL_RAW_DATA,
$iv,
$authTag,
$aad,
16
);
if ($ciphertext === false) {
throw new RuntimeException("AES-GCM 加密失败");
}
$httpBody = [
'iv' => base64_encode($iv),
'data' => base64_encode($ciphertext),
'authtag' => base64_encode($authTag)
];
return [
'req_ts' => $timestamp,
'req_data' => json_encode($httpBody, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
];
}
/**
* 2. 请求报文签名 (RSA-PSS)
*/
public static function signRequest(string $urlPath, string $appId, int $timestamp, string $reqDataJson, string $privateKeyPem): string
{
// 严格按顺序拼接
$payload = "{$urlPath}\n{$appId}\n{$timestamp}\n{$reqDataJson}";
// 加载私钥(phpseclib 自动兼容 PKCS#1 和 PKCS#8 格式)
$privateKey = PublicKeyLoader::load($privateKeyPem)
->withHash('sha256')
->withMGFHash('sha256')
->withSaltLength(32) // PSS 盐长度需对齐 SHA256 (32字节)
->withPadding(RSA::SIGNATURE_PSS);
$signature = $privateKey->sign($payload);
return base64_encode($signature);
}
/**
* 3. 响应报文验签 (RSA-PSS)
*/
public static function verifyResponseSignature(
string $urlPath, string $expectedAppId, string $localCertSn, string $platformCertPem,
string $respAppId, int $respTs, string $respSn, string $respSig,
?string $respDeprecatedSn, ?string $respDeprecatedSig, string $respDataJson
): bool {
// 基础安全校验
if ($expectedAppId !== $respAppId) {
throw new Exception("安全校验失败:响应的 AppId 与本地不匹配");
}
// 证书序列号匹配与平滑轮换支持
$signatureBase64 = '';
if ($localCertSn === $respSn) {
$signatureBase64 = $respSig;
} elseif (!empty($respDeprecatedSn) && $localCertSn === $respDeprecatedSn) {
$signatureBase64 = (!empty($respDeprecatedSig)) ? $respDeprecatedSig : $respSig;
} else {
throw new Exception("验签失败:本地证书编号({$localCertSn})与平台当前编号({$respSn})不匹配");
}
$payload = "{$urlPath}\n{$respAppId}\n{$respTs}\n{$respDataJson}";
$signatureBytes = base64_decode($signatureBase64);
// 加载平台公钥证书
$publicKey = PublicKeyLoader::load($platformCertPem)
->withHash('sha256')
->withMGFHash('sha256')
->withSaltLength(32)
->withPadding(RSA::SIGNATURE_PSS);
return $publicKey->verify($payload, $signatureBytes);
}
/**
* 4. 响应密文解密 (AES-256-GCM)
*/
public static function decryptResponse(string $urlPath, string $appId, int $respTs, string $keySn, string $symKeyBase64, string $respDataJson): array
{
$encBody = json_decode($respDataJson, true);
if (!$encBody || !isset($encBody['iv'], $encBody['data'], $encBody['authtag'])) {
throw new Exception("密文 JSON 格式非法");
}
$iv = base64_decode($encBody['iv']);
$ciphertext = base64_decode($encBody['data']);
$authTag = base64_decode($encBody['authtag']);
$aad = "{$urlPath}|{$appId}|{$respTs}|{$keySn}";
$realKey = base64_decode($symKeyBase64);
// 执行解密(如果 AuthTag 验证失败,或者密文被篡改,将返回 false)
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$realKey,
OPENSSL_RAW_DATA,
$iv,
$authTag,
$aad
);
if ($plaintext === false) {
throw new Exception("解密失败:AuthTag 验证未通过或密文被篡改");
}
$realResp = json_decode($plaintext, true);
// 内部安全字段强校验
if (!isset($realResp['_appid'], $realResp['_timestamp']) ||
$realResp['_appid'] !== $appId ||
(int)$realResp['_timestamp'] !== $respTs) {
throw new Exception("安全字段校验失败:响应明文内部的 _appid 或 _timestamp 被非法篡改");
}
// 联调时可解开此注释进行防重放校验
// if (abs(time() - (int)$realResp['_timestamp']) > 300) {
// throw new Exception("安全校验失败:响应数据距离当前时间误差超过 5 分钟");
// }
return $realResp;
}
}2. 业务调用演示 (demo.php)
php
<?php
require __DIR__ . '/vendor/autoload.php';
use Sud\Security\SudSecurityUtil;
// 模拟应用全局配置
$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-----\nMIID0j... (SUD平台公钥证书) ...Q==\n-----END CERTIFICATE-----"
];
try {
echo "=== 1. 发起请求:业务数据加密与签名 ===\n";
// 原始业务请求参数
$bizReq = [
'appid' => $config['appId'],
'sud_uid' => 'user_889900',
'scene' => 0,
'client_ip' => '127.0.0.1'
];
// 步骤 A:加密
$encResult = SudSecurityUtil::encryptRequest(
$config['urlPath'], $config['appId'], $config['symKeySn'], $config['symKey'], $bizReq
);
$reqTs = $encResult['req_ts'];
$reqDataJson = $encResult['req_data'];
// 步骤 B:签名
$reqSig = SudSecurityUtil::signRequest(
$config['urlPath'], $config['appId'], $reqTs, $reqDataJson, $config['privateKeyPem']
);
echo "HTTP Header X-Sud-Timestamp: {$reqTs}\n";
echo "HTTP Header X-Sud-Signature: {$reqSig}\n";
echo "HTTP Body: {$reqDataJson}\n";
echo "\n=== 2. 收到响应:签名验证与密文解密 ===\n";
// 模拟从 HTTP 响应中获取的数据
$respTs = $reqTs;
$respHeaderAppId = $config['appId'];
$respHeaderSn = $config['platformCertSn'];
$respHeaderSig = $reqSig; // 演示用,实际为接收的签名
$respBodyJson = '{"iv":"r2WDQt56rEAmMuoR","data":"HExs...","authtag":"z2BF..."}';
// 步骤 C:验签
$isSigValid = SudSecurityUtil::verifyResponseSignature(
$config['urlPath'], $config['appId'], $config['platformCertSn'], $config['platformCertPem'],
$respHeaderAppId, $respTs, $respHeaderSn, $respHeaderSig,
null, null, $respBodyJson
);
if ($isSigValid) {
echo "响应验签成功!\n";
// 步骤 D:解密
$decryptedBizData = SudSecurityUtil::decryptResponse(
$config['urlPath'], $config['appId'], $respTs, $config['symKeySn'], $config['symKey'], $respBodyJson
);
echo "响应解密成功,业务数据:\n";
print_r($decryptedBizData);
}
} catch (Exception $e) {
echo "流程中断: " . $e->getMessage() . "\n";
}