zoukankan      html  css  js  c++  java
  • 使用rsa进行http传输加密

    © 版权声明:本文为博主原创文章,转载请注明出处

    1. RSA算法

    RSA是目前最有影响力和最常用的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。
    
    今天只有短的RSA钥匙才可能被强力方式破解。但在分布式计算和量子计算机理论日趋成熟的今天,RSA加密安全性收到了挑战和质疑。
    
    RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解缺及其困难,因此可以将乘积公开作为加密密钥。
    

    2. HTTPS

    2.1 HTTPS优点

    1. 使用HTTPS协议可认证用户和服务器,确保数据发送到正确的客户机和服务器。
    
    2. HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全,可防止数据在传输过程中不被窃取、改变,确保数据的完整性。
    
    3. HTTPS是现行框架下最安全的解决方案,虽然不是觉得安全,但它增加了中间人攻击的成本。
    

    2.2 HTTPS缺点

    1. SSL的专业证书需要购买,功能越强大的证书费用越高
    
    2. 相同的网络环境下,HTTPS协议会使页面的加载时间延长50%,增加10%-20%的耗电。此外,HTTPS协议还会影响缓存,增加数据开销和功耗。
    
    3. HTTPS协议的安全性是有范围的,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
    
    4. 最关键的是,SSL证书的信用链体系并不安全。特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
    

    3. RSA传输加密实现

    综上所述(其实主要是因为HTTPS购买SSL证书需要花钱),可在某些关键数据传输过程中进行RSA加密。比如:登录时对登录密码进行加密。
    

    3.1 所需插件

    3.1.1 JS插件

    BigInt.js - 用于生成一个大整数(这是RSA算法的需要)
    Barrett.js - RSA算法所需要用到的一个支持文件
    RSA_Stripped.js - RSA的主要算法

    下载密码:bhiq
    

    3.1.2 所需JAR

    bcprov-jdk15on
    
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk15on</artifactId>
        <version>1.58</version>
    </dependency>
    

    3.1.3 代码

    pom.xml
    
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.study</groupId>
    	<artifactId>webrsa</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<packaging>war</packaging>
    
    	<properties>
    		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>4.12</version>
    			<scope>test</scope>
    		</dependency>
    		<!-- bcprov-jdk15on -->
    		<dependency>
    		    <groupId>org.bouncycastle</groupId>
    		    <artifactId>bcprov-jdk15on</artifactId>
    		    <version>1.58</version>
    		</dependency>
    	</dependencies>
    	
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.apache.maven.plugins</groupId>
    				<artifactId>maven-compiler-plugin</artifactId>
    				<version>3.6.1</version>
    				<configuration>
    					<target>1.7</target>
    					<source>1.7</source>
    					<encoding>UTF-8</encoding>
    				</configuration>
    			</plugin>
    		</plugins>
    	</build>
    	
    </project>
    
    RSAUtils.java
    
    package com.study.webrsa.utils;
    
    import java.io.ByteArrayOutputStream;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.crypto.Cipher;
    
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    /**
     * RSA加解密工具类
     *
     */
    public class RSAUtils {
        
    	public static final String SECURITY = "RSA"; // 加密方式
    	public static final String ALGORITHM = "MD5withRSA"; // 加密算法
    	public static final String PUBLIC_KEY = "RSAPublicKey"; // 公钥
    	public static final String PRIVATE_KEY = "RSAPrivateKey"; // 私钥
    	
    	/**
    	 * 获取密钥
    	 */
    	public static Map<String, Object> getKey() {
    
    		Map<String, Object> map = null;
    		try {
    			// 生成实现指定算法的KeyPairGenerator对象,用于生成密钥对
    			KeyPairGenerator keyPairGenerator = 
    					KeyPairGenerator.getInstance(SECURITY, new BouncyCastleProvider());
    			keyPairGenerator.initialize(1024); // 初始化密钥长度
    			KeyPair keyPair = keyPairGenerator.generateKeyPair(); // 生成密钥对
    			RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); // 获取公钥
    			RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); // 获取私钥
    			
    			// 保存到map中
    			map = new HashMap<String, Object>();
    			map.put(PUBLIC_KEY, rsaPublicKey);
    			map.put(PRIVATE_KEY, rsaPrivateKey);
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    		return map;
    		
    	}
    	
    	/**
    	 * 利用私钥进行解密
    	 * 
    	 * @param privateKey
    	 * 						私钥
    	 * @param str
    	 * 						密文
    	 * @return
    	 */
    	public static String decrypt(RSAPrivateKey privateKey, String str) {
    		
    		try {
    			System.out.println("密文为:" + str);
    			// 获取实现指定转换的Cipher对象
    			Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
    			cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
    			
    			int blockSize = cipher.getBlockSize(); // 返回块的大小
    			byte[] bytes = hexStringToBytes(str); // 将十六进制转换为二进制
    			int j = 0;
    			ByteArrayOutputStream baos = new ByteArrayOutputStream();
    			while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
    				baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
    				j++;
    			}
    			
    			// 将二进制数据转换为字符串
    			byte[] bs = baos.toByteArray();
    			StringBuilder sb = new StringBuilder();
    			sb.append(new String(bs));
    			String pwd = sb.reverse().toString();
    			System.out.println("明文为:" + pwd);
    			return pwd;
    		} catch (Exception e) {
    			e.printStackTrace();
    		} 
    		return null;
    		
    	}
    	
    	/**
    	 * 将十六进制字符串转换为二进制数组
    	 * 
    	 * @param hexString
    	 * 					十六进制字符串
    	 * @return
    	 */
    	private static byte[] hexStringToBytes(String hexString) {
    		
    		if (hexString == null || "".equals(hexString)) {
    			return null;
    		}
    		
    		hexString = hexString.toUpperCase(); // 全部转换为大写字符
    		int length = hexString.length() / 2; // 获取十六进制数据个数
    		char[] hexChars = hexString.toCharArray(); // 将十六进制字符串转换为字符数组
    		byte[] d = new byte[length];
    		for (int i = 0; i < length; i++) {
    			int pos = i * 2; // 开始位置
    			d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 
    		}
    		return d;
    	}
    	
    	private static byte charToByte(char ch) {
    		
    		return (byte) "0123456789ABCDEF".indexOf(ch);
    		
    	}
    	
    }
    
    login.jsp
    
    <%@page import="java.util.Map"%>
    <%@page import="java.security.interfaces.RSAPrivateKey"%>
    <%@page import="java.security.interfaces.RSAPublicKey"%>
    <%@page import="com.study.webrsa.utils.RSAUtils"%>
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    	<title>用户登录</title>
    	
    	<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    	<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    </head>
    <%
    	// 获取密钥对
    	Map<String, Object> map = RSAUtils.getKey();
    	// 获取公钥
    	RSAPublicKey rsaPublicKey = (RSAPublicKey) map.get(RSAUtils.PUBLIC_KEY);
    	// 获取私钥
    	RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) map.get(RSAUtils.PRIVATE_KEY);
    	// 保存私钥到session中,便于后台进行解密
    	session.setAttribute("rsaKey", rsaPrivateKey);
    	// 保存公钥到request中,便于页面加密
    	String publicExponent = rsaPublicKey.getPublicExponent().toString(16);
    	String publicModulus = rsaPublicKey.getModulus().toString(16);
    	request.setAttribute("publicExponent", publicExponent);
    	request.setAttribute("publicModulus", publicModulus);
    %>
    <body>
    	<div class="container-fluid">
    		<form action="login" method="post" class="col-md-6 col-md-offset-3" 
    			onsubmit="return cmdEncrypt();">
    			<div class="form-group">
    				<label for="loginName">登录名</label>
    				<input type="text" id="loginName" name="loginName" class="form-control" 
    					placeholder="请输入用户名...">
    			</div>
    			<div class="form-group">
    				<label for="loginPwd">登录密码</label>
    				<input type="password" id="loginPwd" name="loginPwd" class="form-control" 
    					placeholder="请输入登录密码...">
    			</div>
    			<button type="submit" class="btn btn-primary">登录</button>
    		</form>
    	</div>
    </body>
    	<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script>
    	<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    	<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
    	<script type="text/javascript" src="resources/rsa/BigInt.js"></script>
    	<script type="text/javascript" src="resources/rsa/Barrett.js"></script>
    	<script type="text/javascript" src="resources/rsa/RSA_Stripped.js"></script>
    	<script type="text/javascript">
    		
    		// 提交前对密码进行加密
    		function cmdEncrypt() {
    			
    			setMaxDigits(131);
    			var pwd = $("#loginPwd").val(); // 获取原始密码
    			var key = new RSAKeyPair("${publicExponent}", "", "${publicModulus}");
    			pwd = encryptedString(key, pwd); // 对密码进行加密
    			$("#loginPwd").val(pwd);
    			return true;
    			
    		}
    	
    	</script>
    </html>
    
    LoginServlet.java
    
    package com.study.webrsa.servlet;
    
    import java.io.IOException;
    import java.security.interfaces.RSAPrivateKey;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.study.webrsa.utils.RSAUtils;
    
    @WebServlet("/login")
    public class LoginServlet extends HttpServlet {
    
    	private static final long serialVersionUID = 1L;
    
    	@Override
    	protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
    			throws ServletException, IOException {
    		
    		doPost(req, resp);
    		
    	}
    
    	@Override
    	protected void doPost(HttpServletRequest req, HttpServletResponse resp) 
    			throws ServletException, IOException {
    		
    		// 设置编码格式
    		req.setCharacterEncoding("UTF-8");
    		resp.setCharacterEncoding("UTF-8");
    		
    		// 获取前台参数
    		String loginName = req.getParameter("loginName");
    		String loginPwd = req.getParameter("loginPwd");
    		
    		// 获取私钥
    		RSAPrivateKey privateKey = (RSAPrivateKey) req.getSession().getAttribute("rsaKey");
    		
    		// 对密码进行解密
    		loginPwd = RSAUtils.decrypt(privateKey, loginPwd);
    		
    		// 校验
    		if (true) {
    			req.setAttribute("username", loginName);
    			System.out.println("用户[" + loginName + "]用密码[" + loginPwd + "]登录本系统");
    			req.getRequestDispatcher("/success.jsp").forward(req, resp);
    		}
    		
    	}
    
    	
    	
    }
    
    success.jsp
    
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>登录成功</title>
    </head>
    <body>
    	<center>
    		<h1>欢迎您,${username }</h1>
    	</center>
    </body>
    </html>
    
    web.xml
    
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://java.sun.com/xml/ns/javaee"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    	                    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    	version="3.0">
    	
    	<welcome-file-list>
    		<welcome-file>login.jsp</welcome-file>
    	</welcome-file-list>
    	
    </web-app>
    

    4. 注意事项

    4.1 setMaxDigits()

    setMaxDigits(),到底应该传值多少?
    
    在JS文件中给出公式为:n * 2 / 16。其中n为密钥长度。
        如果n为1024,则值应为 1024 * 2 / 16 = 128。
    
    经过测试,传128后台解密会报错;正确的值应该大于128。
    
    个人喜好的公式是:n * 2 / 16 + 3
    即  密钥长度若为1024,其值为 131
        密钥长度若为2048,其值为 259
    

    4.2 解密方式

    在网上百度的代码,解密方式一般如下所示:
    
    // 获取实现指定转换的Cipher对象
    Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
    cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
    			
    int blockSize = cipher.getBlockSize(); // 返回块的大小
    byte[] bytes = new BigInteger(str, 16).toByteArray();
    int j = 0;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
    	baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
    	j++;
    }
    
    用上述方式,偶尔会报错如下所示:
    
    java.lang.IllegalArgumentException: Bad arguments
    	at javax.crypto.Cipher.doFinal(Cipher.java:2185)
    	at com.study.webrsa.utils.RSAUtils.decrypt(RSAUtils.java:76)
    	at com.study.webrsa.servlet.LoginServlet.doPost(LoginServlet.java:43)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
    	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
    	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
    	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
    	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
    	at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
    	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
    	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
    	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
    	at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    	at java.lang.Thread.run(Thread.java:745)
    
    后发现问题就出现在toByteArray()上面,因为在用上面的三个JS进行加密时,偶尔得出的密文会比正确的密文多出一个byte,里面是o。
    
    因此可使用如下方式:
    
    // 获取实现指定转换的Cipher对象
    Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
    cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
    
    int blockSize = cipher.getBlockSize(); // 返回块的大小
    byte[] bytes = hexStringToBytes(str); // 将十六进制转换为二进制
    int j = 0;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
    	baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
    	j++;
    }
    			
    /**
     * 将十六进制字符串转换为二进制数组
     * 
     * @param hexString
     * 					十六进制字符串
     * @return
     */
    private static byte[] hexStringToBytes(String hexString) {
    	
    	if (hexString == null || "".equals(hexString)) {
    		return null;
    	}
    	
    	hexString = hexString.toUpperCase(); // 全部转换为大写字符
    	int length = hexString.length() / 2; // 获取十六进制数据个数
    	char[] hexChars = hexString.toCharArray(); // 将十六进制字符串转换为字符数组
    	byte[] d = new byte[length];
    	for (int i = 0; i < length; i++) {
    		int pos = i * 2; // 开始位置
    		d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); 
    	}
    	return d;
    }
    
    private static byte charToByte(char ch) {
    	
    	return (byte) "0123456789ABCDEF".indexOf(ch);
    	
    }
    

    参考:
    JS加密Java解密报rsa bad argument
    HTTPS优缺点、原理解析:我们的网站该不该做HTTPS?

    更好的markdown体验:https://www.zybuluo.com/chy282/note/975080


  • 相关阅读:
    html
    头部标签
    ajax
    分辨率
    js 运动基础
    js DOM
    js定时器
    js数组
    js基础
    例子:js简易日历
  • 原文地址:https://www.cnblogs.com/jinjiyese153/p/7987735.html
Copyright © 2011-2022 走看看