[关闭]
@chy282 2017-12-05T16:14:45.000000Z 字数 12866 阅读 4016

使用rsa进行http传输加密

web


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

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
  1. <dependency>
  2. <groupId>org.bouncycastle</groupId>
  3. <artifactId>bcprov-jdk15on</artifactId>
  4. <version>1.58</version>
  5. </dependency>

3.1.3 代码

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

在网上百度的代码,解密方式一般如下所示:
  1. // 获取实现指定转换的Cipher对象
  2. Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
  3. cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
  4. int blockSize = cipher.getBlockSize(); // 返回块的大小
  5. byte[] bytes = new BigInteger(str, 16).toByteArray();
  6. int j = 0;
  7. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  8. while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
  9. baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
  10. j++;
  11. }
用上述方式,偶尔会报错如下所示:
  1. java.lang.IllegalArgumentException: Bad arguments
  2. at javax.crypto.Cipher.doFinal(Cipher.java:2185)
  3. at com.study.webrsa.utils.RSAUtils.decrypt(RSAUtils.java:76)
  4. at com.study.webrsa.servlet.LoginServlet.doPost(LoginServlet.java:43)
  5. at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
  6. at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
  7. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
  8. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  9. at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
  10. at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
  11. at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
  12. at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
  13. at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
  14. at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
  15. at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
  16. at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
  17. at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
  18. at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
  19. at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
  20. at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
  21. at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
  22. at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
  23. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  24. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
  25. at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  26. at java.lang.Thread.run(Thread.java:745)
后发现问题就出现在toByteArray()上面,因为在用上面的三个JS进行加密时,偶尔得出的密文会比正确的密文多出一个byte,里面是o。

因此可使用如下方式:
  1. // 获取实现指定转换的Cipher对象
  2. Cipher cipher = Cipher.getInstance("RSA/NONE/NoPadding", new BouncyCastleProvider());
  3. cipher.init(Cipher.DECRYPT_MODE, privateKey); // 用密钥初始化此Cipher对象
  4. int blockSize = cipher.getBlockSize(); // 返回块的大小
  5. byte[] bytes = hexStringToBytes(str); // 将十六进制转换为二进制
  6. int j = 0;
  7. ByteArrayOutputStream baos = new ByteArrayOutputStream();
  8. while (bytes.length - j * blockSize > 0) { // 将二进制数据分块写入ByteArrayOutputStream中
  9. baos.write(cipher.doFinal(bytes, j * blockSize, blockSize));
  10. j++;
  11. }
  12. /**
  13. * 将十六进制字符串转换为二进制数组
  14. *
  15. * @param hexString
  16. * 十六进制字符串
  17. * @return
  18. */
  19. private static byte[] hexStringToBytes(String hexString) {
  20. if (hexString == null || "".equals(hexString)) {
  21. return null;
  22. }
  23. hexString = hexString.toUpperCase(); // 全部转换为大写字符
  24. int length = hexString.length() / 2; // 获取十六进制数据个数
  25. char[] hexChars = hexString.toCharArray(); // 将十六进制字符串转换为字符数组
  26. byte[] d = new byte[length];
  27. for (int i = 0; i < length; i++) {
  28. int pos = i * 2; // 开始位置
  29. d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
  30. }
  31. return d;
  32. }
  33. private static byte charToByte(char ch) {
  34. return (byte) "0123456789ABCDEF".indexOf(ch);
  35. }

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


添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注