背景:

最近来到了新的公司,公司做的是保险支付相关业务,对接渠道的时候经常会用到数据的加签和验签,初次涉及RSA加签验签,通过网站生成了RSA公钥和私钥,用私钥将我要传送的数据进行了加签,并将我的公钥提供给了渠道方进行验签,结果在联调的时候,验签总是错误,渠道方用自己的私钥对数据加签后再用自己的公钥对数据进行验签却能通过,于是我也用自己的私钥对数据进行加签后再用自己的公钥对数据进行验签,结果让我惊讶,居然没有通过!

到了这里,产生错误的原因基本上已经一目了然了,我通过网站生成的公私钥是无法配对的,这当中可能涉及到了网站生成公私钥的时候已经对公私钥进行了处理,比如说PKCS8的处理,所以决定自己用Java来生成RSA公钥和私钥进行验证测试,文档写出来了,测试结果自然已经知道了,是通过的。

以下为完整的验签过程:

启动类:ZhongbaohuiApplication.java

package com.test.zhongbaohui;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ZhongbaohuiApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZhongbaohuiApplication.class, args);
    }
}

请求Controller:RequestController.java

package com.test.zhongbaohui.controller;

import com.alibaba.fastjson.JSONObject;
import com.test.zhongbaohui.utils.RSASignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: zhongbaohui
 * @package: com.test.zhongbaohui.controller
 * @description:
 * @auther: chengjunyu
 * @createDate: 2019/12/6 22:05
 */
@RestController
@RequestMapping(value="/test")
@Slf4j
public class RequestController {

    @Autowired
    private ResponseController responseController;

    @PostMapping("/getJsonObject")
    private String getJsonObject(HttpServletRequest request, HttpServletResponse response) {

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("signType", "RSA");
        jsonObject.put("enterpriseId", "201975538583911110");
        jsonObject.put("nonce", "b0eed33073664f5fa983c5b774dbd4b6");
        jsonObject.put("timestamp", "2019-12-07 01:19:25");
        Map<String, Object> map = new HashMap<>();
        map.put("bankCode", "其他");
        map.put("batchNo", "201975538583911110b1084fa29f6c");
        map.put("bankCardNumber", "6217856100077026406");
        map.put("paymentNote", "佣金发放");
        map.put("idCardNumber", "320123199103104650");
        map.put("mobile", "15365176555");
        map.put("bankName", "中国银行");
        map.put("outEnterpriseOrderNo", "T20191207011545663692017");
        map.put("realPayment", "1.00");
        map.put("serviceId", "201968613430727001");
        map.put("userName", "程俊予");
        jsonObject.put("data", map);
        //私钥内容
        String privateKey = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgFnOID56YNquwenrgnW1Ud+GBcSFojPOY00+TYq/qHVaprGPeuKlAcBebkyj4+G3H4t7e1DTOblQtZk/yi+2VcbDnhHQl3UVdkLkVMRXXCBPBJtjSo3RMMJFC6OCiKfzhujhhio7MJWWrMYLtAgMBAAECgYBplZud/CZv1KLzIA5bdbF2yk36FYoc3hl3iXLeiyp91NGc6hqhFSEyXPhvrZP0aAym9IC824Bjq4Gg7pkkHzYT3IGDCqqyodBYcdof8Jsk9t0G0Ll7G1dlQwl9R6+SAvauF5RUbwz5Byos6cnFbybfqAdRUdF96yH0Hw0QF1u8XQJBAPrpHvZpeOZNSY/M1wlJZv5gV1OoI9s+PZgJQHgWbT7FaiPDkZiAa7B6hGNBgUa7m4vEzGJNAOHxhdl1QMtlTjMCQQD3VInIf9EjKZn7LNcPQsl1AkXbwuXjtMceeuX43lcdapgQ+4Y6G5QU3fhwZxwsdZnUbLqJWzFgXw/F2E2DxopfAkBxGErgfsID7KpPquDySqel2P8DsjIXTIKu2Ny6REGRnaIt5KTnvFrN/StXIduHamC+K0KEvHi9XwQZ9IP0KgGJAkEA3hUzzywuP3OYhzhhN5vRx1YuIkGkKU3nSdAy9b+323seZoljooOm+QHDljKP0sAaS+sBqFqRQKa7Q/yQxdWd4wJBAIUXethFnMr3U9FetKHmWKwOPh23EHM0xPdVzMcb24WwK7QAXCMo71ugG6qqmBA+wYCrjPwbMu5XysB5+d5ZNC0=";
        String sign = RSASignUtils.sign(jsonObject, privateKey);
        log.info("验签sign为:{}", sign);
        jsonObject.put("sign", sign);
        String message = responseController.returnMsg(jsonObject.toJSONString());
        return message;
    }
}

响应Controller:ResponseController.java

package com.test.zhongbaohui.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.test.zhongbaohui.utils.RSASignUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: zhongbaohui
 * @package: com.test.zhongbaohui.controller
 * @description:
 * @auther: chengjunyu
 * @createDate: 2019/12/7 20:46
 */
