Spring Boot使用RSA模拟简单的登录加密功能

什么是RSA?

RSA是一种公钥密码算法,它的名字由三位开发者,即Ron Rivest、Adi Shamir和Leonard Adleman的姓氏的首字母组成的。

RSA被用于公钥密码和数字签名。

RSA被认为是非常安全的,不过计算速度要比DES慢很多。同DES一样,其安全性也从未被证明过,但想攻破RSA算法涉及的大数(至少200位的大数)的因子分解是一个极其困难的问题。所以,由于缺乏解决大数的因子分解的有效方法,因此,可以推测出目前没有有效的办法可以破解RSA。

RSA加密和解密的过程

在这里插入图片描述
通过一对密钥(公钥和私钥)实现的加密和解密,通过公钥对明文进行加密,使用时再使用私钥进行解密即可。

RSA加密和解密的实现

编写一个工具类,实现方法:创建公钥和私钥、通过公钥加密、通过私钥解密。

package cn.qhj.backtoweb.util;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.util.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.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * @author QHJ
 * @date 2021/11/30  14:21
 * @description: RSA加密
 */
public class RSAUtils {
    protected static final Log log = LogFactory.getLog(RSAUtils.class);
    // 加密的方式
    private static String KEY_RSA_TYPE = "RSA";
    // 转换 算法/反馈模式/填充方案    RSA/ECB/PKCS1Padding (1024, 2048)
    // 加密算法有:AES,DES,DESede(DES3)和RSA 四种
    // 模式有CBC(有向量模式)和ECB(无向量模式),向量模式可以简单理解为偏移量,使用CBC模式需要定义一个IvParameterSpec对象
    private static String KEY_RSA_TYPE_ALL = "RSA/ECB/PKCS1Padding";
    // JDK方式 RSA加密最大只有 1024位
    private static int KEY_SIZE = 1024;
    // 模长
    private static int ENCODE_PART_SIZE = KEY_SIZE / 8;
    // 公钥
    public static final String PUBLIC_KEY_NAME = "public";
    // 私钥
    public static final String PRIVATE_KEY_NAME = "private";

    /**
     * 创建公钥和私钥
     * @return
     */
    public static Map<String, String> createRSAKeys(){
        // 用来存放公钥和私钥的 Base64位加密
        Map<String, String> keyPairMap = new HashMap<String, String>();
        try {
            // 生成公钥和私钥对——给予 RSA算法生成对象
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE);
            // 使用给定的随机源(以及默认参数集)初始化特定密钥大小的密钥对生成器
            keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());

            // 生成密钥对
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 返回对此密钥对的公钥组件的引用
            RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
            // 返回对此密钥对的私钥组件的引用
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

