登录
原创

RSA+AES 组合加密身份证四要素接口示例代码

发布于 2025-12-02 阅读 39
  • 后端
原创
package cn.juhe;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;

/**
 * 身份证四要素 v8 - SHA512 版本客户端
 * 
 * 接口文档:https://www.juhe.cn/docs/api/id/550
 * 
 * 加密规范:
 * - RSA密钥长度:2048位
 * - RSA加密方式:RSA/ECB/OAEPWithSHA-512AndMGF1Padding
 * - AES加密方式:AES/GCM/NoPadding
 * - 签名方式:HmacSHA512
 */
public class IdCardFourFactorsSHA512Client {
    
    private static final String API_URL = "https://apis.juhe.cn/id_card_four_factors/queryhwsha512";
    private static final int AES_KEY_LENGTH = 16; // 16字节 = 128位
    private static final int AES_IV_LENGTH = 12;  // 12字节用于GCM模式
    private static final int GCM_TAG_LENGTH = 128; // 128位标签
    
    private String apiKey;
    private String openId;
    private PublicKey rsaPublicKey;
    private byte[] aesKey;
    private byte[] aesIv;
    
    /**
     * 构造函数
     * 
     * @param apiKey 聚合数据分配的 API Key
     * @param openId 聚合数据分配的 OpenId
     * @param publicKeyPath RSA公钥文件路径(PEM格式)
     */
    public IdCardFourFactorsSHA512Client(String apiKey, String openId, String publicKeyPath) throws Exception {
        this.apiKey = apiKey;
        this.openId = openId;
        this.rsaPublicKey = loadPublicKey(publicKeyPath);
        
        // 生成随机的AES密钥和IV
        SecureRandom random = new SecureRandom();
        this.aesKey = new byte[AES_KEY_LENGTH];
        this.aesIv = new byte[AES_IV_LENGTH];
        random.nextBytes(this.aesKey);
        random.nextBytes(this.aesIv);
    }
    
    /**
     * 加载RSA公钥
     */
    private PublicKey loadPublicKey(String publicKeyPath) throws Exception {
        String publicKeyPEM = new String(Files.readAllBytes(Paths.get(publicKeyPath)), StandardCharsets.UTF_8);
        
        // 移除PEM头尾和换行符
        publicKeyPEM = publicKeyPEM
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .replaceAll("\\s", "");
        
        byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(keySpec);
    }
    
