Skip to content

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