@RestController
@Slf4j
public class ResponseController {
    @PostMapping("/returnMsg")
    public String  returnMsg(String message) {
        JSONObject jsonObject = JSONObject.parseObject(message);
        log.info("接受请求内容为:{}", jsonObject.toJSONString());
        String sign = jsonObject.getString("sign");
        jsonObject.remove("sign");
        String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD5gn/eAnLyf6xZziA+emDarsHp64J1tVHfhgXEhaIzzmNNPk2Kv6h1Wqaxj3ripQHAXm5Mo+Phtx+Le3tQ0zm5ULWZP8ovtlXGw54R0Jd1FXZC5FTEV1wgTwSbY0qN0TDCRQujgoin84bo4YYqOzCVlqzGC7QIDAQAB";
        boolean flag = RSASignUtils.verify(jsonObject, publicKey, sign);
        JSONObject object = new JSONObject();
        if(flag) {
            object.put("code", "200");
            object.put("status", "success");
            object.put("message:", "验签成功");
        }else {
            object.put("code", "400");
            object.put("status", "failure");
            object.put("message:", "验签失败");
        }
        return object.toJSONString();
    }
}

RSA工具类:RSASignUtils.java

package com.test.zhongbaohui.utils;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: zhongbaohui
 * @package: com.test.zhongbaohui.controller
 * @description:
 * @auther: chengjunyu
 * @createDate: 2019/12/6 23:03
 */
@Slf4j
@Component
public class RSASignUtils {

    public static final String KEY_ALGORITHM = "RSA";
    private static final String PUBLIC_KEY = "RSAPublicKey";
    private static final String PRIVATE_KEY = "RSAPrivateKey";
    public static final String SIGNATURE_ALGORITHM="MD5withRSA";
    public static final Integer RSA_KEY_SIZE = 1024;

    /*
     * @function: 使用字符串格式的私钥为JSONObject格式的内容加签
     * @param: [jsonObject, privateKey]
     * @return: java.lang.String
     * @auther: chengjunyu
     * @date: 2019/12/7 21:06
     */
    public static String sign(JSONObject jsonObject, String privateKey) {
        String signMsg = "";
        String data = jsonObject.toString();
        log.info("加签对象内容为:{}", data);
        try {
            byte[] keyBytes = decryptBASE64(privateKey);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey key = keyFactory.generatePrivate(keySpec);
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initSign(key);
            signature.update(data.getBytes("ISO-8859-1"));
            signMsg = Base64.encodeBase64String(signature.sign());
        } catch (Exception var8) {
            var8.printStackTrace();
        }
        return signMsg;
    }

    /*
     * @function: 使用字符串格式的公钥为JSONObject格式的内容验签
     * @param: [jsonObject, publicKey, sign]
     * @return: boolean
     * @auther: chengjunyu
     * @date: 2019/12/8 14:56
     */
    public static boolean verify(JSONObject jsonObject, String publicKey, String sign) {
        String s = jsonObject.toJSONString();
        log.info("s:{}",s);
        boolean rs = false;
        try {
            byte[] keyBytes = decryptBASE64(publicKey);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey key = keyFactory.generatePublic(keySpec);
            Signature signature = Signature.getInstance("MD5withRSA");
            signature.initVerify(key);
            signature.update(s.getBytes("ISO-8859-1"));
            return signature.verify(Base64.decodeBase64(sign.getBytes()));
        } catch (Exception var9) {
            var9.printStackTrace();
            return rs;
        }
    }

    /*
     * @function: 获取PublicKey格式的公钥,本例中未使用
     * @param: [key]
     * @return: java.security.PublicKey
     * @auther: chengjunyu
     * @date: 2019/12/8 16:10
     */
    public static PublicKey getPublicKey(String key) {
        PublicKey publicKey = null;
        try {
            byte[] keyBytes = (new BASE64Decoder()).decodeBuffer(key);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            // RSA对称加密算法
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            // 取公钥匙对象
            publicKey = keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return publicKey;
    }


    /*
     * @function: 获取PublicKey格式的私钥,本例中未使用
     * @param: [key]
     * @return: java.security.PrivateKey
     * @auther: chengjunyu
     * @date: 2019/12/8 16:10
     */
    public static PrivateKey getPrivateKey(String key) {
        PrivateKey privateKey = null;
        try {
            byte[] keyBytes = (new BASE64Decoder()).decodeBuffer(key);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            privateKey = keyFactory.generatePrivate(keySpec);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    /*
     * @function: 初始化公钥和私钥
     * @param: []
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     * @auther: chengjunyu
     * @date: 2019/12/8 14:34
     */
    public static Map<String, Object> initKey() {
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyPairGen.initialize(RSA_KEY_SIZE);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

    //获得公钥字符串
    public static String getPublicKeyStr(Map<String, Object> keyMap) {
        //获得map中的公钥对象 转为key对象
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    //获得私钥字符串
    public static String getPrivateKeyStr(Map<String, Object> keyMap) {
        //获得map中的私钥对象 转为key对象
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    //编码返回字符串
    public static String encryptBASE64(byte[] key) {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    //解码返回byte
    public static byte[] decryptBASE64(String key) {
        byte[] bytes = null;
        try {
            return (new BASE64Decoder()).decodeBuffer(key);
        } catch (IOException e) {
            return bytes;
        }
    }

    public static void main(String[] args) {
        Map<String, Object> keyMap = initKey();
        String publicKey = getPublicKeyStr(keyMap);
        log.info("JAVA生成RSA公钥:{}", publicKey);
        String privateKey = getPrivateKeyStr(keyMap);
        log.info("JAVA生成RSA私钥:{}", privateKey);
    }
}

注意:

本文中请求和响应类中的私钥和公钥均是不完整的,在请求和响应类中的私钥和公钥由RSASignUtils工具类生成后,再替代入请求和响应类中,请求类中使用私钥加签,响应类中使用公钥验签。