程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

RSA+AES混合加密-JavaWeb

发布于2021-05-30 12:09     阅读(1638)     评论(0)     点赞(26)     收藏(5)


一、前言

RSA与AES加密的详细介绍这里就不写了,网上很多博客,这里就只是简单说明一下:

  • AES:属于对称加密,通过一个公共的秘钥,实现加密解密;
  • RSA:非对称加密,需要生成一个公钥、一个私钥,这两个秘钥使用时,一个用来加密时,那么就需要另一个秘钥进行解密,公钥一般提供给客户端。

二、整体构思

RSA+AES的混合加密时,AES用于给传输的数据加密,然后通过RSA给AES的秘钥加密,所以接收到数据后,就需要先解密得到AES的秘钥,然后通过AES秘钥再去解密得到数据。
下面简单说下demo中加密解密的实现过程:

  1. 前后端各自生成自己的RSA公私密钥(这就必须确保双方的RSA算法要匹配,不然双方就无法正常解密)
  2. 当访问项目首页时,前端生成RSA秘钥,并存放在window对象中的localStorage
  3. 页面发起请求获取服务端的RSA公钥,服务端收到请求后生成RSA公司秘钥,并将秘钥放入session,所以每次建立会话连接时都是不一样的秘钥,然后将公钥返回给前端页面
  4. 页面接收到服务端的RSA公钥后,存入window对象,然后用服务端RSA公钥加密前端的RSA公钥发送给服务端
  5. 服务端收到前端发过来的请求后,通过自己的私钥解密数据,从而得到前端的公钥,并存入session。

这里面提到的存储秘钥的方式只是在demo中作为演示使用,可以采用更合理、更安全的方式是实现!

这样,前后端都拥有的对方RSA的公钥,后面在同一个会话中具体的请求数据时,每次各自都会生成新的AES秘钥(AES的算法也需要前后端能匹配上),RSA的秘钥则在响应位置去取就可以了。
在这里插入图片描述
在这里插入图片描述

三、主要代码

1、服务端

两个加密解密工具类,里面部分有使用第三方jar(hutool-all.jar)。

  • AESUtil
package com.lr.demo.util;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

public class AESUtil {
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法

    public static String getKey(int len){
        if(len % 16 != 0){
            System.out.println("长度要为16的整数倍");
            return null;
        }

        char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
        char[] uuid = new char[len];

        if (len > 0) {
            for (int i = 0; i < len; i++) {
                int x = (int) (Math.random() * (len - 0 + 1) + 0);
                uuid[i] = chars[x % chars.length];
            }
        }

        return new String(uuid);
    }


    public static String byteToHexString(byte[] bytes){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String strHex=Integer.toHexString(bytes[i]);
            if(strHex.length() > 3){
                sb.append(strHex.substring(6));
            } else {
                if(strHex.length() < 2){
                    sb.append("0" + strHex);
                } else {
                    sb.append(strHex);
                }
            }
        }
        return  sb.toString();
    }

    /**
     * AES 加密操作
     *
     * @param content 待加密内容
     * @param key 加密密码
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(String content, String key) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器

            byte[] byteContent = content.getBytes("utf-8");

            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器

            byte[] result = cipher.doFinal(byteContent);// 加密

            return org.apache.commons.codec.binary.Base64.encodeBase64String(result);//通过Base64转码返回
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content
     * @param key
     * @return
     */
    public static String decrypt(String content, String key) {

        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);

            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));

            //执行操作
            byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(content));

            return new String(result, "utf-8");
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    private static SecretKeySpec getSecretKey(final String key) throws UnsupportedEncodingException {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
        KeyGenerator kg = null;

        try {
            kg = KeyGenerator.getInstance(KEY_ALGORITHM);

            //AES 要求密钥长度为 128
            kg.init(128, new SecureRandom(key.getBytes()));

            //生成一个密钥
            SecretKey secretKey = kg.generateKey();

            return new SecretKeySpec(Arrays.copyOf(key.getBytes("utf-8"), 16), KEY_ALGORITHM);// 转换为AES专用密钥
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
        }

        return null;
    }

}

  • RSAUtil
package com.lr.demo.util;

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import org.springframework.util.Base64Utils;

