Java编写post请求-入参使用MD5withRSA加密,处理入参

#1024程序员节|征文#

废话可以不用看:最近经常遇到加密请求,一般都是使用代码处理入参加密后,再复制到apifox请求。但!这次不行了,有个字段很快就过期,还有个时间戳字段也用代码生成的,我这边刚跑起来加密的代码,把时间戳和加密的复制过去正好字段过期,经历了一番过后,手速明显提高,但还是赶在点击发送请求之前字段过期,我觉得还是用脑力节省体力,写个Java一劳永逸。

注:代码逻辑根据实际请求需要更改。需要自取~

代码中有:

1、自动生成RSA密钥 或 使用自己的私钥公钥。

2、私钥对明文签名,公钥对签名验证。

3、13位和17位时间戳。

4、生成随机数字和大小写字母。

5、POST请求地址、入参获取响应值。

package test;
//加密等操作引入
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.security.SecureRandom;
import java.text.SimpleDateFormat;
import java.util.Date;
//POST请求引入
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.BufferedReader;
import java.io.InputStreamReader;

public class testP {
    //定义常量-RSA:RSA、ALGORITHM:ALGORITHM、CHAR_SET:数字和大小写字母
    private static final String RSA = "RSA";
    private static final String ALGORITHM = "MD5withRSA";
    private static final String CHAR_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    //已知私钥,生成PrivateKey方式
    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePrivate(keySpec);
    }
    //已知公钥,生成PublicKey方式
    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(keySpec);
    }
    /**
     * 如果使用自动生成RSA密钥可使用此方法
     * @return
     * @throws NoSuchAlgorithmException
     */
    public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        keyPairGenerator.initialize(2048);
        return keyPairGenerator.genKeyPair();
    }
    /**
     * 使用私钥对明文进行签名
     * @param plainText  明文
     * @param privateKey 私钥
     * @return 签名结果
     * @throws Exception 签名异常
     */
    public static String sign(String plainText, PrivateKey privateKey) throws Exception {
        Signature signature = Signature.getInstance(ALGORITHM);
        signature.initSign(privateKey);
        signature.update(plainText.getBytes());
        byte[] signedData = signature.sign();
        return Base64.getEncoder().encodeToString(signedData);
    }
    /**
     * 使用公钥对签名进行验证
     * @param plainText 明文
     * @param signature 签名结果
     * @param publicKey 公钥
     * @return 验证结果,true表示验证通过,false表示验证失败
     * @throws Exception 验证异常
     */
    public static boolean verify(String plainText, String signature, PublicKey publicKey) throws Exception {
        Signature signatureObj = Signature.getInstance(ALGORITHM);
        signatureObj.initVerify(publicKey);
        signatureObj.update(plainText.getBytes());
        byte[] decodedSignature = Base64.getDecoder().decode(signature);
        return signatureObj.verify(decodedSignature);
    }

    public static void main(String[] args) throws Exception {
//        // 自动生成RSA密钥对
//        KeyPair keyPair = generateKeyPair();
//        PrivateKey privateKey = keyPair.getPrivate();
//        PublicKey publicKey = keyPair.getPublic();
        //自己的公钥和私钥转换为PrivateKey可以使用的格式
        String privateKeyString = "你的私钥";
        String publicKeyString = "你的公钥";
        PrivateKey privateKey = getPrivateKey(privateKeyString);
        PublicKey publicKey = getPublicKey(publicKeyString);
        //生成13位和17位时间戳
        long timestamp = System.currentTimeMillis();
        String timestampStr13 = Long.toString(timestamp);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        String timestampStr17 = sdf.format(new Date());
        String tradeNo ="1111"+timestampStr17+"992211";
        System.out.println("订单号:" + tradeNo);
        //生成随机数字和大小写字母
        SecureRandom random = new SecureRandom();
        StringBuilder sb = new StringBuilder(22);
        for (int i = 0; i < 22; i++) {
            sb.append(CHAR_SET.charAt(random.nextInt(CHAR_SET.length())));
        }
        String nonceStr = sb.toString();
        // 明文plainText:待加密明文
        String authCode = "123456";
        String plainText = "nonceStr="+nonceStr+"&timestamp="+timestampStr13+"&tradeNo="+tradeNo;
        System.out.println("签名前数据:" + plainText);
        // 使用私钥对明文进行签名
        String signature = testP.sign(plainText, privateKey);
        System.out.println("签名结果:" + signature);
        // 使用公钥对签名进行验证
        boolean result = testP.verify(plainText, signature, publicKey);
        System.out.println("验证签名结果:" + result);
        //编写post请求
        try {
            URL url = new URL("http://你的请求链接/ncel");
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 设置请求头
            conn.setRequestProperty("Content-Type", "application/json");
            // 写入POST数据
            String postData = "{\"nonceStr\":\""+nonceStr+"\",\"sign\":\""+signature+"\",\"timestamp\":\""+timestampStr13+"\",\"tradeNo\":\""+tradeNo+"\"}";
            byte[] outputInBytes = postData.getBytes("UTF-8");
            OutputStream os = conn.getOutputStream();
            os.write(outputInBytes);
            os.close();
            // 获取响应码
            BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String inputLine;
            StringBuilder response = new StringBuilder();
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
            System.out.println("请求返回值:"+ response);
            in.close();
            // 获取响应状态码,读取响应内容
            int responseCode = conn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
            }
            //关闭http连接
            conn.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

小知识点:Java基础类型

基础类型直接存储值通常存储在栈内存中,使访问速度快。

1、byte:8位有符号整数。
2、short:16位有符号整数。
3、int:32位有符号整数。
4、long:64位有符号整数。
5、float:32位单精度浮点数。
6、double:64位双精度浮点数。
7、char:16位Unicode字符。
8、boolean:用于表示真/假值。

int和long用于数值计算,如计数器、循环索引等。
float和double用于科学计算或者需要精确小数的场景。
char用于处理单个字符,如读取文件中的字符。
boolean用于逻辑运算,如条件判断。

异常是程序运之前,编译期间出实现的错误,这些异常异常往往在运时才能被发现。

<think>好的,我现在需要解决Java jar包支付回调验签失败的问题。首先,我应该回忆一下用户提供的引用内容,看看有没有相关的线索。 用户提到了引用[1]中的情况,是关于微信小程序支付v3报错illegal key size的问题,解决方法是替换JRE的jar包。这可能和加密算法有关,比如Java默认的加密策略限制了密钥长度,替换jar包可以解除限制。验签失败可能涉及类似的加密问题,比如密钥不匹配或者算法不支持。 然后引用[2]提到处理银联支付回调结果,需要正确处理返回的数。验签失败可能是因为回调数没有正确获取,或者签名生成方式不对。比如,数顺序错误、编码问题,或者签名算法不一致。 接下来,我需要考虑验签的一般流程。通常,支付平台会提供签名算法,商户需要按照文档生成签名,并与回调中的签名对比。如果失败,可能的原因包括: 1. **密钥问题**:公私钥不匹配,或者密钥格式错误。比如微信v3可能需要正确的API密钥,或者证书问题。 2. **数拼接错误**:比如数没有按字典序排序,遗漏了某些字段,或者包含多余的空格。 3. **编码问题**:比如数传递时没有统一使用UTF-8,导致签名验签时的字符串不一致。 4. **签名算法实现错误**:比如SHA256WithRSAMD5WithRSA混淆使用,或者签名生成步骤有误。 5. **网络传输问题**:回调数在传输过程中被修改,比如特殊字符未转义,导致接收到的数与签名时的不同。 根据引用[1]中的解决方案,可能需要检查JCE策略文件是否正确安装,特别是如果使用高强度加密算法时。比如AES-256可能需要替换local_policy.jar和US_export_policy.jar。如果验签使用了高强度的加密算法,而环境没有正确配置,可能导致验签失败。 另外,引用[2]中的银联支付回调处理,需要注意回调数的获取方式是否正确。比如,是否从正确的请求数中获取了签名、时间戳、随机串等,是否有数被错误地忽略或转换。 可能的解决步骤包括: 1. **确认密钥配置**:检查商户私钥和平台公钥是否正确,是否使用最新证书。比如微信支付可能需要定期更新证书。 2. **核对签名算法**:确认双方使用的签名算法是否一致,如RSA2(SHA256WithRSA)或RSA。 3. **调试数拼接**:打印出与签名的所有数,确保顺序、字段名和值与文档一致。特别注意是否遗漏了某些必传字段,或者是否有多余的数被包含进去。 4. **检查编码和转义**:确保所有数在拼接前都正确进行了URL编码或解码,没有多余的转义字符。 5. **验证签名逻辑**:单独编写单元测试,模拟回调数,逐步验证签名生成的每一步是否正确,比如字符串拼接、哈希计算、加密方式。 6. **查看日志和错误信息**:检查支付平台返回的具体错误码或信息,可能有更具体的提示,如“签名无效”或“数不完整”。 7. **更新SDK或依赖库**:确保使用的支付SDK版本是最新的,避免已知的bug导致验签失败。 8. **网络抓包分析**:使用工具如Wireshark或Charles捕获回调请求,确认实际接收到的数与预期是否一致,是否存在数据截断或编码问题。 可能还需要考支付平台的具体文档,比如微信支付或支付宝的验签指南,确认是否有特殊的处理步骤,比如需要将数按特定格式排序,或者需要排除某些数。 此外,如果项目是部署在Java环境中,注意不同的容器(如Tomcat、Jetty)处理数的方式可能不同,比如对GET/POST数的解析是否有差异,或者是否有过滤器修改了请求数。 最后,如果以上步骤都检查过仍然失败,可能需要联系支付平台的技术支持,提供详细的验签数和生成的签名,以便对方协助排查问题。</think>### 解决Java JAR包支付回调验签失败的步骤分析 #### 1. 密钥配置核查 - **检查密钥对匹配性**:确认商户私钥与支付平台公钥是否配对,特别是证书过期或更换后未及时更新的情况。例如微信支付V3要求定期更换证书[^1]。 - **密钥格式验证**:确保使用PKCS#8格式的私钥(以`-----BEGIN PRIVATE KEY-----`开头),而非PKCS#1格式。错误格式会导致签名生成异常。 - **JCE策略文件更新**:若涉及高强度加密算法(如AES-256),需按引用[1]方法替换`%JRE_HOME%\lib\security`下的`local_policy.jar`和`US_export_policy.jar`。 #### 2. 签名逻辑验证 - **数拼接规范性**: ```java // 示例:按数名ASCII字典序拼接键值对 Map<String, String> params = ...; // 获取回调数 List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); StringBuilder sb = new StringBuilder(); for (String key : keys) { if (!"sign".equals(key)) { // 排除签名字段本身 sb.append(key).append("=").append(params.get(key)).append("&"); } } String signContent = sb.substring(0, sb.length() - 1); // 去除末尾& ``` - **签名算法一致性**:确认双方使用相同算法,如支付宝推荐`RSA2`,微信V3使用`HMAC-SHA256`。检查代码中`Signature.getInstance("SHA256WithRSA")`等实现是否匹配文档。 #### 3. 编码与转义处理 - **统一字符编码**:在签名计算前,所有数应使用UTF-8编码,避免中文字符乱码: ```java String value = URLDecoder.decode(params.get(key), StandardCharsets.UTF_8.name()); ``` - **特殊符号处理**:检查回调数中的`+`、`/`等符号是否被错误转义,需与支付平台编码规则一致。 #### 4. 日志与调试辅助 - **关键数据输出**:在验签流程中添加日志,打印与签名的原始字符串、生成的签名和回调中的签名: ```java logger.info("签名字符串: {}", signContent); logger.info("生成签名: {}", generatedSign); logger.info("回调签名: {}", receivedSign); ``` - **网络抓包验证**:使用工具捕获回调请求原始数据,对比实际传输数与代码接收值,排查数据篡改或丢失问题。 #### 5. 依赖与环境检查 - **SDK版本升级**:若使用支付平台提供的SDK,检查是否为最新版本。旧版本可能存在已知验签漏洞,例如微信支付SDK 3.0.9修复了特定场景下的验签异常。 - **容器配置影响**:如Tomcat的`server.xml`中`URIEncoding`是否设为UTF-8,避免GET请求数解码错误。 #### 6. 支付平台联调 - **沙箱环境测试**:利用支付平台提供的测试工具(如支付宝沙箱)模拟回调,隔离生产环境干扰。 - **错误代码查询**:根据返回的错误码(如微信的`SIGN_ERROR`)查阅官方文档,针对性调整逻辑。 ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

方心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值