本文介绍如何使用Java调用聚合数据的银行卡三要素核验API(SM2+SM4加密版),包含完整的实现代码、技术要点分析和常见问题解决方案。
本示例实现了聚合数据银行卡三要素核验API的调用,该API使用国密算法(SM2+SM4)进行数据加密传输:
- API文档: https://www.juhe.cn/docs/api/id/207
- 加密方式: SM2公钥加密 + SM4对称加密
- 传输协议: HTTP POST (application/x-www-form-urlencoded)
加密流程
1. 随机生成SM4密钥(16字节) + SM4向量IV(16字节)
2. 使用SM2公钥加密SM4密钥和IV → Base64编码
3. 使用SM4-CBC模式加密业务数据(姓名、身份证、银行卡号)→ Base64编码
4. 发送HTTP请求,包含加密后的所有参数
Maven依赖
在 pom.xml 中添加以下依赖:
<!-- Bouncy Castle 加密库 - 提供SM2、SM4算法支持 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>1.78.1</version>
</dependency>
<!-- Apache HttpClient 5 - 用于HTTP请求 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
核心技术要点
1. SM2加密 - 关键实现
⚠️ 重要:直接加密byte[],避免字符编码问题
public static String encryptWithSM2(PublicKey publicKey, byte[] data, boolean useDER) throws Exception {
// 初始化SM2加密引擎
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
ECDomainParameters domainParameters = new ECDomainParameters(
sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN()
);
BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(
bcecPublicKey.getQ(),
domainParameters
);
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
// ✅ 关键:直接加密byte[],不经过String转换
byte[] rawCiphertext = sm2Engine.processBlock(data, 0, data.length);
// 转换为ASN.1/DER格式(聚合数据API要求)
byte[] finalCiphertext = useDER
? SM2EncryptExample.convertToASN1DER(rawCiphertext)
: rawCiphertext;
return Base64.getEncoder().encodeToString(finalCiphertext);
}
为什么必须直接加密byte[]?
❌ 错误方式:byte[] → String → byte[] → 加密
问题:随机字节可能包含无效的UTF-8序列,转换时数据损坏
✅ 正确方式:byte[] → 直接加密
优势:保持数据完整性,与PHP等语言行为一致
2. HTTP请求参数构建
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("key", API_KEY));
params.add(new BasicNameValuePair("sm4Key", encryptedSm4Key)); // SM2加密后的SM4密钥
params.add(new BasicNameValuePair("sm4Iv", encryptedSm4Iv)); // SM2加密后的SM4向量
params.add(new BasicNameValuePair("realname", encryptedRealname)); // SM4加密后的姓名
params.add(new BasicNameValuePair("idcard", encryptedIdcard)); // SM4加密后的身份证
params.add(new BasicNameValuePair("bankcard", encryptedBankcard)); // SM4加密后的银行卡号
完整代码实现
package org.gmssl.test;
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.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.gmssl.SM2EncryptExample;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
/**
* 聚合数据 - 银行卡三要素核验(SM2+SM4加密版)示例
* API文档: https://www.juhe.cn/docs/api/id/207
*/
public class Example207 {
static {
// 添加 Bouncy Castle 安全提供者
Security.addProvider(new BouncyCastleProvider());
}
// API配置
private static final String API_URL = "http://v.juhe.cn/verifybankcard3/queryEncrySm";
private static final String API_KEY = "请替换为你的API Key"; // 请替换为你的API Key
/**
* 使用SM2公钥直接加密byte[]数据(不经过String转换,避免编码问题)
* @param publicKey SM2公钥
* @param data 待加密的字节数组
* @param useDER 是否转换为ASN.1/DER格式(false则使用原始C1C2C3格式)
*/
public static String encryptWithSM2(PublicKey publicKey, byte[] data, boolean useDER) throws Exception {
// 获取SM2椭圆曲线参数
X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
ECDomainParameters domainParameters = new ECDomainParameters(
sm2ECParameters.getCurve(),
sm2ECParameters.getG(),
sm2ECParameters.getN()
);
// 转换公钥格式
BCECPublicKey bcecPublicKey = (BCECPublicKey) publicKey;
ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(
bcecPublicKey.getQ(),
domainParameters
);
// 初始化SM2加密引擎(使用C1C2C3模式)
SM2Engine sm2Engine = new SM2Engine(SM2Engine.Mode.C1C2C3);
sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));
// 直接加密byte[]数据,不经过String转换
byte[] rawCiphertext = sm2Engine.processBlock(data, 0, data.length);
// 根据参数决定是否转换为ASN.1/DER格式
byte[] finalCiphertext;
if (useDER) {
// 转换为ASN.1/DER格式(GmSSL兼容)
finalCiphertext = SM2EncryptExample.convertToASN1DER(rawCiphertext);
} else {
// 使用原始C1C2C3格式
finalCiphertext = rawCiphertext;
}
// 返回Base64编码的密文
return Base64.getEncoder().encodeToString(finalCiphertext);
}
/**
* 使用SM4-CBC模式加密数据
*/
public static String encryptWithSM4CBC(byte[] key, byte[] iv, String plaintext) throws Exception {
// 创建SM4密钥规范
SecretKeySpec keySpec = new SecretKeySpec(key, "SM4");
// 创建IV参数规范
IvParameterSpec ivSpec = new IvParameterSpec(iv);
// 初始化Cipher
Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding", "BC");
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
// 执行加密
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 返回Base64编码的密文
return Base64.getEncoder().encodeToString(ciphertext);
}
/**
* 生成随机字节数组
*/
public static byte[] generateRandomBytes(int length) {
byte[] bytes = new byte[length];
new SecureRandom().nextBytes(bytes);
return bytes;
}
/**
* 发送银行卡三要素核验请求
*/
public static String verifyBankCard(String realname, String idcard, String bankcard,
String sm2PublicKeyPemPath) throws Exception {
System.out.println("=== 银行卡三要素核验(SM2+SM4加密版)===\n");
// 1. 生成随机的SM4密钥和IV(各16字节)
byte[] sm4Key = generateRandomBytes(16);
byte[] sm4Iv = generateRandomBytes(16);
System.out.println("1. 生成SM4密钥和IV:");
System.out.println(" SM4密钥(Hex): " + bytesToHex(sm4Key));
System.out.println(" SM4 IV(Hex): " + bytesToHex(sm4Iv));
// 2. 读取SM2公钥(调用SM2EncryptExample的方法)
System.out.println("\n2. 读取SM2公钥: " + sm2PublicKeyPemPath);
PublicKey sm2PublicKey = SM2EncryptExample.loadPublicKeyFromPEM(sm2PublicKeyPemPath);
System.out.println(" ✓ 公钥加载成功");
// 3. 使用SM2公钥加密SM4密钥和IV
boolean useDER = true; // false=原始C1C2C3格式, true=ASN.1/DER格式
System.out.println("\n3. 使用SM2加密SM4密钥和IV (格式: " + (useDER ? "ASN.1/DER" : "C1C2C3") + "):");
String encryptedSm4Key = encryptWithSM2(sm2PublicKey, sm4Key, useDER);
String encryptedSm4Iv = encryptWithSM2(sm2PublicKey, sm4Iv, useDER);
System.out.println(" ✓ SM4密钥加密完成");
System.out.println(" ✓ SM4 IV加密完成");
// 4. 使用SM4-CBC加密实际数据
System.out.println("\n4. 使用SM4-CBC加密业务数据:");
String encryptedRealname = encryptWithSM4CBC(sm4Key, sm4Iv, realname);
String encryptedIdcard = encryptWithSM4CBC(sm4Key, sm4Iv, idcard);
String encryptedBankcard = encryptWithSM4CBC(sm4Key, sm4Iv, bankcard);
System.out.println(" ✓ 姓名加密完成");
System.out.println(" ✓ 身份证加密完成");
System.out.println(" ✓ 银行卡号加密完成");
// 5. 构建请求参数(application/x-www-form-urlencoded格式)
System.out.println("\n5. 构建请求参数:");
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("key", API_KEY));
params.add(new BasicNameValuePair("sm4Key", encryptedSm4Key));
params.add(new BasicNameValuePair("sm4Iv", encryptedSm4Iv));
params.add(new BasicNameValuePair("realname", encryptedRealname));
params.add(new BasicNameValuePair("idcard", encryptedIdcard));
params.add(new BasicNameValuePair("bankcard", encryptedBankcard));
System.out.println(" 参数已构建");
// 6. 发送HTTP POST请求
System.out.println("\n6. 发送HTTP请求到: " + API_URL);
String response = sendPostRequest(API_URL, params);
System.out.println("\n7. 响应结果:");
System.out.println(response);
return response;
}
/**
* 发送HTTP POST请求(application/x-www-form-urlencoded)
*/
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 {
// ⚠️ 重要:这里必须使用聚合数据官方提供的SM2公钥!
// 请从聚合数据API文档或客服处获取官方公钥,不要使用自己生成的公钥
String sm2PublicKeyPath = "/Users/java0904/GmSSL-Java/src/main/resources/publicKey207.pem";
// 待验证的银行卡三要素信息
String realname = "奥宝奔"; // 姓名
String idcard = "440104 XXXXXXXXX3410"; // 身份证号
String bankcard = "6222XXXXXXXX6972009"; // 银行卡号
System.out.println("待验证信息:");
System.out.println(" 姓名: " + realname);
System.out.println(" 身份证: " + idcard);
System.out.println(" 银行卡号: " + bankcard);
System.out.println();
// 发送验证请求
verifyBankCard(realname, idcard, bankcard, sm2PublicKeyPath);
System.out.println("\n=== 验证完成 ===");
} catch (Exception e) {
System.err.println("❌ 验证失败: " + e.getMessage());
e.printStackTrace();
}
}
}