官网 

jsencrypt :JSEncrypt

encryptlong:encryptlong - npm

前端

1,安装

1.1 安装jsencrypt,执行以下命令

npm install jsencrypt --save-dev

1.2 安装encryptlong,执行以下命令:

npm i encryptlong -S

2,创建rsa.js文件

2.1 在src/util/文件夹下创建rsa.js文件

2.2 引入‘jsencrypt’,‘encryptlong’

/* 产引入jsencrypt实现数据RSA加密 */
import JSEncrypt from 'jsencrypt' // 处理长文本数据时报错 jsencrypt.js Message too long for RSA
/* 产引入encryptlong实现数据RSA加密 */
import Encrypt from 'encryptlong' // encryptlong是基于jsencrypt扩展的长文本分段加解密功能。

 2.3 准备publicKey(公钥)&& privateKey(私钥)

1、 密钥对生成地址 http://web.chacuo.net/netrsakeypair

2、Windows系统可以使用git命令行工具

  • 单击鼠标右键——git bash here 调出git bash
  • 生成私钥,密钥长度为1024bit
$ openssl genrsa -out private.pem 1024
  • 把pkcs1格式转为pkcs8格式(java侧使用的是pkcs8)
    $ openssl pkcs8 -topk8 -inform PEM -in private.pem -outform pem -nocrypt -out pkcs8.pem

    PKCS1的文件头格式 -----BEGIN RSA PRIVATE KEY-----
    PKCS8的文件头格式 -----BEGIN PRIVATE KEY-----

  • 从私钥中提取公钥
  • $ openssl rsa -in pkcs8.pem -pubout -out public.pem

2.4 写完整的加密解密函数

export default {
  /* JSEncrypt加密 */
  rsaPublicData(data) {
    var jsencrypt = new JSEncrypt()
    jsencrypt.setPublicKey(publicKey)
    // 如果是对象/数组的话,需要先JSON.stringify转换成字符串
    var result = jsencrypt.encrypt(data)
    return result
  },
  /* JSEncrypt解密 */
  rsaPrivateData(data) {
    var jsencrypt = new JSEncrypt()
    jsencrypt.setPrivateKey(privateKey)
    // 如果是对象/数组的话,需要先JSON.stringify转换成字符串
    var result = jsencrypt.encrypt(data)
    return result
  },
  /* 加密 */
  encrypt(data) {
    const PUBLIC_KEY = publicKey
    var encryptor = new Encrypt()
    encryptor.setPublicKey(PUBLIC_KEY)
    // 如果是对象/数组的话,需要先JSON.stringify转换成字符串
    const result = encryptor.encryptLong(data)
    return result
  },
  /* 解密 - PRIVATE_KEY - 验证 */
  decrypt(data) {
    const PRIVATE_KEY = privateKey
    var encryptor = new Encrypt()
    encryptor.setPrivateKey(PRIVATE_KEY)
    // 如果是对象/数组的话,需要先JSON.stringify转换成字符串
    var result = encryptor.decryptLong(data)
    return result
  }
}

2.5在main.js主文件引入,将Rsa注册为公共方法,方便其他页面调用

import Rsa from "@/utils/rsa.js"
Vue.prototype.Rsa = Rsa // 将Rsa注册为公共方法,方便其他页面调用

3,在*.vue 页面使用RSA加解密(demo)

由于已经将Rsa注册为全局公共方法,所以在需要的地方通过 this.Rsa.方法名 引入并使用就可以了,具体操作见下面demo

3.1 demo完整代码

<template>
    <div class="rsa-container">
      <van-row>
        <van-col span="24">
          <img src="../../assets/img/demo/rsa_banner.jpg" height="220" width="100%"/>
          <van-field v-model="rasEncryptData.reqStr" rows="1" autosize label="加密前" type="textarea" placeholder="请输入……" @input="reqTest()"></van-field>
          <van-field v-model="rasEncryptData.encryptStr" rows="1" autosize label="加密后" type="textarea" placeholder="请输入……" readonly></van-field>
          <van-field v-model="rasEncryptData.decryptStr" rows="1" autosize label="解密后" type="textarea" placeholder="请输入……" readonly></van-field>
        </van-col>
      </van-row>
    </div>
</template>
 
<script>
export default {
  data() { // 定义数据
    return {
      rasEncryptData: { // 加密后数据
        reqStr: '', // 加密前数据
        encryptStr: '', // 加密后数据
        decryptStr: '' // 解密后数据
      }
    }
  },
  methods: { // 定义方法
    reqTest() {
      this.rasEncryptData.encryptStr = this.Rsa.encrypt(this.rasEncryptData.reqStr) // 加密
      this.rasEncryptData.decryptStr = this.Rsa.decrypt(this.rasEncryptData.encryptStr) // 解密
    }
  },
  mounted() { // 此时已经将编译好的模板,挂载到了页面指定的容器中显示
  },
  filters: {}, // 定义私有过滤器
  directives: {}, // 定义私有指令
  components: {}, // 定义实例内部私有组件的
  beforeCreate() { }, // 实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
  created() { }, // 实例已经在内存中创建OK,此时data和methods已经创建OK,此时还没有开始编译模板
  beforeMount() { }, // 此时已经完成了模板的编译,但是还没有挂载到页面中
  beforeUpdate() { }, // 状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
  updated() { }, // 实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  beforeDestroy() { }, // 实例销毁之前调用。在这一步,实例仍然完全可用。
  destroyed() { } // Vue实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
}
</script>

 4,前端rsa加密,后端去解密(乱码问题) 