            // 得到公钥字符串和秘钥字符串
            String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded());
            String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());

            // 存入公钥和私钥,以便以后获取
            keyPairMap.put(PUBLIC_KEY_NAME, publicKeyValue);
            keyPairMap.put(PRIVATE_KEY_NAME, privateKeyValue);
        } catch (NoSuchAlgorithmException e) {
            log.error("当前JDK版本没有找到RSA加密算法!");
            e.printStackTrace();
        }
        return keyPairMap;
    }


    /**
     * 公钥加密
     * 描述:1字节 = 8 位,最大加密长度为 128 - 11 = 117 字节,不管多长数据,加密出来都是128字节的长度
     * @param str 加密字符串
     * @param publicKey 公钥
     * @return 密文
     * @throws Exception 加密过程中的异常信息
     */
    public static String encryptByPublicKey(String str, String publicKey){
        // base64 编码的公钥
        byte[] publicBytes = Base64.decodeBase64(publicKey);
        // 公钥加密——按照X509标准对其进行编码的密钥 复制数组的内容,以防随后进行修改。
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);
        // 已经加密过的数据
        List<byte[]> encodedDataList = new LinkedList<byte[]>();

        // 最大加密长度 1024/8/8 - 11
        int maxEncodeSize = ENCODE_PART_SIZE - 11;
        // 所有加密的数据
        String encodeBase64Result = null;
        try {
            // 密钥工厂——用于将密钥(Key类型的不透明加密密钥)转换成密钥规范(底层密钥材料的透明表示)
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
            // 根据提供的密钥规范(密钥材料)生成公钥对象
            PublicKey publicKeys = keyFactory.generatePublic(x509EncodedKeySpec);
            // 为加密和解密提供密码功能(所传递的参数:转换)
            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
            // 用密钥初始化此Cipher    ENCRYPT_MODE:用于将 Cipher初始化为解密模式的常量
            cipher.init(Cipher.ENCRYPT_MODE, publicKeys);

            byte[] strBytes = str.getBytes("utf-8");
            // 获取所有被加密数据长度
            int strLen = strBytes.length;
            // 如果明文长度大于 模长-11 则要分组加密
            for (int i = 0;i < strLen;i += maxEncodeSize){
                int curPosition = strLen - i;
                int tempLen = curPosition;
                if (curPosition > maxEncodeSize){
                    tempLen = maxEncodeSize;
                }
                // 待加密分段数据
                byte[] tempBytes = new byte[tempLen];
                System.arraycopy(strBytes, i, tempBytes, 0, tempLen);
                byte[] tempEncodedData = cipher.doFinal(tempBytes);
                encodedDataList.add(tempEncodedData);
            }

            // 加密次数
            int partLen = encodedDataList.size();
            // 所有加密的长度
            int allEncodedLen = partLen * ENCODE_PART_SIZE;
            // 存放所有 RSA分段加密数据
            byte[] encodeData = new byte[allEncodedLen];
            for (int i = 0;i < partLen;i++){
                byte[] tempByteList = encodedDataList.get(i);
                System.arraycopy(tempByteList, 0, encodeData, i * ENCODE_PART_SIZE, ENCODE_PART_SIZE);
            }
            encodeBase64Result = Base64.encodeBase64String(encodeData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encodeBase64Result;
    }


    /**
     * 私钥解密
     * @param str 解密字符串
     * @param privateKey 私钥
     * @return 明文
     * @throws Exception 解密过程中的异常信息
     */
    public static String decryptByPrivateKey(String str, String privateKey){
        byte[] privateBytes = Base64.decodeBase64(privateKey);
        byte[] encodeStr= Base64.decodeBase64(str);

        // 要解密的数据长度
        int encodePartLen = encodeStr.length / ENCODE_PART_SIZE;
        List<byte[]> decodeListData = new LinkedList<byte[]>();
        // 所有解密的数据
        String decodeStrResult = null;

        // 私钥解密
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
        try {
            // 密钥工厂——用于将密钥(Key类型的不透明加密密钥)转换成密钥规范(底层密钥材料的透明表示)
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
            // 根据提供的密钥规范(密钥材料)生成私钥对象
            PrivateKey privateKeys = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            // 为加密和解密提供密码功能(所传递的参数:转换)
            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
            // 用密钥初始化此Cipher    ENCRYPT_MODE:用于将 Cipher初始化为解密模式的常量
            cipher.init(Cipher.DECRYPT_MODE, privateKeys);

            // 初始化所有被解密数据长度
            int allDecodeByteLen = 0;
            for (int i = 0;i < encodePartLen;i++){
                // 待解密数据分段
                byte[] tempEncodedData = new byte[ENCODE_PART_SIZE];
                System.arraycopy(encodeStr, i * ENCODE_PART_SIZE, tempEncodedData, 0, ENCODE_PART_SIZE);
                byte[] decodePartData = cipher.doFinal(tempEncodedData);
                decodeListData.add(decodePartData);
                allDecodeByteLen += decodePartData.length;
            }

            byte[] decodeResultBytes = new byte[allDecodeByteLen];
            for (int i = 0, curPosition = 0;i < encodePartLen;i++){
                byte[] tempStrBytes = decodeListData.get(i);
                int tempStrBytesLen = tempStrBytes.length;
                System.arraycopy(tempStrBytes, 0, decodeResultBytes, curPosition, tempStrBytesLen);
                curPosition += tempStrBytesLen;
            }
            decodeStrResult = new String(decodeResultBytes, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return decodeStrResult;
    }
}

模拟简单登录加密功能

  • 编写控制层

    /**
     * @author QHJ
     * @date 2021/11/30  18:19
     * @description: 模拟登录(密码加密解密)
     */
    @Controller
    public class LoginController {
        /**
         * 登录之前生成秘钥
         * @param request
         * @return
         */
        @RequestMapping(value = "/login")
        public String login(HttpServletRequest request, HttpServletResponse response) throws IOException {
            Map<String, String> rsaKeys = RSAUtils.createRSAKeys();
            String publicKey = rsaKeys.get(RSAUtils.PUBLIC_KEY_NAME);
            System.out.println("publicKey:" + publicKey);
            String privateKey = rsaKeys.get(RSAUtils.PRIVATE_KEY_NAME);
            System.out.println("privateKey:" + privateKey);
    
            // 存入session
            HttpSession session = request.getSession();
            session.setAttribute("publicKey", publicKey);
            session.setAttribute("privateKey", privateKey);
            return "redirect:/toIndex";
        }
    
    	/**
         * 跳转到登录页面
         */
    	@RequestMapping(value = "/toIndex")
        public String toIndex(){
            return "index";
        }
    
        /**
         * 执行登陆操作
         * @param username 用户名
         * @param password   加密后的密码
         * @param request
         * @return
         */
        @ResponseBody
        @RequestMapping(value = "/doLogin", method = RequestMethod.POST)
        public String doLogin(String username, String password, HttpServletRequest request) {
            // 获取 session中的 privateKey
            String privateKey = (String) request.getSession().getAttribute("privateKey");
    
            String decryptPwd = RSAUtils.decryptByPrivateKey(password, privateKey);
            if ("zhangsan123".equals(decryptPwd)){
                return "登录成功!";
            }
            return "登录失败!";
        }
    
    }
    
  • 编写简单页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>模拟登录</title>
    </head>
    <body>
    <form action="/doLogin" method="post" name="user" onsubmit="fun()">
        用户名:<input name="username" type="text"><br>
        密码:<input name="password" type="password" id="pwd"><br>
        <button type="submit">登录</button>
    </form>
    <script type="text/javascript" src="js/jsencrypt.min.js"></script>
    <script>
        function fun(){
            // 获取 name为 user的 form元素下面的 name为 password的元素的值
            // 获取 form 表单中的密码
            var pwd = user.password.value;
            // 创建 JSEncrypt 对象
            var encrypt = new JSEncrypt();
            // 获取 session中的 publicKey
            encrypt.setPublicKey('[[${session.publicKey}]]');
            // 对密码加密,并且放入 input的value值中
            document.getElementsByName("password")[0].value = encrypt.encrypt(pwd);
            return true;
        }
    </script>
    </body>
    </html>
    
  • 测试登录
    在这里插入图片描述

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值