    /**
     * 使用RSA OAEP SHA-512加密
     */
    private String rsaEncryptWithSHA512(byte[] data) throws Exception {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
        
        // 配置OAEP参数:使用SHA-512作为摘要算法,MGF1使用SHA-512
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-512",                          // 主摘要算法
            "MGF1",                              // 掩码生成函数
            new MGF1ParameterSpec("SHA-512"),   // MGF1使用的摘要算法
            PSource.PSpecified.DEFAULT           // P源(默认为空)
        );
        
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey, oaepParams);
        byte[] encrypted = cipher.doFinal(data);
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    /**
     * 使用AES-GCM加密
     */
    private String aesGcmEncrypt(String plaintext) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, aesIv);
        
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
        byte[] encrypted = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        
        return Base64.getEncoder().encodeToString(encrypted);
    }
    
    /**
     * 计算HmacSHA512签名
     * 签名规则:HMAC-SHA512(aesKey + aesIv + apiKey, openId)
     */
    private String calculateSignature() throws Exception {
        // 拼接消息:aesKey + aesIv + apiKey
        byte[] message = new byte[aesKey.length + aesIv.length + apiKey.getBytes(StandardCharsets.UTF_8).length];
        System.arraycopy(aesKey, 0, message, 0, aesKey.length);
        System.arraycopy(aesIv, 0, message, aesKey.length, aesIv.length);
        System.arraycopy(apiKey.getBytes(StandardCharsets.UTF_8), 0, message, aesKey.length + aesIv.length, 
                        apiKey.getBytes(StandardCharsets.UTF_8).length);
        
        // 使用openId作为密钥进行HMAC-SHA512
        Mac mac = Mac.getInstance("HmacSHA512");
        SecretKeySpec secretKeySpec = new SecretKeySpec(openId.getBytes(StandardCharsets.UTF_8), "HmacSHA512");
        mac.init(secretKeySpec);
        byte[] hmacBytes = mac.doFinal(message);
        
        // 转换为十六进制字符串(小写)
        StringBuilder hexString = new StringBuilder();
        for (byte b : hmacBytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }
    
    /**
     * 发起身份证四要素验证请求
     * 
     * @param idcard 身份证号码
     * @param realname 真实姓名
     * @param startDate 证件有效期起始日期(格式:yyyyMMdd,如:20160731)
     * @param endDate 证件有效期结束日期(格式:yyyyMMdd,如:20360730,长期填写:29991231)
     * @return API响应结果
     */
    public String verifyIdCard(String idcard, String realname, String startDate, String endDate) throws Exception {
        // 1. 使用RSA OAEP SHA-512加密AES密钥和IV
        String encryptedAesKey = rsaEncryptWithSHA512(aesKey);
        String encryptedAesIv = rsaEncryptWithSHA512(aesIv);
        
        // 2. 使用AES-GCM加密敏感参数
        String encryptedIdcard = aesGcmEncrypt(idcard);
        String encryptedRealname = aesGcmEncrypt(realname);
        String encryptedStartDate = aesGcmEncrypt(startDate);
        String encryptedEndDate = aesGcmEncrypt(endDate);
        
        // 3. 计算签名
        String signature = calculateSignature();
        
        // 4. 构建请求参数
        StringBuilder postData = new StringBuilder();
        postData.append("key=").append(URLEncoder.encode(apiKey, "UTF-8"));
        postData.append("&aesKey=").append(URLEncoder.encode(encryptedAesKey, "UTF-8"));
        postData.append("&aesIv=").append(URLEncoder.encode(encryptedAesIv, "UTF-8"));
        postData.append("&idcard=").append(URLEncoder.encode(encryptedIdcard, "UTF-8"));
        postData.append("&realname=").append(URLEncoder.encode(encryptedRealname, "UTF-8"));
        postData.append("&start_date=").append(URLEncoder.encode(encryptedStartDate, "UTF-8"));
        postData.append("&end_date=").append(URLEncoder.encode(encryptedEndDate, "UTF-8"));
        postData.append("&signature=").append(URLEncoder.encode(signature, "UTF-8"));
        
        // 5. 发送HTTP POST请求
        URL url = new URL(API_URL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        conn.setDoOutput(true);
        
        // 写入请求体
        try (OutputStream os = conn.getOutputStream()) {
            byte[] input = postData.toString().getBytes(StandardCharsets.UTF_8);
            os.write(input, 0, input.length);
        }
        
        // 6. 读取响应
        int responseCode = conn.getResponseCode();
        StringBuilder response = new StringBuilder();
        
        try (BufferedReader br = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            String responseLine;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
        }
        
        return response.toString();
    }
    
    /**
     * 测试示例
     */
    public static void main(String[] args) {
        try {
            // 配置参数(请替换为实际的值)
            String apiKey = "bc87c549f724ae2d204596187xxxxxx";  // 您的API Key
            String openId = "JH285250c81e065e255d449d49xxxxxxx";  // 您的OpenId
            String publicKeyPath = "/Users/java0904/www/vv.juhe.cn/id_card_four_factors/common/rsa_key/rsa_public_key.pem";
            
            // 创建客户端实例
            IdCardFourFactorsSHA512Client client = new IdCardFourFactorsSHA512Client(apiKey, openId, publicKeyPath);
            
            // 调用接口
            String result = client.verifyIdCard(
                "320382xxxxxxxxxx9",  // 身份证号
                "特朗普",               // 姓名
                "20160731",            // 有效期开始日期
                "20360730"             // 有效期结束日期
            );
            
            // 输出结果
            System.out.println("API响应结果:");
            System.out.println(result);
            
        } catch (Exception e) {
            System.err.println("请求失败:" + e.getMessage());
            e.printStackTrace();
        }
    }
}


评论区

眉上的汗水,眉下的泪水,你总要选择一样

0

0

4

举报