前端rsa加密,后端去解密,但是呢有中文的时候解密出来就乱了,想了各种办法未果;

结论,既然中文乱码,我就不传中文就行了哈。在加密之前用

这个方法很巧妙的利用了解决url加密的工具,何乐而不为呢。

// 前端加密前将所有中文encoder掉
// 此函数是js原生函数
var en = encodeURIComponent(str);
 
 
 
// 后台再转换回来就行了
String result = java.net.URLDecoder.decode(en ,"UTF-8");

4.1 前端公钥私钥都保存。

 

 本人前端使用密钥时包含 密钥的头部 -----BEGIN PUBLIC KEY-----

后端要去掉头部。

后端

5、JAVA RSA后端加解密工具

package com.epf.gp.gp_admin.utils;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
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;

public class RSAUtil {
    // 加密数据和秘钥的编码方式
    public static final String UTF_8 = "UTF-8";

    // 填充方式
    public static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
    public static final String RSA_ALGORITHM_NOPADDING = "RSA";
    //私钥
    public static final String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMsbzWNUjDWM0mgK\n" +
            "RbY7yd/4+2JcsR7R1C+1BCWoeYTA9+lZtmSqQEPxj+W8YAmUw+uvNF6ZGOM3bAlt\n" +
            "4DCgFVaqBUCPYh3gtUjtDoNd6OhQ1hab/i7HRYq+DU5bBfm0386X/GMqCbimGkIi\n" +
            "T8hDxOlSjrl90D6atbfm5aXg3+m/AgMBAAECgYBI+CcU7ZYl0v7wo+ghrV88mR3v\n" +
            "W6/Ci834iccApINr30vxyOpPRh8qicmR1tiF+K1eqiZdahFX6FxnEtN9hLXFNhqE\n" +
            "Tr2L3/qChICNHJl/ZG7NDJo4vbrDzDHc6GV/OxUoUjVYOABPt/MhjCWyLg3Ywrul\n" +
            "+kU+ZnFlNdBtJIjquQJBAObeQ4xfATBkP9hrabgpZoedzHKA3Cj2WWBk1S1QIW/C\n" +
            "S3Oe1mTmzi71D2+S6XUsp1IBedkhsRh0VLaJ4m727S0CQQDhN/IHBps+DNX9PPRX\n" +
            "XAVejj6qRl4tPNV1YuAej2RLX7DK/qL38zhP0e3C+qVhLnevi4MZWDRghPRs6brj\n" +
            "dD4bAkB6ypKw0UMHovpWOGMlYVe4H9TCvgja/JPy8g50KF9wWq3Y1A2B40Scsxi/\n" +
            "pirneJWRx1kwhHv13sHDyLiXflwNAkEA2t8NvLo04GaFB6fXJZbuOOgwCjZ8i6YV\n" +
            "JBFFES31IDMMkxpHsoOR9DBlyhjf48PO2LDSZS0NKcfL6pRy5V0whQJAIcxeDmSm\n" +
            "CIxDd4F+CRvu4aUBvKc/vWAnwtgO+SNboqbv07AjjvDoH4henkcWECPxpDAOPKte\n" +
            "tB7VUhbOfBxsNA==";


    /**
     * 非对称加密,根据公钥和原始内容产生加密内容
     */
    private static String encryptRSA(Key key, String plainText)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException,
            BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, key);
        return Base64.encodeBase64(cipher.doFinal(plainText.getBytes(UTF_8))).toString();
    }

    /**
     * 根据私钥和加密内容产生原始内容
     */
    private static String decryptRSA(Key key, String content) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, DecoderException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] contentArry = content.getBytes();
        return new String(cipher.doFinal(Base64.decodeBase64(contentArry)), UTF_8);
    }

    /**
     * 创建RSA的公钥和私钥示例 将生成的公钥和私钥用Base64编码后打印出来
     * @throws NoSuchAlgorithmException
     */
    public static void createKeyPairs() throws NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        System.out.println("公钥"+Base64.encodeBase64(publicKey.getEncoded()));
        System.out.println("私钥"+Base64.encodeBase64(privateKey.getEncoded()));
    }

    /**
     *  Description:默认的RSA解密方法 一般用来解密 参数 小数据
     */
    public static String decryptRSADefault(String privateKeyStr,String data) {
        try{
            KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM_NOPADDING);
            byte[] privateKeyArray = privateKeyStr.getBytes();
            byte[] dataArray = data.getBytes();
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyArray));
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);

            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM_NOPADDING);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return new String(cipher.doFinal(Base64.decodeBase64(dataArray)), UTF_8);
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
    }

    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, DecoderException, InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, BadPaddingException, InvalidKeyException {
        String messageEn = "mLSTatgmYvhk8SGvny5grdYOisiYK2kX4tpnvgRL75GmP1oqjhnQa91u0Ch+/yZnclmKeu6Uid4DEkBBqeQyDj9oeZu8sEsvEHF/yIPbXFe4sKdBC5WbhDEH3TkbCrE/Y0zKQqlfdlbG9nGrbjDCts59w7e1Y/OoVtyryStedJ8=";
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(PRIVATE_KEY);
        PrivateKey priKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
        String s = URLDecoder.decode(RSAUtil.decryptRSADefault(RSAUtil.PRIVATE_KEY,messageEn) ,"UTF-8");
        System.out.println(s);
    }
}

