RSA 加密解密,中文不乱码



import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class RSAEncrypt {


    public static void main(String[] args) throws Exception {

        String point = "xxx";
        //生成公钥和私钥
        Map<String, String> keyMap = new HashMap<>();//genKeyPair();
        keyMap.put("publicKey", "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOUDwJVY1JMY4oSHM1+VKeYZ5T2LjQ4wvENnt0TlRoOYDrUen4Nm3GbVKiGTot76gu7xYL1X9PQvDnYLpUVu0mA2oLrXWZj2ByTW83Ehdc5Y9aLXtNzmm4e6PXtuEtXk2sqUZz+XtBBAUMAne4J9G9DAZVPLFxUUJyzVb9cnK6NQIDAQAB");
        keyMap.put("privateKey", "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM5QPAlVjUkxjihIczX5Up5hnlPYuNDjC8Q2e3ROVGg5gOtR6fg2bcZtUqIZOi3vqC7vFgvVf09C8OdgulRW7SYDagutdZmPYHJNbzcSF1zlj1ote03Oabh7o9e24S1eTaypRnP5e0EEBQwCd7gn0b0MBlU8sXFRQnLNVv1ycro1AgMBAAECgYAKkJlCcRsXEG6TKYKc1POiIKWW7ZYpPDcyCQgxYIF6BNfRNRSiHUdpzddZbalJCOi33o5mdLxcNrVXY+CmyPzDyeyNWWX8UcL2Wud8vRlWU7kQ+YcCVyS/nqRLBpHb0QgW7bqzb7fRpnmqhfj+A9hzRaoKxsZ8EWQfvN5UcdmQgQJBAPyTXzHcicK6gsgaXVo8awXsKxT6/bVAq7+FO/F4ckflS3oyABFNnqVRTC3nuQsU6nq3fu1kDwa03NcAa5zZaykCQQDRHEw4N0pnGKdecKTjBlD95B9WI0KCMWHpSOTIZJUIEvKXANX5BFaGHY01BNxDmwVcuecHgG2XH/WyIzVeJEQtAkEA8WM/NX4aQvrRhsB7u4PGnPBq9DA0TQeznOSOt2ZvgfrIOc6TdfYCyuh5r92oYcjpl8LLEcHxAm3UKb8DGfJIkQJAAjyYQB2vSQ0FdUglK1x870pKX4R/CJ94maMy90XEJlL1j1Ht9/zo5ARa509G/94fn49JflYMVgp8eUxRHNGsfQJBAKm39ZUaFyuDSpRINHZNHfldasmy9hLyXdTb3sLFj/bPaq0MyORAZPqq6XCu+nnIhVKVyADbXb+8T5kn70lzIbg=");
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        System.out.println(publicKey);
        System.out.println(privateKey);
        //加密字符串
        String message = "这是亲前端请求的入参";
        System.out.println("-------------------------------------------------------");
        System.out.println("前端请求的原数据 :" + message);
        System.out.println("-------------------------------------------------------");
        String messageEn = publicKeyEncrypt(message, publicKey, point);
        System.out.println("前端请求的加密 :" + messageEn);
        System.out.println("-------------------------------------------------------");
        String messageDe = privateKeyDecrypt(messageEn, privateKey,point);
        System.out.println("后端解密出来的数据 :" + messageDe);
        System.out.println("-------------------------------------------------------");

        //模拟前端数据展示处理
        //私钥加密,公钥解密
        String s = privateKeyEncrypt(messageDe, privateKey,point);
        System.out.println("-------------------------------------------------------");
        System.out.println("后端返回的加密数据 :"+s);
        System.out.println("-------------------------------------------------------");
        String s1 = publicKeyDecrypt(s, publicKey,point);
        System.out.println("前端解密出来显示的数据 :"+s1);
        System.out.println("-------------------------------------------------------");

    }

    /**
     * 随机生成密钥对
     *
     * @param point
     * @throws NoSuchAlgorithmException
     */
    public static Map<String, String> genKeyPair(String point) throws NoSuchAlgorithmException {
        log.info("{}|开始生成公私钥", point);
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 初始化密钥对生成器,密钥大小为96-1024位
        keyPairGen.initialize(1024, new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        // 得到私钥字符串
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        // 将公钥和私钥保存到Map
        Map<String, String> map = new HashMap<>();
        map.put("publicKey", publicKeyString);
        map.put("privateKey", privateKeyString);
        log.info("{}|生成的公私钥|map:{}", point, map);
        return map;
    }

    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     * 问题场景:
     * Cipher提供加解密API,其中RSA非对称加密解密内容长度是有限制的,加密长度不超过117Byte,解密长度不超过128Byte,
     * 报错如下:javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes。
     * @throws Exception 加密过程中的异常信息
     */
    public static String publicKeyEncrypt(String str, String publicKey, String point) throws Exception {
        log.info("{}|RSA公钥加密前的数据|str:{}|publicKey:{}", point, str, publicKey);
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").
                generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes("UTF-8")));
        log.info("{}|公钥加密后的数据|outStr:{}", point, outStr);
        return outStr;
    }


    /**
     * RSA公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return  当入参超过117个字节时,会分段进行加密
     * 问题场景:
     * @throws Exception 加密过程中的异常信息
     */
    public static List<String> publicKeyEncryptBigStr(String str, String publicKey, String point) throws Exception {
        List<String> resultList = new ArrayList<>();
        log.info("{}|RSA公钥加密前的数据|str:{}|publicKey:{}", point, str, publicKey);
        // 分割后的入参

        List<String> listStr = split(str);
        for (String s : listStr) {
            //base64编码的公钥
            byte[] decoded = Base64.decodeBase64(publicKey);
            RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").
                    generatePublic(new X509EncodedKeySpec(decoded));
            //RSA加密
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            String outStr = Base64.encodeBase64String(cipher.doFinal(s.getBytes("UTF-8")));
            resultList.add(outStr);
            log.info("{}|公钥加密后的数据|outStr:{}",point,outStr);
        }
        return resultList;
    }

    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @param point
     * @return 铭文
     * @throws Exception 解密过程中的异常信息
     */
    public static String privateKeyDecrypt(String str, String privateKey, String point) throws Exception {
        log.info("{}|RSA私钥解密前的数据|str:{}|privateKey:{}", point, str, privateKey);
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA")
                .generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        String outStr = new String(cipher.doFinal(inputByte));
        log.info("{}|RSA私钥解密后的数据|outStr:{}", point, outStr);
        return outStr;
    }

    /**
     * RSA私钥加密
     *
     * @param str
     * @param privateKey
     * @return
     * @throws Exception
     */
    public static String privateKeyEncrypt(String str, String privateKey, String point) throws Exception {
        log.info("{}|RSA私钥加密前的数据|str:{}|publicKey:{}", point, str, privateKey);
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        PrivateKey priKey = KeyFactory.getInstance("RSA").
                generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, priKey);
        String outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes()));
        log.info("{}|RSA私钥加密后的数据|outStr:{}", point, outStr);
        return outStr;
    }

    /**
     * RSA公钥解密
     *
     * @param str
     * @param publicKey
     * @return
     * @throws Exception
     */
    public static String publicKeyDecrypt(String str, String publicKey, String point) throws Exception {
        log.info("{}|RSA公钥解密前的数据|str:{}|publicKey:{}", point, str, publicKey);
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes("UTF-8"));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        PublicKey pubKey = KeyFactory.getInstance("RSA")
                .generatePublic(new X509EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, pubKey);
        String outStr = new String(cipher.doFinal(inputByte));
        log.info("{}|RSA公钥解密后的数据|outStr:{}", point, outStr);
        return outStr;
    }


    public static List<String> split(String originalString) {
        int chunkSize = 30;
        List<String> chunks = splitString(originalString, chunkSize);
        return chunks;
    }

    public static List<String> splitString(String original, int chunkSize) {
        List<String> chunks = new ArrayList<>();
        for (int i = 0; i < original.length(); i += chunkSize) {
            int endIndex = Math.min(i + chunkSize, original.length());
            chunks.add(original.substring(i, endIndex));
        }
        return chunks;
    }


}