import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;

public class RSAUtil {

    public static final String PB_KEY = "pb_key";
    public static final String PR_KEY = "pr_key";

    public static final String CLI_PB_KEY = "cli_pb_key";

    /**
     * 获取公私秘钥对
     * @return
     */
    public static Map<String, Key> getRSAKey(){
        KeyPair pair = SecureUtil.generateKeyPair("RSA");
        PrivateKey privateKey = pair.getPrivate();
        PublicKey publicKey = pair.getPublic();

        Map<String, Key> keys = new HashMap<>();
        keys.put(PR_KEY,privateKey);
        keys.put(PB_KEY,publicKey);

        return keys;
    }

    /**
     * 公钥加密
     * @param pbKey
     * @param content
     * @return
     */
    public static String encByPbKey(String pbKey,String content){
        try {
            byte[] bytes = Base64Utils.decode(pbKey.getBytes("UTF-8"));
            RSA rsa = new RSA(null,bytes);
            byte[] enc = rsa.encrypt(content.getBytes("UTF-8"), KeyType.PublicKey);
            String s = new String(Base64Utils.encode(enc));
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公钥加密
     * @param pbKey
     * @param content
     * @return
     */
    public static String encByPbKey(PublicKey pbKey,String content){
        try {
            RSA rsa = new RSA(null,pbKey);
            byte[] enc = rsa.encrypt(content.getBytes("UTF-8"), KeyType.PublicKey);
            String s = new String(Base64Utils.encode(enc));
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     * @param prKey
     * @param content
     * @return
     */
    public static String dencByPrKey(String prKey,String content){
        try {
            byte[] bytes = Base64Utils.decode(prKey.getBytes("UTF-8"));
            RSA rsa = new RSA(bytes,null);
            byte[] denc = rsa.decrypt(Base64Utils.decode(content.getBytes("UTF-8")), KeyType.PrivateKey);
            String s = new String(Base64Utils.encode(denc));
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     * @param prKey
     * @param content
     * @return
     */
    public static String dencByPrKey(PrivateKey prKey,String content){
        try {
            RSA rsa = new RSA(prKey,null);
            byte[] denc = rsa.decrypt(Base64Utils.decode(content.getBytes("UTF-8")), KeyType.PrivateKey);
            String s = new String(denc);
            return s;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

}

l两个Controller,一个是初始化是秘钥交换的,一个用于测试

package com.lr.demo.controller;

import com.lr.demo.commons.Result;
import com.lr.demo.util.RSAUtil;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;
import java.security.Key;
import java.security.PrivateKey;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("secret")
public class SecretController {

    /**
     * 返回服务端的RSA公钥
     * @param session
     * @return
     */
    @RequestMapping("getKey")
    public Result getKey(HttpSession session){
        Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys");
        if(rsaKey == null){
            rsaKey = RSAUtil.getRSAKey();
            session.setAttribute("keys",rsaKey);
        }

        byte[] encode = Base64Utils.encode(rsaKey.get(RSAUtil.PB_KEY).getEncoded());
        return Result.success(new String(encode));

    }

    /**
     * 分段解密发送过来的客户端RSA公钥
     * @param map
     * @param session
     * @return
     */
    @RequestMapping("acceptKey")
    public Result acceptKey(@RequestBody Map<String,Object> map, HttpSession session){

        List<String> clientKeys = (List<String>) map.get("clientKey");
        System.out.println("clientKey:" + clientKeys);
        Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys");

        String cli_key = "";
        if(clientKeys != null){
            for (String item : clientKeys) {
                cli_key += RSAUtil.dencByPrKey((PrivateKey) rsaKey.get(RSAUtil.PR_KEY), item);
            }
        }
        session.setAttribute(RSAUtil.CLI_PB_KEY,cli_key);
        System.out.println("解密后客户端公钥:" + cli_key);
        return Result.success();
    }

}

package com.lr.demo.controller;


import com.alibaba.fastjson.JSON;
import com.lr.demo.commons.Constant;
import com.lr.demo.commons.Result;
import com.lr.demo.util.AESUtil;
import com.lr.demo.util.RSAUtil;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.security.Key;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("sys")
public class SystemController {

    @RequestMapping("login")
    public Result login(@RequestBody String data, HttpServletRequest request,HttpServletResponse response){
        String plaintext = dencrypt(request, data);
        return Result.success(encrypt("登录成功啦",response));
    }


    private String dencrypt(HttpServletRequest request,String data){
        // 从session中获取服务端RSA的私钥
        HttpSession session = request.getSession();
        Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys");

        HashMap<String,String> hashMap = JSON.parseObject(data, HashMap.class);
        // 获取客户端发送的加密数据
        String enc_data = hashMap.get(Constant.ENCRYPT_DATA);
        System.out.println("获取请求数据---->:" + enc_data);
        // 获取发送过来的AES秘钥
        String enc_aes_key = request.getHeader(Constant.ENCRYPT_AES_KEY);
        // 解密AES秘钥
        String aes_key = RSAUtil.dencByPrKey((PrivateKey) rsaKey.get(RSAUtil.PR_KEY), enc_aes_key);
        // AES解密
        String plaintext = AESUtil.decrypt(enc_data, aes_key);

        System.out.println("解密数据---->:" + plaintext);
        return plaintext;
    }

    public Map<String, String> encrypt(String data, HttpServletResponse response){
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpSession session = request.getSession();
        String cliKey = (String) session.getAttribute(RSAUtil.CLI_PB_KEY); // 获取客户端RSA公钥
        String aesKey = AESUtil.getKey(16); // 获取AES秘钥
        // RSA加密AES秘钥
        String encrypt_aes_key = RSAUtil.encByPbKey(cliKey, aesKey);
        // AES加密返回数据
        String encrypt_data = AESUtil.encrypt(data, aesKey);
        // 添加响应头(AES秘钥)
        response.addHeader(Constant.ENCRYPT_AES_KEY, encrypt_aes_key);
        Map<String,String> map = new HashMap<>();
        map.put(Constant.ENCRYPT_DATA,encrypt_data);
        return map;
    }
}

2、前端

前端涉及的文件较多,这里就只展示下页面,其余详细代码可以下载源码后查看。

  • aes_v1.0.js:AES加解密
  • rsa.js、crypto-js.js:RSA加解密
  • demo.js:封装的函数

index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
    <script src="static/js/aes_v1.0.js"></script> 
    <script src="static/js/rsa.js"></script>
    <script src="static/js/crypto-js.js"></script>
    <script src="static/js/demo.js"></script>
    <script src="static/js/jquery-3.5.1.js"></script>
  </head>
  <body>

  <form action="<%=request.getContextPath()%>/sys/login" id="loginForm">
      <input type="text" name="username" value="">
      <input type="password" name="password" value="">
      <input type="button" value="登录" id="loginBtn">
  </form>

  <script>
      // 首页加载时,秘钥生成和交换
      getRsaKeys(f)
  </script>
  
  <script>

      function getFormJson(formJqueryObj) {
          var o = {};
          var a = formJqueryObj.serializeArray();
          $.each(a, function () {
              if (o[this.name]) {
                  if (!o[this.name].push) {
                      o[this.name] = [o[this.name]];
                  }
                  o[this.name].push(this.value || '');
              } else {
                  o[this.name] = this.value || '';
              }
          });
          return o;
      }


      $('#loginBtn').click(function () {
          var json = getFormJson($('#loginForm'))
          // demo.js封装的函数
          request(json,'<%=request.getContextPath()%>/sys/login',function (res) {
              console.log(res)
          })
      })
  </script>
  </body>
</html>

四、测试

启动项目,打开首页:
在这里插入图片描述
服务端日志:
在这里插入图片描述

当交换完秘钥后,进行登录测试:
在这里插入图片描述
服务端就收时的日志输出:
在这里插入图片描述
响应后页面的输出:
在这里插入图片描述
在这里插入图片描述
以上就是一个简单的demo,源码点击下载




所属网站分类: 技术文章 > 博客

作者:Jjxj

链接:http://www.qianduanheidong.com/blog/article/116124/ac0d1121eeeee2e32027/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

26 0
收藏该文
已收藏

评论内容:(最多支持255个字符)