使用 Java 调用人脸比对认证接口
verifyface/sm4:无 SM2;OpenId + SM3 派生 SM4 密钥;姓名、身份证、人像(Base64 串)SM4-GCM;HMAC-SM3 请求签名为明文拼接。人像需纯 Base64,无data:image/...;base64,前缀。
- 请求地址:
https://apis.juhe.cn/verifyface/sm4 - 加密: OpenId → SM3 → 小写十六进制前 16 字符的 ASCII 字节 = 16 字节 SM4 密钥;SM4-GCM(tag 16 字节,无 AAD),密文+tag 再 Base64;nonce 随机、Base64 入参,服务端对 nonce Base64 解码后作为 GCM IV;HMAC-SM3 请求签名
- 传输: HTTP POST,
Content-Type: application/x-www-form-urlencoded
流程摘要
1. OpenId 做 SM3,hex 小写,取前 16 字符 → US_ASCII 为 16 字节 SM4 密钥
2. 随机 12 字节 nonce → Base64 作为参数 nonce
3. 姓名、身份证、人像 Base64 串 分别 SM4-GCM/NoPadding,UTF-8,共用一个 nonce/密钥,结果 Base64
4. HMAC-SM3:消息 = idcard+realname+image+key(image 为明文层的 Base64 串),密钥 = OpenId,Base64 → signature
5. POST:key、nonce、realname、idcard、image、signature;可选 thousand
成功时通常 error_code=0;result 中相似分等以官方文档 ID 264 及实际返回 JSON 为准。
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>
完整代码
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.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
* 人脸比对认证 /verifyface/sm4。OpenId 派生 SM4 密钥;GCM+nonce;HMAC 与 /sm 明文链一致。
*/
public class Example264Sm4 {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String API_URL = "https://apis.juhe.cn/verifyface/sm4";
private static final String API_KEY = "7a312b221dxxxxxxxxxxxxx8bacb";
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);
return Base64.getEncoder().encodeToString(
cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
}
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 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[] 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 verifyFaceSm4(String realname, String idcard, String imageBase64, String thousand)
throws Exception {
System.out.println("=== 人脸比对(SM4-GCM / verifyface sm4)===\n");
byte[] sm4Key = deriveSm4KeyFromOpenId(OPEN_ID);
System.out.println("1. 由 OpenId 派生 SM4 密钥");
byte[] nonce = generateRandomNonce(12);
String nonceB64 = Base64.getEncoder().encodeToString(nonce);
System.out.println("\n2. nonce(Base64): " + nonceB64);
String enReal = encryptWithSM4GCM(sm4Key, nonce, realname);
String enId = encryptWithSM4GCM(sm4Key, nonce, idcard);
String enImg = encryptWithSM4GCM(sm4Key, nonce, imageBase64);
String signature = hmacSm3SignatureBase64(OPEN_ID, idcard, realname, imageBase64, API_KEY);
System.out.println("\n3. 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", enReal));
params.add(new BasicNameValuePair("idcard", enId));
params.add(new BasicNameValuePair("image", enImg));
params.add(new BasicNameValuePair("signature", signature));
if (thousand != null && !thousand.isEmpty()) {
params.add(new BasicNameValuePair("thousand", thousand));
}
System.out.println("\n4. POST " + API_URL);
String response = sendPostRequest(API_URL, params);
System.out.println("\n5. 响应:\n" + response);
return response;
}
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 -> {
int code = r.getCode();
String body = EntityUtils.toString(r.getEntity(), StandardCharsets.UTF_8);
System.out.println(" HTTP: " + code);
return body;
});
}
}
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 realname = "赵xx";
String idcard = "220122xxxxxxxxxx";
String imageB64 = imageFileToBase64(face);
System.out.println("待比对: 姓名=" + realname + " 身份证=" + idcard + " 人像Base64长=" + imageB64.length());
verifyFaceSm4(realname, idcard, imageB64, null);
System.out.println("\n=== 请求结束 ===");
} catch (Exception e) {
e.printStackTrace();
}
}
}