登录
原创

银行卡三要素核验(SM2+SM4国密加密版)- 聚合数据API调用Java示例

发布于 2026-01-07 阅读 153
  • Java
原创

本文介绍如何使用Java调用聚合数据的银行卡三要素核验API(SM2+SM4加密版),包含完整的实现代码、技术要点分析和常见问题解决方案。

本示例实现了聚合数据银行卡三要素核验API的调用,该API使用国密算法(SM2+SM4)进行数据加密传输:

加密流程

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();
        }
    }
}

评论区

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

0

0

4

举报