前后端解密不通过情况:

密钥对的获取:主要涉及到两个参数:秘钥格式、秘钥长度

  (1)常见秘钥格式:PKCS8(java适用)、PKCS1(其他),如果不是java平台生成的秘钥,有可能报以下错误: 

java.security.InvalidKeyException: invalid key format //秘钥格式不合法

(2)秘钥长度:1024、2048(越长越安全,效率越低),详细介绍可以参考 :https://blog.csdn.net/luoluo_onion/article/details/78354799

  注意:分段加解密时:1024的秘钥每次加密最大长度为117(128-11),长度为2048秘钥每次加解密长度为256,如果加密时设置的长度不匹配,可能会报以下错误:

javax.crypto.BadPaddingException : Decryption error //解码失败

秘钥获取的代码实现:java.security.KeyPairGenerator提供了生成秘钥的API,只需要调用即可。

public static Map<String, Object> genKeyPair() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);//2048
        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;

    }

 

3、加密实现(公钥加密)

  (1)获取Cipher需要的java.security.Key类型公钥;

  (2)调用getInstance()方法获取cipher实例,参数为填充方式(加密和解密的填充方式要统一,否则解密失败!)

  (3)使用公钥初始化cipher实例,同时设置加解密类型

  (4)对数据进行分段加密,得到字节数组。

private static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
        byte[] keyBytes = Base64.decodeBase64(publicKey);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        Key publicK = keyFactory.generatePublic(x509KeySpec);
        // 对数据加密
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicK);
        int inputLen = data.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > 256) {
                cache = cipher.doFinal(data, offSet, 256);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * 256;
        }
        byte[] encryptedData = out.toByteArray();
        out.close();
        return encryptedData;

    }

4、解密实现

  (1)获取Cipher需要的java.security.Key类型私钥;

  (2)调用getInstance()方法获取cipher实例,参数为填充方式(加密和解密的填充方式要统一,否则解密失败!)

  (3)使用私钥初始化cipher实例,同时设置类型为解密类型

  (4)分段解密,得到字节数组。

private static byte[] decryptByPrivateKey(byte[] data, String privateKey) throws Exception {
        // 得到私钥  
        byte[] keyBytes = Base64.decodeBase64(privateKey.getBytes());
        PKCS8EncodedKeySpec pKCS8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        Key key = keyFactory.generatePrivate(pKCS8EncodedKeySpec);
        // 解密数据
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, key);
       // SealedObject obj = new SealedObject(data, cipher);
        int inputLen = data.length;
        System.out.println(data);
        System.out.println(inputLen);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解�?
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(data, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedData = out.toByteArray();
        out.close();
        //byte[] messageBytes = (byte[]) obj.getObject(cipher);
        return decryptedData;
    }

 

5\由于本人是首次接触RSA,并且对加密算法一片空白,所以过程中几乎把能遇见的错误碰了个遍,总结以下几个常见错误,引以为戒:

(1)java.security.InvalidKeyException: invalid key format

秘钥格式不合法,或者是秘钥被修改了。

java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : DerInputStream.getLength(): lengthTag=111, too big. 

    秘钥字符串主体前后有空格,或者是换行,或者有多余字段,如(-----BEGIN RSA PRIVATE KEY-----)(秘钥格式不正确)

(3)java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence

    原因可能是没有使用 PKCS8(java适用)

(4)javax.crypto.BadPaddingException : Decryption error

    解密时报这个错误,很可能是填充格式不匹配,请确认加密时使用的填充格式,解密时保持统一即可,我这里使用功能的是RSA/ECB/PKCS1Padding

(5)javax.crypto.BadPaddingException: Message is larger than modulus

    解密时出现这个错误(这个错误我是羞于启齿的),可能是因为要解密的字符串中混入了什么奇怪的东西,你可以在解密前打印下要解密的字符串,再打印下转为base64后的字节数组长度(正常应该为128的倍数),否则应该是手残多加了什么东西呢。

前端参考:VUE项目使用RSA加解密(小白版)_江小白写bug的博客-CSDN博客_vue使用rsa加密

密钥生成参考:

(34条消息) 利用jsencrypt.js 进行前端RSA加密,java后端解密_tang89176的博客-CSDN博客h​​​​​​​hh​​​​​​​​​​​​​​a​​​​​​​

 解密异常参考:RSA加解密的java实现(及中途的各种异常情况分析) - 聪明的娃 - 博客园

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