子龙 子龙
首页
学习指南
工具
AI副业
开源项目
技术书籍

程序员子龙

Java 开发从业者
首页
学习指南
工具
AI副业
开源项目
技术书籍
  • 基础

  • JVM

  • Spring

  • 并发编程

  • Mybatis

  • 网络编程

  • 数据库

  • 缓存

  • 设计模式

  • 分布式

  • 高并发

  • SpringBoot

  • SpringCloudAlibaba

  • Nginx

  • 面试

  • 生产问题

  • 系统设计

    • API接口安全设计
    • PO、VO、DAO、BO、DTO、POJO 能分清吗?
    • 别再写满屏的 try catch 了,教你如何统一处理异常!
    • 面试官:生成订单30分钟未支付,则自动取消,该怎么实现?
    • 前后端分离必备的接口规范
    • 如何做到百万数据半小时跑批结束
    • 我总结了写出高质量代码的12条建议
    • 线上的接口响应时间太长,怎么排查?
    • Linux部署Grafana
    • 接口被刷了,怎么办?
    • SpringBoot + vue前后端数据传输加密
      • 前言
      • RAS 非对称加密
      • 代码实现
        • Java代码
        • 生成密钥
        • 公钥加密
        • 私钥解密
        • 测试类
        • VUE代码
        • 引入加密库
        • 加密
        • 解密
        • 测试类
      • 总结
    • 面试官:业务开发时,接口不能对外暴露怎么办?有 3 种实现方案!
  • 消息中间件

  • Java
  • 系统设计
程序员子龙
2024-07-29
目录

SpringBoot + vue前后端数据传输加密

# 前言

在前后端交互时,常常采取http方式进行传输,而明文传输通常会被网络抓包、反编译等手段得到http通讯接地址和参数等。

为了确保信息的安全,在生产中使用了很多种加密手段。

# RAS 非对称加密

RSA也就是非对称加密算法,是使用不同密钥进行加密和解密的算法,也称为公私钥加密。公钥和私钥是同时生成的,公钥用来加密,私钥用来解密,加解密的密钥是成对出现。但是不推荐用RSA加密请求报文,因为RSA加密后的报文会很长,经常会出现超过请求体长度限制。优点是安全性高,缺点是RSA的加密效率低。

该加密方式存在缺点,明文的长度不能超过 117 bytes,如果超长,就会产生异常

javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
	at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:346)
	at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:391)
	at javax.crypto.Cipher.doFinal(Cipher.java:2168)
	at com.xsaas.hr.stockapp.base.utils.RsaUtils.encrypt(RsaUtils.java:137)
	at com.xsaas.RasTest.testRas(RasTest.java:122)
1
2
3
4
5
6

可以通过分段加密的方式进行处理此种问题。但是 JS 前端代码中分段加密存在缺陷,偶尔会有数据无法解密的情况,分段加解密也存在代码繁琐等问题(常规加密方式是直接调用库,这里在调用库的基础上,还需要加工代码,可能这也是导致解密失败的原因)

# 代码实现

# Java代码

# 生成密钥

    /**
     * RAS非对称加密,随机生成密钥对
     *
     * @return 密钥对
     */
    public static Map<String, String> genKeyPair() {
        // KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        // 初始化密钥对生成器,密钥大小为96-1024位
        assert keyPairGen != null;
        keyPairGen.initialize(1024, new SecureRandom());
        // 生成一个密钥对,保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();   // 得到私钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();  // 得到公钥
        // 将公钥和私钥保存到Map
        Map<String, String> res = new HashMap<String, String>(2) {{
            put(AdminConstant.PUBLIC_KEY, new String(Base64.encodeBase64(publicKey.getEncoded())));
            put(AdminConstant.PRIVATE_KEY, new String(Base64.encodeBase64((privateKey.getEncoded()))));
        }};
        return res;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 公钥加密

    /**
     * RAS非对称加密: 公钥加密
     *
     * @param str       加密字符串
     * @param publicKey 公钥
     * @return 密文
     */
    public static String encrypt(String str, String publicKey) {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey;
        String outStr = null;

        try {
            pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
            Cipher cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.ENCRYPT_MODE, pubKey);
            outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
        } catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        //RSA加密
        return outStr;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 私钥解密

    /**
     * RSA私钥解密
     *
     * @param str        加密字符串
     * @param privateKey 私钥
     * @return 铭文
     */
    public static String decrypt(String str, String privateKey) {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey;
        //RSA解密
        Cipher cipher;
        String outStr = null;

        try {
            priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
            cipher = Cipher.getInstance("RSA");
            cipher.init(Cipher.DECRYPT_MODE, priKey);
            outStr = new String(cipher.doFinal(inputByte));
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return outStr;
    }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 测试类

    @Test
    public void testRasDemo() {
        JSONObject object = new JSONObject();
        object.put("name", "我是子龙");
        object.put("id", "123456");
        object.put("age", 20);
        // 随机获取秘钥对
        Map<String, String> keyPair = RsaUtils.genKeyPair();
        // 秘钥
        String privateKey = keyPair.get(AdminConstant.PUBLIC_KEY);
        // 公钥
        String publicKey = keyPair.get(AdminConstant.PUBLIC_KEY);
        // 加密后的数据
        String encryptData = null;
        try {
            // 使用公钥加密
            encryptData = RsaUtils.encrypt(JSON.toJSONString(object), publicKey);
        } catch (Exception e) {
            log.error("加密失败", e);
        }
        // 解密后的数据
        String decryptData = null;
        try {
            // 使用私钥解密
            decryptData = RsaUtils.decrypt(encryptData, privateKey);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("原始明文数据={}", object.toJSONString());
        log.info("公钥={}", publicKey);
        log.info("私钥={}", privateKey);
        log.info("加密后的数据={}", encryptData);
        log.info("解密后的数据={}", decryptData);
    }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# VUE代码

# 引入加密库

npm i jsencrypt
import JSEncrypt from 'jsencrypt'
1
2

# 加密

const encrypt = new JSEncrypt();
encrypt.setPublicKey('你的公钥');
var encryptData = encrypt.encrypt(‘你的数据’);// 加密后的字符串
1
2
3

# 解密

const decrypt =new JSEncrypt()
decrypt.setPrivateKey(privateKey)
var decryptData = decrypt.decrypt(msg)
1
2
3

# 测试类

      const privateKey = 'xxxxxx'
      const msg = 'xxxxxxxxxx'
      const encrypt = new JSEncrypt()
      encrypt.setPrivateKey(privateKey)
      console.log('加密前的数据=' + msg)
      console.log('解密后的数据=' + encrypt.decrypt(msg))

1
2
3
4
5
6
7

# 总结

在实际生产环境中,公钥和私钥最好通过后端接口去获取,加上token验证等方式,这样保证安全性,考虑到RAS加密解密的缺点,可以使用RAS+AES结合使用,后续写一篇文章阐述下用法。

上次更新: 2024/07/29, 22:01:05
接口被刷了,怎么办?
面试官:业务开发时,接口不能对外暴露怎么办?有 3 种实现方案!

← 接口被刷了,怎么办? 面试官:业务开发时,接口不能对外暴露怎么办?有 3 种实现方案!→

最近更新
01
保姆级教程 用DeepSeek+飞书,批量写文案、写文章,太高效了
06-06
02
还在为整理视频思维导图发愁?2 种超实用技巧,让你 10 分钟搞定,高效又省心!
06-06
03
熬夜做PPT?AI一键生成高逼格幻灯片,效率提升10倍!
06-06
更多文章>
Theme by Vdoing | Copyright © 2024-2025

    辽ICP备2023001503号-2

  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式