Java示例代码
1. 核心安全组件 (SudApiSecurityUtil.java)
java
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.PSSParameterSpec;
import java.util.Arrays;
import java.util.Base64;
/**
* SUD OpenPaaS API 安全通信核心工具类
*/
public class SudApiSecurityUtil {
private static final String AES_ALGORITHM = "AES/GCM/NoPadding";
private static final String RSA_PSS_ALGORITHM = "RSASSA-PSS";
private static final int GCM_TAG_LENGTH_BIT = 128;
private static final int GCM_IV_LENGTH_BYTE = 12;
/**
* 1. 请求业务数据加密 (AES-256-GCM)
*/
public static JsonObject encryptRequest(String urlPath, String appId, String keySn, String symmetricKeyBase64, JsonObject reqData) throws Exception {
long timestamp = System.currentTimeMillis() / 1000;
String nonce = generateNonce();
// 补齐安全校验字段
reqData.addProperty("_n", nonce);
reqData.addProperty("_appid", appId);
reqData.addProperty("_timestamp", timestamp);
String plaintext = reqData.toString();
// 拼接 AAD: urlpath|appid|timestamp|sn
String aad = String.join("|", urlPath, appId, String.valueOf(timestamp), keySn);
byte[] keyBytes = Base64.getDecoder().decode(symmetricKeyBase64);
byte[] ivBytes = generateRandomBytes(GCM_IV_LENGTH_BYTE);
byte[] aadBytes = aad.getBytes(StandardCharsets.UTF_8);
byte[] plaintextBytes = plaintext.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH_BIT, ivBytes);
cipher.init(Cipher.ENCRYPT_MODE, keySpec, parameterSpec);
cipher.updateAAD(aadBytes);
// Java doFinal 会输出 cipherText + authTag
byte[] ciphertextWithTag = cipher.doFinal(plaintextBytes);
// 分离密文与 AuthTag (后16字节)
int tagLength = GCM_TAG_LENGTH_BIT / 8;
byte[] ciphertext = Arrays.copyOfRange(ciphertextWithTag, 0, ciphertextWithTag.length - tagLength);
byte[] authTag = Arrays.copyOfRange(ciphertextWithTag, ciphertextWithTag.length - tagLength, ciphertextWithTag.length);
// 组装最终请求 Body
JsonObject httpBody = new JsonObject();
httpBody.addProperty("iv", Base64.getEncoder().encodeToString(ivBytes));
httpBody.addProperty("data", Base64.getEncoder().encodeToString(ciphertext));
httpBody.addProperty("authtag", Base64.getEncoder().encodeToString(authTag));
// 返回包含 HTTP Header 需用到的时间戳及 Body 密文对象
JsonObject result = new JsonObject();
result.addProperty("req_ts", timestamp);
result.addProperty("req_data", httpBody.toString());
return result;
}
/**
* 2. 请求报文签名 (RSA-PSS)
*/
public static String signRequest(String urlPath, String appId, long timestamp, String reqData, String privateKeyPem) throws Exception {
// 严格按顺序拼接,String.join 保证了末尾绝对不会产生多余的 \n
String payload = String.join("\n", urlPath, appId, String.valueOf(timestamp), reqData);
byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8);
// 解析 PKCS#8 格式私钥
String privateKeyBase64 = privateKeyPem
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
byte[] decodedKey = Base64.getDecoder().decode(privateKeyBase64);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
RSAPrivateKey privateKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(keySpec);
// 配置 PSS 签名参数 (Salt 长度为 32,对齐 SHA256)
Signature signature = Signature.getInstance(RSA_PSS_ALGORITHM);
PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
signature.setParameter(pssSpec);
signature.initSign(privateKey);
signature.update(dataBuffer);
byte[] sigBuffer = signature.sign();
return Base64.getEncoder().encodeToString(sigBuffer);
}
/**
* 3. 响应报文验签 (RSA-PSS)
*/
public static boolean verifyResponseSignature(String urlPath, String expectedAppId, String localCertSn, String platformCertPem,
String respAppId, long respTs, String respSn, String respSig,
String respDeprecatedSn, String respDeprecatedSig, String respData) throws Exception {
// 基础安全校验:AppId 匹配
if (!expectedAppId.equals(respAppId)) {
return false;
}
// 证书序列号匹配与平滑轮换支持
String signatureBase64;
if (localCertSn.equals(respSn)) {
signatureBase64 = respSig;
} else if (respDeprecatedSn != null && localCertSn.equals(respDeprecatedSn)) {
signatureBase64 = (respDeprecatedSig != null && !respDeprecatedSig.isEmpty()) ? respDeprecatedSig : respSig;
} else {
return false; // 证书编号不匹配
}
// 待验签串拼接
String payload = String.join("\n", urlPath, respAppId, String.valueOf(respTs), respData);
byte[] dataBuffer = payload.getBytes(StandardCharsets.UTF_8);
// 解析 X.509 证书
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
ByteArrayInputStream inputStream = new ByteArrayInputStream(Base64.getDecoder().decode(platformCertPem));
X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(inputStream);
// 配置 PSS 验签参数
Signature verifier = Signature.getInstance(RSA_PSS_ALGORITHM);
PSSParameterSpec pssSpec = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1);
verifier.setParameter(pssSpec);
verifier.initVerify(certificate);
verifier.update(dataBuffer);
byte[] sigBuffer = Base64.getDecoder().decode(signatureBase64);
return verifier.verify(sigBuffer);
}
/**
* 4. 响应密文解密 (AES-256-GCM)
*/
public static JsonObject decryptResponse(String urlPath, String appId, long respTs, String keySn, String symmetricKeyBase64, String respDataStr) throws Exception {
JsonObject respDataObj = JsonParser.parseString(respDataStr).getAsJsonObject();
String iv = respDataObj.get("iv").getAsString();
String data = respDataObj.get("data").getAsString();
String authTag = respDataObj.get("authtag").getAsString();
// 拼接 AAD
String aad = String.join("|", urlPath, appId, String.valueOf(respTs), keySn);
byte[] dataBytes = Base64.getDecoder().decode(data);
byte[] authTagBytes = Base64.getDecoder().decode(authTag);
// 拼接密文和 AuthTag 供 Cipher 处理
byte[] combinedData = new byte[dataBytes.length + authTagBytes.length];
System.arraycopy(dataBytes, 0, combinedData, 0, dataBytes.length);
System.arraycopy(authTagBytes, 0, combinedData, dataBytes.length, authTagBytes.length);
byte[] keyBytes = Base64.getDecoder().decode(symmetricKeyBase64);
byte[] ivBytes = Base64.getDecoder().decode(iv);
byte[] aadBytes = aad.getBytes(StandardCharsets.UTF_8);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH_BIT, ivBytes);
cipher.init(Cipher.DECRYPT_MODE, keySpec, parameterSpec);
cipher.updateAAD(aadBytes);
byte[] decryptedBytes = cipher.doFinal(combinedData); // 内部自动验证 AuthTag
String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8);
JsonObject realResp = JsonParser.parseString(decryptedData).getAsJsonObject();
// 强校验明文内部的安全字段
if (!appId.equals(realResp.get("_appid").getAsString()) || respTs != realResp.get("_timestamp").getAsLong()) {
throw new SecurityException("安全字段校验失败:响应明文内部的 _appid 或 _timestamp 与外部不匹配");
}
return realResp;
}
/* ================= 辅助方法 ================= */
private static String generateNonce() {
byte[] nonce = generateRandomBytes(16);
return Base64.getEncoder().encodeToString(nonce).replace("=", "");
}
private static byte[] generateRandomBytes(int length) {
byte[] bytes = new byte[length];
new SecureRandom().nextBytes(bytes);
return bytes;
}
}2. 业务调用演示 (SudApiDemo.java)
java
import com.google.gson.JsonObject;
public class SudApiDemo {
// 模拟应用全局配置
private static final String APP_ID = "cp_10010";
private static final String URL_PATH = "https://api.sud.com/v1/game/login";
private static final String SYM_KEY_SN = "key_v1";
private static final String SYM_KEY = "otUpngOjU+nVQaWJIC3D/yMLV17RKaP6t4Ot9tbnzLY=";
private static final String PRIVATE_KEY_PEM = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANB... (你的完整私钥) ...V6W6w=\n-----END PRIVATE KEY-----";
private static final String PLATFORM_CERT_SN = "sud_cert_v1";
private static final String PLATFORM_CERT_PEM = "-----BEGIN CERTIFICATE-----\nMIID0jCCArqgAw... (SUD平台公钥证书) ...Q==\n-----END CERTIFICATE-----";
public static void main(String[] args) {
try {
System.out.println("=== 1. 发起请求:业务数据加密与签名 ===");
// 准备原始业务请求参数
JsonObject bizReq = new JsonObject();
bizReq.addProperty("appid", APP_ID);
bizReq.addProperty("sud_uid", "user_889900");
bizReq.addProperty("scene", 0);
// 执行加密
JsonObject encResult = SudApiSecurityUtil.encryptRequest(URL_PATH, APP_ID, SYM_KEY_SN, SYM_KEY, bizReq);
long reqTimestamp = encResult.get("req_ts").getAsLong();
String reqBodyData = encResult.get("req_data").getAsString();
// 执行签名
String reqSignature = SudApiSecurityUtil.signRequest(URL_PATH, APP_ID, reqTimestamp, reqBodyData, PRIVATE_KEY_PEM);
System.out.println("HTTP Header X-Sud-Timestamp: " + reqTimestamp);
System.out.println("HTTP Header X-Sud-Signature: " + reqSignature);
System.out.println("HTTP Body: " + reqBodyData);
System.out.println("\n=== 2. 收到响应:签名验证与密文解密 ===");
// 模拟从 HTTP 响应拦截器中获取到的 Header 与 Body
long respTimestamp = reqTimestamp; // 理想情况下响应时间戳与请求相近
String respHeaderAppId = APP_ID;
String respHeaderSn = PLATFORM_CERT_SN;
String respHeaderSig = "Ht0VfQkkEweJ4hU266C14... (假定的签名串)";
String respBodyData = "{\"iv\":\"r2WDQt56rEAmMuoR\",\"data\":\"HExs...\",\"authtag\":\"z2BF...\"}";
// 执行响应验签
boolean isSigValid = SudApiSecurityUtil.verifyResponseSignature(
URL_PATH, APP_ID, PLATFORM_CERT_SN, PLATFORM_CERT_PEM,
respHeaderAppId, respTimestamp, respHeaderSn, respHeaderSig,
null, null, respBodyData
);
if (isSigValid) {
System.out.println("响应验签成功!");
// 执行解密
JsonObject decryptedBizData = SudApiSecurityUtil.decryptResponse(URL_PATH, APP_ID, respTimestamp, SYM_KEY_SN, SYM_KEY, respBodyData);
System.out.println("响应解密成功,业务数据:" + decryptedBizData.toString());
} else {
System.err.println("响应验签失败,丢弃该报文!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}