登录
原创

人脸实名认证(SM2+SM4国密加密版)- 聚合数据API调用Java示例

发布于 2026-04-20 阅读 808
  • 后端
原创

使用 Java 调用人脸比对认证(国密版)接口 verifyface/sm:需聚合数据提供的 SM2 公钥 PEM;随机 SM4 密钥 + IV,拼成 32 字节经 SM2 加密后以 sm4secret 提交;姓名、身份证、人像(Base64 串)用 SM4-CBCHMAC-SM3 请求签名为明文字段拼接。人像需纯 Base64,无 data:image/...;base64, 前缀。

  • 请求地址: https://apis.juhe.cn/verifyface/sm
  • 加密: 随机 16 字节 SM4 密钥 + 16 字节 IV,拼接为 32 字节 → 使用平台 SM2 公钥加密(示例为 C1C2C3ASN.1/DER 后 Base64,与 GmSSL 等常见服务端一致;若验密失败可对照文档尝试原始 C1C2C3);表单项 sm4secret;业务字段 SM4-CBC/PKCS5UTF-8同一密钥与 IV、密文再 Base64
  • 传输: HTTP POST,Content-Type: application/x-www-form-urlencoded

流程摘要

1. 随机 16 字节 SM4 密钥、16 字节 IV
2. 32 字节 = key || iv,SM2 公钥加密 → Base64 作为 sm4secret
3. 姓名、身份证、人像 Base64 串 分别 SM4-CBC 加密,UTF-8/同一 key+iv,结果 Base64
4. HMAC-SM3:消息 = idcard+realname+image+key(均为明文/原始 Base64 串),密钥 = OpenId,结果 Base64 → signature
5. POST:key、sm4secret、idcard、realname、image、signature;可选 thousand

成功时通常 error_code=0;具体字段以官方文档 ID 264 为准。若接口返回响应体签名字段,其算法与请求中的 signature 不同,以文档为准。

Maven

与仓库 pom.xml 一致即可,例如:

<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>

完整代码

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.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

/**
 * 人脸比对认证 /verifyface/sm。SM2(sm4secret) + SM4-CBC + HMAC-SM3。
 */
public class Example264 {

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

    private static final String API_URL = "https://apis.juhe.cn/verifyface/sm";
    private static final String API_KEY = "7a312b221dxxxxxxxxxxxxx8bacb";
    private static final String OPEN_ID = "JH285250cxxxxxxxa515221d";

    public static PublicKey loadPublicKeyFromPEM(String pemFilePath) throws IOException {
        try (FileReader fileReader = new FileReader(pemFilePath);
             PEMParser pemParser = new PEMParser(fileReader)) {
            Object object = pemParser.readObject();
            JcaPEMKeyConverter c = new JcaPEMKeyConverter().setProvider("BC");
            if (object instanceof org.bouncycastle.asn1.x509.SubjectPublicKeyInfo) {
                return c.getPublicKey((org.bouncycastle.asn1.x509.SubjectPublicKeyInfo) object);
            }
            throw new IllegalArgumentException("PEM 格式错误");
        }
    }

    public static String encryptWithSM2(PublicKey publicKey, byte[] data, boolean useDER) throws Exception {
        X9ECParameters ec = GMNamedCurves.getByName("sm2p256v1");
        ECDomainParameters domain = new ECDomainParameters(ec.getCurve(), ec.getG(), ec.getN());
        BCECPublicKey pk = (BCECPublicKey) publicKey;
        ECPublicKeyParameters pub = new ECPublicKeyParameters(pk.getQ(), domain);
        SM2Engine sm2 = new SM2Engine(SM2Engine.Mode.C1C2C3);
        sm2.init(true, new ParametersWithRandom(pub, new SecureRandom()));
        byte[] raw = sm2.processBlock(data, 0, data.length);
        byte[] out = useDER ? convertToASN1DER(raw) : raw;
        return Base64.getEncoder().encodeToString(out);
    }

    public static byte[] convertToASN1DER(byte[] c1c2c3) throws Exception {
        byte[] x = Arrays.copyOfRange(c1c2c3, 1, 33);
        byte[] y = Arrays.copyOfRange(c1c2c3, 33, 65);
        int c2len = c1c2c3.length - 97;
        byte[] c2 = Arrays.copyOfRange(c1c2c3, 65, 65 + c2len);
        byte[] c3 = Arrays.copyOfRange(c1c2c3, 65 + c2len, c1c2c3.length);
        ASN1EncodableVector v = new ASN1EncodableVector();
        v.add(new ASN1Integer(new BigInteger(1, x)));
        v.add(new ASN1Integer(new BigInteger(1, y)));
        v.add(new DEROctetString(c3));
        v.add(new DEROctetString(c2));
        return new DERSequence(v).getEncoded("DER");
    }

    public static String encryptWithSM4CBC(byte[] key, byte[] iv, String plaintext) throws Exception {
        SecretKeySpec spec = new SecretKeySpec(key, "SM4");
        IvParameterSpec ivs = new IvParameterSpec(iv);
        Cipher c = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
        c.init(Cipher.ENCRYPT_MODE, spec, ivs);
        return Base64.getEncoder().encodeToString(
                c.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
    }

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

    public static String verifyFace(String realname, String idcard, String imageBase64,
                                    String sm2PemPath, String thousand) throws Exception {

        byte[] k = new byte[16], iv = new byte[16];
        new SecureRandom().nextBytes(k);
        new SecureRandom().nextBytes(iv);

        PublicKey pub = loadPublicKeyFromPEM(sm2PemPath);
        byte[] secret32 = new byte[32];
        System.arraycopy(k, 0, secret32, 0, 16);
        System.arraycopy(iv, 0, secret32, 16, 16);
        String sm4secret = encryptWithSM2(pub, secret32, true);

        String enReal = encryptWithSM4CBC(k, iv, realname);
        String enId = encryptWithSM4CBC(k, iv, idcard);
        String enImg = encryptWithSM4CBC(k, iv, imageBase64);
        String signature = hmacSm3SignatureBase64(OPEN_ID, idcard, realname, imageBase64, API_KEY);

        List<NameValuePair> params = new ArrayList<>();
        params.add(new BasicNameValuePair("key", API_KEY));
        params.add(new BasicNameValuePair("sm4secret", sm4secret));
        params.add(new BasicNameValuePair("idcard", enId));
        params.add(new BasicNameValuePair("realname", enReal));
        params.add(new BasicNameValuePair("image", enImg));
        params.add(new BasicNameValuePair("signature", signature));
        if (thousand != null && !thousand.isEmpty()) {
            params.add(new BasicNameValuePair("thousand", thousand));
        }

        return sendPostRequest(API_URL, params);
    }

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

    public static String imageFileToBase64(Path p) throws IOException {
        return Base64.getEncoder().encodeToString(Files.readAllBytes(p));
    }

    public static void main(String[] args) {
        try {
            Path face = Paths.get("src/main/resources/face.jpg");
            String sm2Pem = "src/main/resources/publickey264.pem";
            String realname = "赵xx";
            String idcard = "220122xxxxxxxxxx";
            String imageB64 = imageFileToBase64(face);
            System.out.println("待比对: 姓名=" + realname + " 身份证=" + idcard);
            String resp = verifyFace(realname, idcard, imageB64, sm2Pem, null);
            System.out.println(resp);
            System.out.println("=== 请求结束 ===");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

评论区

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

0

0

3

举报