关于中文乱码分析

应用场景 :后端返回数据时,对数据进行RSA加密。

问题1: 后端返回数据过长,RSA无法加密过长的数据

        抛出此异常 :Data must not be longer than 117 bytes。

解决方案(1)(坑) : (此方案返回数据包含中文时,会有概率乱码)

        把返回原始数据转为 byte数组 byte[] inputArray = input.getBytes()

然后把byte数组切割成长度 为117(每次加密117Byte) ,(加密长度不超过117Byte,解密长度不超过128Byte),返回前端数据格式为 List<String> ,此时前端拿到数据解密,发现部分中文乱码

,若返回数据不包含中文,则正常显示。(同样的也不能先Base64)

解决方案 (2):(正确解决)

       把返回的数据提前切割成长度为35为的List<String> ,(为什么切割为35 ?,1个英文字符1占一个字节,一个中文,或一个中文符号占3个字节 假设这35个字符都是中文,则 35*3 = 102,若设置过大,加密会有异常 ) 然后循环加密,返回前端List<String>,前端循环解密,而后正常显示

思考? 为什么方案1会有部分乱码?

        因为一个中文字符为3个字节,会被切开,所以正好切到中文的时候,所以就造成了部分中文乱码,就是因为中文被切断造成的,所以要对原始数据切割,这样就不会把中文截断,就不会造成中文乱码的现象