登录
原创

身份证四要素校验(国密版)(SM4国密加密版)- 聚合数据API调用Java示例

发布于 2026-04-23 阅读 31
  • 后端
原创

使用 Java 调用身份证四要素接口 id_card_four_factors/sm4:无 SM2;OpenId + SM3 派生 SM4 密钥;四要素 SM4-GCMHMAC-SM3 请求签名。日期 yyyyMMdd

  • 请求地址: https://apis.juhe.cn/id_card_four_factors/sm4
  • 加密: OpenId → SM3 → 小写十六进制前 16 字符的 ASCII 字节 = 16 字节 SM4 密钥;SM4-GCM(tag 16 字节,无 AAD),密文+tag 再 Base64;nonce 随机、Base64 入参;HMAC-SM3 请求签名
  • 传输: HTTP POST,Content-Type: application/x-www-form-urlencoded

四要素: 姓名、身份证号、有效期开始有效期结束yyyyMMdd;长期有效时结束为 00000000

说明: 企业类接口,需资质。同一 OpenId 既用于密钥派生也用于HMACkey 为 AppKey,勿混淆。

流程摘要

1. OpenId 做 SM3,hex 小写,取前 16 字符 → US_ASCII 为 16 字节 SM4 密钥
2. 随机 12 字节 nonce → Base64 作为参数 nonce
3. 各明文字段 SM4-GCM/NoPadding,UTF-8,共用一个 nonce/密钥,结果 Base64
4. HMAC-SM3:消息 = idcard+realname+start_date+end_date+key,密钥 = OpenId,Base64 → signature
5. POST:key、nonce、四个密文字段、signature

成功时 error_code=0result.res 为 1 一致、2 不一致。result.signature 为服务端对返回的签名,与请求中 signature 不同。

Maven

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcpkix-jdk18on</artifactId>
    <version>1.78.1</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.2.1</version>
</dependency>

完整代码

API_KEYOPEN_ID 改为真实值;下述为脱敏示例。

package org.example;

import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ParseException;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicNameValuePair;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

/**
 * 身份证四要素 /id_card_four_factors/sm4。OpenId 派生 SM4 密钥;GCM+nonce;HMAC 签名同约定。
 */
public class Example550Sm4 {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final String API_URL = "https://apis.juhe.cn/id_card_four_factors/sm4";
    private static final String API_KEY = "bc87c549fxxxxx2xxxxe49f4";
    private static final String OPEN_ID = "JH285250cxxxxxxxa515221d";

    private static final int GCM_TAG_BITS = 128;

    public static byte[] deriveSm4KeyFromOpenId(String openId) {
        SM3Digest digest = new SM3Digest();
        byte[] input = openId.getBytes(StandardCharsets.UTF_8);
        digest.update(input, 0, input.length);
        byte[] hash = new byte[digest.getDigestSize()];
        digest.doFinal(hash, 0);
        String hex = Hex.toHexString(hash);
        return hex.substring(0, 16).getBytes(StandardCharsets.US_ASCII);
    }

    public static String encryptWithSM4GCM(byte[] key, byte[] nonce, String plaintext) throws Exception {
        SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_BITS, nonce);

        Cipher cipher = Cipher.getInstance("SM4/GCM/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);

        byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(ciphertext);
    }

    public static byte[] generateRandomNonce(int length) {
        byte[] bytes = new byte[length];
        new SecureRandom().nextBytes(bytes);
        return bytes;
    }

    public static String hmacSm3SignatureBase64(String openId, String idcard, String realname,
                                                String startDate, String endDate, String apiKey) throws Exception {
        String message = idcard + realname + startDate + endDate + apiKey;
        HMac hmac = new HMac(new SM3Digest());
        byte[] keyBytes = openId.getBytes(StandardCharsets.UTF_8);
        hmac.init(new KeyParameter(keyBytes));
        byte[] msg = message.getBytes(StandardCharsets.UTF_8);
        hmac.update(msg, 0, msg.length);
        byte[] out = new byte[hmac.getMacSize()];
        hmac.doFinal(out, 0);
        return Base64.getEncoder().encodeToString(out);
    }

    public static String verifyIdCardFourFactorsSm4(String realname, String idcard,
                                                    String startDate, String endDate) throws Exception {

        System.out.println("=== 身份证四要素(SM4-GCM / sm4)===\n");

        byte[] sm4Key = deriveSm4KeyFromOpenId(OPEN_ID);
        System.out.println("1. 由 OpenId 派生 SM4 密钥:");
        System.out.println("   SM4密钥(Hex): " + bytesToHex(sm4Key));

        byte[] nonce = generateRandomNonce(12);
        String nonceB64 = Base64.getEncoder().encodeToString(nonce);
        System.out.println("\n2. nonce(Base64): " + nonceB64);

        System.out.println("\n3. SM4-GCM 加密四要素");
        String encryptedRealname = encryptWithSM4GCM(sm4Key, nonce, realname);
        String encryptedIdcard = encryptWithSM4GCM(sm4Key, nonce, idcard);
        String encryptedStart = encryptWithSM4GCM(sm4Key, nonce, startDate);
        String encryptedEnd = encryptWithSM4GCM(sm4Key, nonce, endDate);

        String signature = hmacSm3SignatureBase64(OPEN_ID, idcard, realname, startDate, endDate, API_KEY);
        System.out.println("\n4. HMAC-SM3 已计算");

        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("key", API_KEY));
        params.add(new BasicNameValuePair("nonce", nonceB64));
        params.add(new BasicNameValuePair("realname", encryptedRealname));
        params.add(new BasicNameValuePair("idcard", encryptedIdcard));
        params.add(new BasicNameValuePair("start_date", encryptedStart));
        params.add(new BasicNameValuePair("end_date", encryptedEnd));
        params.add(new BasicNameValuePair("signature", signature));

        System.out.println("\n5. POST " + API_URL);
        String response = sendPostRequest(API_URL, params);

        System.out.println("\n6. 响应:\n" + response);
        return response;
    }

    private static String sendPostRequest(String url, List<NameValuePair> params) throws IOException, ParseException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, StandardCharsets.UTF_8);
            httpPost.setEntity(entity);
            return httpClient.execute(httpPost, response -> {
                int statusCode = response.getCode();
                String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
                System.out.println("   HTTP: " + statusCode);
                return responseBody;
            });
        }
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        try {
            String realname = "赵xx";
            String idcard = "220122xxxxxxxxxx";
            String startDate = "20170606";
            String endDate = "20270606";

            System.out.println("待核验四要素:");
            System.out.println("  姓名: " + realname);
            System.out.println("  身份证: " + idcard);
            System.out.println("  有效期起: " + startDate);
            System.out.println("  有效期止: " + endDate + "(长期有效 00000000)");
            System.out.println();

            verifyIdCardFourFactorsSm4(realname, idcard, startDate, endDate);

            System.out.println("\n=== 请求结束 ===");
        } catch (Exception e) {
            System.err.println("❌ 调用失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

评论区

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

0

0

2

举报