RSA 加密解密,客户端JS加密,服务端JAVA解密

Java
223
0
0
2023-09-17

因为 RSA 加密的代码都是比较通用的,所以没有特意去整合,这里参照着两位大神的代码重新写了一遍,做了一些简单的修改,符合本地运行环境

服务端代参照:

客户端代码参照:

JS加密依赖:jsencrypt.jsGithub地址:可客户端尽量依赖JAVA自带的Jar,只是Base64加密的时候额外依赖了apache的工具类commons-net-3.3.jar

服务端工RSA工具类

 package com.wzh.config.utils;

import org.apache.commons.net.util.Base;
import org.apache.logj.Logger;

import javax.crypto.Cipher;
import java.io. byte ArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCSEncodedKeySpec;
import java.security.spec.XEncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * <RSA加密解密工具类>
 * <额外依赖 commons-net-.3.jar,日志用的log4j,如果是其他的日志框架可以更改>
 * @author wzh
 * @version-12-16 18:20
 * @see [相关类/方法] (可选)
 **/public class RsaUtils
{
    
    private static Logger log = Logger.getLogger(RsaUtils.class);
    
    /**
     * 块加密大小
     */    private static final int  cache _SIZE = 1024;
    
    /**
     * 加密算法RSA
     */    public static final String KEY_ALGORITHM = "RSA";
    
    /**
     * 签名算法
     */    public static final String SIGNATURE_ALGORITHM = "MDwithRSA";
    
    /**
     * 获取公钥的key
     */    private static final String PUBLIC_KEY = "RsaPublicKey";
    
    /**
     * 获取 私钥 的key
     */    private static final String PRIVATE_KEY = "RsaPrivateKey";
    
    /**
     * RSA最大加密明文大小
     */    private static final int MAX_ENCRYPT_BLOCK =;
    
    /**
     * RSA最大解密密文大小
     */    private static final int MAX_DECRYPT_BLOCK =;
    
    /**
     * Base 字符串 解码为二进制数据
     * @param base
     * @return 二进制数据
     * @throws Exception
     */    public static byte[] decodeBase(String base64)
        throws Exception
    {
        return Base.decodeBase64(base64.get bytes ());
    }
    
    /**
     * 二进制数据编码为Base字符串
     * @param bytes
     * @return Base字符串
     * @throws Exception
     */    public static String encodeBase(byte[] bytes)
        throws Exception
    {
        return new String(Base.encodeBase64(bytes));
    }

    /**
     * 生成秘钥对
     * @return 返回公钥和私钥的Map集合
     * @throws Exception
     */    public static Map<String, Object> initKeyPair()
        throws Exception
    {
        KeyPairGenerator keyPairGen = KeyPairGenerator. getInstance (KEY_ALGORITHM);
        keyPairGen.initialize(CACHE_SIZE);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey)keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();

        Map<String, Object> keyMap = new HashMap<String, Object>();
        // 公钥
        keyMap.put(PUBLIC_KEY, publicKey);
        // 私钥
        keyMap.put(PRIVATE_KEY, privateKey);

        return keyMap;
    }

    /**
     * 获取私钥
     * @param keyMap 秘钥对Map
     * @return 私钥字符串
     * @throws Exception
     */    public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        return encodeBase(key.getEncoded());
    }

    /**
     * 获取公钥字符串
     * @param keyMap 秘钥对Map
     * @return 公钥字符串
     * @throws Exception
     */    public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        return encodeBase(key.getEncoded());
    }

    /**
     * 使用私钥生成数字签名
     * @param data 使用私钥加密的数据
     * @param privateKey 是哟啊字符串
     * @return 数字签名
     * @throws Exception
     */    public static String sign(byte[] data, String privateKey) throws Exception {
        // 获取byte数组
        byte[] keyBytes = decodeBase(privateKey);
        // 构造PKCSEncodedKeySpec对象
        PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        // 指定的加密算法
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 取私钥匙对象
        PrivateKey privateK = keyFactory.generatePrivate(pkcsKeySpec);
        // 用私钥对信息生成数字签名
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        signature.initSign(privateK);
        signature.update(data);
        return encodeBase(signature.sign());
    }

    /**
     * 校验数字签名
     * @param data 私钥加密的数据
     * @param publicKey 公钥字符串
     * @param sign 私钥生成的签名
     * @return 校验成功返回true 失败返回false
     * @throws Exception
     */    public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
        // 获取byte数组
        byte[] keyBytes = decodeBase(publicKey);
        XEncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        // 构造XEncodedKeySpec对象
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        // 指定的加密算法
        Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
        // 取公钥匙对象
        PublicKey publicK = keyFactory.generatePublic(keySpec);
        signature.initVerify(publicK);
        signature.update(data);

        // 验证签名是否正常
        return signature.verify(decodeBase(sign));
    }

    /**
     * 私钥加密
     * @param data 需要加密的数据
     * @param privateKey 私钥
     * @return 加密后的数据
     * @throws Exception
     */    public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        byte[] keyBytes = decodeBase(privateKey);
        PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcsKeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, privateK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet =;
        byte[] cache;
        int i =;
        // 对数据分段加密
        while (inputLen - offSet >) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache,, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 公钥加密
     * @param data 需要加密的数据
     * @param publicKey 公钥字符串
     * @return 加密后的数据
     * @throws Exception
     */    public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        byte[] keyBytes = decodeBase(publicKey);
        XEncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(xKeySpec);
        // 对数据加密
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet =;
        byte[] cache;
        int i =;
        // 对数据分段加密
        while (inputLen - offSet >) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache,, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;
    }

    /**
     * 私钥解密
     * @param encryptedData 公钥加密的数据
     * @param privateKey 私钥字符串
     * @return 私钥解密的数据
     * @throws Exception
     */    public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
        byte[] keyBytes = decodeBase(privateKey);
        PKCSEncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key privateK = keyFactory.generatePrivate(pkcsKeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet =;
        byte[] cache;
        int i =;
        // 对数据分段解密
        while (inputLen - offSet >) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache,, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥解密
     * @param encryptedData 私钥加密的数据
     * @param publicKey 公钥字符串
     * @return 公钥解密的数据
     * @throws Exception
     */    public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
        byte[] keyBytes = decodeBase(publicKey);
        XEncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key publicK = keyFactory.generatePublic(xKeySpec);
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, publicK);
        int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet =;
        byte[] cache;
        int i =;
        // 对数据分段解密
        while (inputLen - offSet >) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache,, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        return decryptedData;
    }

    /**
     * 公钥加密方法
     * @param data 需加密的字符串
     * @param PUBLICKEY 公钥字符串
     * @return 加密后的字符串
     */    public static String encryptedDataByPublic(String data, String PUBLICKEY) {
        try {
            data = encodeBase(encryptByPublicKey(data.getBytes(), PUBLICKEY));
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage(),e);
        }
        return data;
    }

    /**
     * 私钥解密方法
     * @param data 公钥加密的字符串
     * @param PRIVATEKEY 私钥字符串
     * @return 私钥解密的字符串
     */    public static String decryptDataByPrivate(String data, String PRIVATEKEY) {
        String temp = "";
        try {
            byte[] rs = decodeBase(data);
            //以utf-的方式生成字符串
            temp = new String(decryptByPrivateKey(rs, PRIVATEKEY),"UTF-");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return temp;
    }

    public static void main(String[] args) {
        try {
            Map<String, Object> keyMap = RsaUtils.initKeyPair();
            String publicKey = RsaUtils.getPublicKey(keyMap);
            String privateKey = RsaUtils.getPrivateKey(keyMap);
            System.out.println("公钥:" + publicKey);
            System.out.println("私钥:" + privateKey);
            String source = "我是需要私钥加密的字符串!";
            System.out.println("签名验证逻辑,私钥加密--公钥解密,需要加密的字符串:" + source);
            byte[] data = source.getBytes();
            byte[] encodedData = RsaUtils.encryptByPrivateKey(data, privateKey);
            System.out.println("私钥加密后:" + new String(encodedData));
            String sign = RsaUtils.sign(encodedData, privateKey);
            System.out.println("签名:" + sign);
            boolean status = RsaUtils.verify(encodedData, publicKey, sign);
            System.out.println("验证结果:" + status);
            byte[] decodedData = RsaUtils.decryptByPublicKey(encodedData, publicKey);
            String target = new String(decodedData);
            System.out.println("公钥解密私钥加密的数据:" + target);

            System.out.println("---------公钥加密----私钥解密----------");
            // 这里尽量长一点,复制了一段歌词
            String msg = "月溅星河,长路漫漫,风烟残尽,独影阑珊;谁叫我身手不凡,谁让我爱恨两难,到后来," +
                    "肝肠寸断。幻世当空,恩怨休怀,舍悟离迷,六尘不改;且怒且悲且狂哉,是人是鬼是妖怪,不过是," +
                    "心有魔债。叫一声佛祖,回头无岸,跪一人为师,生死无关;善恶浮世真假界,尘缘散聚不分明,难断!" +
                    "我要这铁棒有何用,我有这变化又如何;还是不安,还是氐惆,金箍当头,欲说还休。我要这铁棒醉舞魔," +
                    "我有这变化乱迷浊;踏碎灵霄,放肆桀骜,世恶道险,终究难逃。";
            String ecodeMsg = RsaUtils.encryptedDataByPublic(msg,publicKey);
            System.out.println("加密后的歌词:" + ecodeMsg);
            String decodeMsg = RsaUtils.decryptDataByPrivate(ecodeMsg,privateKey);
            System.out.println("解密后的歌词:" + decodeMsg);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

首先测试一下工具类,main函数跑一下,成功验证签名,加密,解密

image.png

客户端JS代码,需要JSEncrypt库,前文有给出github地址,这里对这个库做一个简单的扩展,因为RSA长文本超过秘钥长度要报错,所以需要扩展修改下

 /**
 * ---------------------------
 * 此JS需加载JSEncrypt库的后面,加密解密调用着两个方法
 * ---------------------------
 */
/**
 * 长文本加密
 * @param {string} string 待加密长文本
 * @returns {string} 加密后的base编码
* */JSEncrypt.prototype.encryptLong = function (string) {
    var k = this.getKey();
    try {
        var ct = "";
        //RSA每次加密bytes,需要辅助方法判断字符串截取位置
        //.获取字符串截取点
        var bytes = new Array();
        bytes.push();
        var byteNo =;
        var len, c;
        len = string.length;
        var temp =;
        for (var i =; i < len; i++) {
            c = string.charCodeAt(i);
            if (c >=x010000 && c <= 0x10FFFF) {  //特殊字符,如Ř,Ţ
                byteNo +=;
            } else if (c >=x000800 && c <= 0x00FFFF) { //中文以及标点符号
                byteNo +=;
            } else if (c >=x000080 && c <= 0x0007FF) { //特殊字符,如È,Ò
                byteNo +=;
            } else { // 英文以及标点符号
                byteNo +=;
            }
            if ((byteNo %) >= 114 || (byteNo % 117) == 0) {
                if (byteNo - temp >=) {
                    bytes.push(i);
                    temp = byteNo;
                }
            }
        }
        //.截取字符串并分段加密
        if (bytes.length >) {
            for (var i =; i < bytes.length - 1; i++) {
                var str;
                if (i ==) {
                    str = string.substring(, bytes[i + 1] + 1);
                } else {
                    str = string.substring(bytes[i] +, bytes[i + 1] + 1);
                }
                var t = k.encrypt(str);
                ct += t;
            }
            ;
            if (bytes[bytes.length -] != string.length - 1) {
                var lastStr = string.substring(bytes[bytes.length -] + 1);
                ct += k.encrypt(lastStr);
            }
            return hexb64(ct);
        }
        var t = k.encrypt(string);
        var y = hexb64(t);
        return y;
    } catch (ex) {
        console.log(ex);
        return false;
    }
};

/**
 * 长文本解密
 * @param {string} string 加密后的base编码
 * @returns {string} 解密后的原文
 * */JSEncrypt.prototype.decryptLong = function (string) {
    var k = this.getKey();
    var maxLength =;
    try {
        var string = btohex(string);
        var ct = "";
        if (string.length > maxLength *) {
            var lt = string.match(/.{,256}/g);  //128位解密。取256位
            lt.forEach(function (entry) {
                var t = k.decrypt(entry);
                ct += t;
            });
            return ct;
        }
        var y = k.decrypt(string);
        return y;
    } catch (ex) {
        return false;
    }
};

function hexb64(h) {
    var bmap="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var bpadchar="=";
    var i;
    var c;
    var ret = "";
    for(i =; i+3 <= h.length; i+=3) {
        c = parseInt(h.substring(i,i+),16);
        ret += bmap.charAt(c >> 6) + b64map.charAt(c & 63);
    }
    if(i+ == h.length) {
        c = parseInt(h.substring(i,i+),16);
        ret += bmap.charAt(c << 2);
    }
    else if(i+ == h.length) {
        c = parseInt(h.substring(i,i+),16);
        ret += bmap.charAt(c >> 2) + b64map.charAt((c & 3) << 4);
    }
    while((ret.length &) > 0) ret += b64padchar;
    return ret;
}

一个简单的测试页面,就不做前后台衔接了,只是在前提用后台生成的公钥进行加密,然后后台main方法解密一下。

 <!DOCTYPE html>
<html>
  <head>
    <title>MyHtml.html</title>
    <meta name="keywords" content="keyword,keyword2,keyword3">
    <meta name="description" content="this is my page">
    <meta name="content-type" content="text/html; charset=UTF-">
    <script src="${request.contextPath}/js/jquery-.3.1.min.js"></script>
    <script src="${request.contextPath}/js/jsencrypt.js?v="></script>
    <script src="${request.contextPath}/js/RsaJs.js?v="></script>

    <script type="text/javascript">
        $(function() {
            $('#submit').click(function() {
                var data = $('#msg').val();
                // 公钥
                var publickey = $('#publickey').val();

                // 使用jsencrypt库加密前端参数
                var jsencrypt = new JSEncrypt();
                jsencrypt.setPublicKey(publickey);
                // 这里调用长文本的加密方法
                var ecodeMsg = jsencrypt.encryptLong(data);
                $('#ecodeMsg').val(ecodeMsg);

            });
        });

    </script>
  </head>
  
  <body>
  需要加密的内容:</br><textarea id="msg" name="msg" rows="" cols="60"></textarea></br>
  公钥:</br><textarea id="publickey" rows="" cols="60"></textarea></br>
  密文:</br><textarea id="ecodeMsg" rows="" cols="60"></textarea>
  <br/>
  <br/>

  <input id="submit" type="button" value="加密" />
  </body>
  </body>
</html>

简单的测试,页面获取密文

image.png

后台main解密一下

 import com.wzh.config.utils.RsaUtils;

/**
 * <一句话功能描述>
 * <功能详细描述>
 * @author wzh
 * @version-12-16 23:31
 * @see [相关类/方法] (可选)
 **/public class RsaManTest {

    public static void main(String[] args) {
        String msg = RsaUtils.decryptDataByPrivate("XsMCYaNhdx2pJXebCgl3g3pF7FX9KrPY+gtwgbQs0Q1mqJL4VHqQytxOJfUwXHLP/hLck80AWSctJ29/dB4IQ2mSbcO4OInAJMkPwqWsnh1E9bFlFP2KjQ5RBVngb//IiSgBSFo8NR00y1/h47CrNch6ljW1nCLG82Qk2olhfI=",
                "MIICdgIBADANBgkqhkiGw0BAQEFAASCAmAwggJcAgEAAoGBAIlXVcewyLHiE9aidGpcR7hMT5opgXTGUfWX2idJnr2b++Edzg8Id6FOFgmzAWBkoSfAZodkTgZcOUjc/F/ZDQbuSDSBiakVb6T+WK53oVKb8a9Y9Ouq63Bwqihq9kqp9+aLSRcRqyLkF/tGg87dGMEMGeHq2djOI8rrbXGlyQNhAgMBAAECgYAHMCD3MJNwa+qp4xrArk+6n5PS97UkzXRgrC/ounuqZM2L/KlaNBE+yf1xSIMb7mhY0kMLdv52asE8xQQYaB28WVJHxExgcMDDdhtOp+4++WEe2xPWrJSUWSvLQWxrJ01yw9smezt1N0qrA4psJ+eq3JP366wZ3GLFhq0BOW8wGQJBANZyo2Dm1aP4aBHfLSa51YQJ+izFJyC53Hn43CPhXNDT08GO6tuZ9KiIJkk7rNdDrkAFnR4cDwEZT0C6Fk/VWA8CQQCj8+/fGGV+z3D0VCFkPf+VlZdNuITFD+wzlaalJZq5mYtzdHAZ3AGxpNs+Qbykq4TSb5XhfQoZuBeMD3brCv2PAkEA1Wuby4mP3zMOJ5MrtVnG9DSVxU6kxT4T/VO9ivvzSmU2XnDkrY7H3Z46NDHurwHNfivYFSopiJdut2U7ZVJW4wJAc/FsLq7IB9eXH5HnU0Zs2lHBgBr++YT7Gre3844WTy6AaZNsOz1UjVXyHaLLTwBkm5SBv8Z3QBzpugitpiZNjQJAR9SqcarmoE7xNxOJ0gqj2Pht26rAfcQogpoVvbvJTgGlpvEnnV2wEX47mzLFewzQ2nxjwAJPdzr+rOKXKnDCFA==");
        System.out.println(msg);
    }
}

前台加密,后台解密成功