目錄javascript
© 版權聲明:本文爲博主原創文章,轉載請註明出處css
RSA是目前最有影響力和最經常使用的公鑰加密算法,它可以抵抗到目前爲止已知的絕大多數密碼攻擊,已被ISO推薦爲公鑰數據加密標準。 今天只有短的RSA鑰匙纔可能被強力方式破解。但在分佈式計算和量子計算機理論日趨成熟的今天,RSA加密安全性收到了挑戰和質疑。 RSA算法基於一個十分簡單的數論事實:將兩個大質數相乘十分容易,可是想要對其乘積進行因式分解缺及其困難,所以能夠將乘積公開做爲加密密鑰。
1. 使用HTTPS協議可認證用戶和服務器,確保數據發送到正確的客戶機和服務器。 2. HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,要比HTTP協議安全,可防止數據在傳輸過程當中不被竊取、改變,確保數據的完整性。 3. HTTPS是現行框架下最安全的解決方案,雖然不是以爲安全,但它增長了中間人攻擊的成本。
1. SSL的專業證書須要購買,功能越強大的證書費用越高 2. 相同的網絡環境下,HTTPS協議會使頁面的加載時間延長50%,增長10%-20%的耗電。此外,HTTPS協議還會影響緩存,增長數據開銷和功耗。 3. HTTPS協議的安全性是有範圍的,在黑客攻擊、拒絕服務攻擊、服務器劫持等方面幾乎起不到什麼做用。 4. 最關鍵的是,SSL證書的信用鏈體系並不安全。特別是在某些國家能夠控制CA根證書的狀況下,中間人攻擊同樣可行。
綜上所述(其實主要是由於HTTPS購買SSL證書須要花錢),可在某些關鍵數據傳輸過程當中進行RSA加密。好比:登陸時對登陸密碼進行加密。
BigInt.js - 用於生成一個大整數(這是RSA算法的須要)
Barrett.js - RSA算法所須要用到的一個支持文件
RSA_Stripped.js - RSA的主要算法html
下載密碼:bhiq
bcprov-jdk15on
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.58</version> </dependency>
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>
setMaxDigits(),到底應該傳值多少? 在JS文件中給出公式爲:n * 2 / 16。其中n爲密鑰長度。 若是n爲1024,則值應爲 1024 * 2 / 16 = 128。 通過測試,傳128後臺解密會報錯;正確的值應該大於128。 我的喜愛的公式是:n * 2 / 16 + 3 即 密鑰長度若爲1024,其值爲 131 密鑰長度若爲2048,其值爲 259
在網上百度的代碼,解密方式通常以下所示:
// 獲取實現指定轉換的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?java
更好的markdown體驗:https://www.zybuluo.com/chy282/note/975080jquery