用戶登錄模塊進行必要的安全處理(MD5加密、加鹽和傳輸過程加密)
1、首先簡談一下常規Web登錄模塊的開發(只為了實現簡單的登錄功能,未對數據庫字段進行加密處理以及傳輸過程中進行加密處理)
非安全性登錄模塊開發
使用JSP+MYSQL
數據庫表如下所示:
先用jsp頁面創建login.jsp和index.jsp頁面(為了方便講解,直接使用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> <form action="index.jsp" method="post"> 賬 號:<input type="text" name="username"/><br/> 密 碼:<input type="password" name="password"><br/><br/> <input type="submit" value="提交"><br/> </form> </body> </html>
<%@page import="java.sql.ResultSet"%> <%@page import="java.sql.PreparedStatement"%> <%@page import="java.sql.DriverManager"%> <%@page import="java.sql.Connection"%> <%@ 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> <% //獲取mysql連接對象 String driverClass="com.mysql.jdbc.Driver"; String user="root"; String psw="zwkkwz"; String url="jdbc:mysql://localhost:3306/mytest"; Class.forName(driverClass); Connection conn=DriverManager.getConnection(url,user,psw); //獲取login.jsp傳過來的username和password String username=request.getParameter("username"); String password=request.getParameter("password"); String sql="select * from login where username=?"; PreparedStatement stmt=conn.prepareStatement(sql); stmt.setString(1, username); ResultSet rs=stmt.executeQuery(); %> <% if(rs.next()){ String p=rs.getString("password"); if(password.equals(p)){ out.println("用戶"+username+"登錄成功"); }else{ out.println("用戶"+username+"登錄失敗"); } }else{ out.println("用戶"+username+"不存在"); } %> </body> </html>
通過上面兩個jsp頁面進行實現登錄頁面的,可以實現校驗功能。但存在的安全隱患問題太多
- 數據庫以明文的形式進行存儲
- 數據傳輸的過程中未對數據進行加密處理(可以使用WireShark等抓包工具獲取post傳遞的明文信息
2、接下來針對以上兩個問題進行分析和解決:
安全加固1:對數據庫表的password字段進行摘要處理(在MYSQL中對數據摘要處理,sql語句如下)
//使用SHA進行摘要處理 UPDATE loginset password = SHA(password) //使用MD5進行摘要處理 UPDATE userinfo set password = MD5(password)
原來明文123456 經過是用MD5加密後是e10adc3949ba59abbe56e057f20f883e
但是這樣子還是不安全,進入http://www.cmd5.com/ 輸入加密後的密文進行解密後可以得到明文密碼
- 比如數據庫有多個密碼的明文是123456,通過MD5加密生成的密文是一模一樣的,這樣別人解密後就可以獲取到其他一樣的密碼
- 針對上述問題,我們會進行加鹽處理。即在用戶註冊時隨機生成一個規定長度的字段,然後和用戶密碼相結合,在進行MD5加密,等會再討論這個問題。
當對數據庫的明文密碼進行MD5加密後,我們再來改造一下jsp頁面的處理邏輯,對用戶輸入的密碼也進行MD5處理後再校驗
- 寫一個工具類DigestUtil,由這個工具類來幫助我們生成用戶輸入的password對應的MD5
- 寫一個工具類BytesToString,將字節數組轉化為字符串
- 具體代碼分別如下,具體過程請讀者自己分析代碼
package util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class DigestUtil { public static String getMD5(byte[] data) throws NoSuchAlgorithmException{ MessageDigest md = MessageDigest.getInstance("MD5"); md.update(data); byte[] resultBytes = md.digest(); String resultString = BytesToString.fromBytesToString(resultBytes); return resultString; } public static String getSHA1(byte[] data) throws NoSuchAlgorithmException{ MessageDigest md = MessageDigest.getInstance("SHA1"); md.update(data); byte[] resultBytes = md.digest(); String resultString = BytesToString.fromBytesToString(resultBytes); return resultString; } }
package util; public class BytesToString { public static String fromBytesToString(byte[] resultBytes) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < resultBytes.length; i++) { if (Integer.toHexString(0xFF & resultBytes[i]).length() == 1) { builder.append("0").append( Integer.toHexString(0xFF & resultBytes[i])); } else { builder.append(Integer.toHexString(0xFF & resultBytes[i])); } } return builder.toString(); } }
改造後的index.jsp代碼,如下:
<%@page import="util.DigestUtil"%> <%@page import="java.sql.ResultSet"%> <%@page import="java.sql.PreparedStatement"%> <%@page import="java.sql.DriverManager"%> <%@page import="java.sql.Connection"%> <%@ 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> <% //獲取mysql連接對象 String driverClass="com.mysql.jdbc.Driver"; String user="root"; String psw="zwkkwz"; String url="jdbc:mysql://localhost:3306/mytest"; Class.forName(driverClass); Connection conn=DriverManager.getConnection(url,user,psw); //獲取login.jsp傳過來的username和password String username=request.getParameter("username"); String password=request.getParameter("password"); String sql="select * from login where username=?"; PreparedStatement stmt=conn.prepareStatement(sql); stmt.setString(1, username); ResultSet rs=stmt.executeQuery(); %> <% if(rs.next()){ String p=rs.getString("password"); //簡單的明文校驗代碼 /* if(password.equals(p)){ out.println("用戶"+username+"登錄成功"); }else{ out.println("用戶"+username+"登錄失敗"); } }else{ out.println("用戶"+username+"不存在"); } */ //sql使用MD5加密後的密文和用戶輸入的password校驗代碼 if(p.equals(DigestUtil.getMD5(password.getBytes()))) //getMD5(byte[] data)方法是將原始的數據轉換成加密後的密文 { out.println("數據庫password字段"+p); out.println("用戶輸入的password"+password); out.println("經過處理後的password"+DigestUtil.getMD5(password.getBytes())); out.println("用戶"+username+"登錄成功"); }else{ out.println("用戶"+username+"登錄失敗"); } }else{ out.println("用戶"+username+"不存在"); } %> </body> </html>
通過以上步驟,我們只對數據庫的password明文字段進行了簡單的MD5加密,有以下缺點:
- 容易根據密文位數推測算法,從而使用工具破解
- 真實密碼相同,加密過的密碼也相同
接下來我們介紹一下對其進行加鹽處理:
3、加鹽處理,以此來增強系統的復雜度,再通過摘要處理,就能得到隱蔽性更強的摘要值
- 將表中的salt字段隨意輸入abccba,然後和原來的明文密碼123456結合,再進行SHA1加密
- 多建一對數據,將表中的salt字段輸入cbaabc,然後和原來的明文密碼123456結合,再進行SHA1加密
所謂的salt字段就是一個隨機的字段,具體隨機算法就不討論了,每當用戶註冊賬戶時,後臺就給它隨機生成一個不同的字段
然後根據password和salt字段結合進行摘要處理,存在數據庫表中的password字段,這樣一來,原來明文都是123456生成的密文就不一樣了
操作如下:
得到加密後密文是(原來密碼都是123456):
接下來改造index.jsp的處理邏輯,對於用戶輸入的密碼進行SHA1處理後的工具類在上面的DigestUtil方法中有列舉出
index.jsp代碼如下:
<%@page import="util.DigestUtil"%> <%@page import="java.sql.ResultSet"%> <%@page import="java.sql.PreparedStatement"%> <%@page import="java.sql.DriverManager"%> <%@page import="java.sql.Connection"%> <%@ 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> <% //獲取mysql連接對象 String driverClass="com.mysql.jdbc.Driver"; String user="root"; String psw="zwkkwz"; String url="jdbc:mysql://localhost:3306/mytest"; Class.forName(driverClass); Connection conn=DriverManager.getConnection(url,user,psw); //獲取login.jsp傳過來的username和password String username=request.getParameter("username"); String password=request.getParameter("password"); String sql="select * from login where username=?"; PreparedStatement stmt=conn.prepareStatement(sql); stmt.setString(1, username); ResultSet rs=stmt.executeQuery(); %> <% if(rs.next()){ String p=rs.getString("password"); //sql使用SHA1進行加鹽加密後的密文和用戶輸入的password校驗代碼 //我們需要獲取到用戶輸入的密碼和對應的salt String salt=rs.getString("salt"); if(p.equals(DigestUtil.getSHA1((password+salt).getBytes()))){ out.println("數據庫password字段"+p); out.println("用戶輸入的password"+password); out.println("經過處理後的password"+DigestUtil.getSHA1((password+salt).getBytes())); out.println("用戶"+username+"登錄成功"); }else{ out.println("用戶"+username+"登錄失敗"); } }else{ out.println("用戶"+username+"不存在"); } %> <% rs.close(); stmt.close(); conn.close(); %> </body> </html>
以上的步驟我們只是對sql進行了加密操作。
為了防止用戶輸入密碼在傳輸的過程中被抓包工具獲取,我們還要在密碼傳輸的過程中進行加密,這樣可以使得獲取到的也是密文。
使用MD5.js對表單加密(可以百度搜索MD5.js下載)
傳輸加密:在瀏覽器端發送出數據之前就要對數據進行加密處理。
在jsp引入MD5.js;給input標簽指定一個id,然後在提交的時候給定一個方法onclick,來對用戶輸入的密碼進行加密,具體的login.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> <script type="text/javascript" src="md5.js"></script> </head> <body> <form action="index.jsp" method="post"> 賬 號:<input type="text" name="username"/><br/> 密 碼:<input type="password" name="password" id="password"><br/><br/> <input type="submit" value="提交" onclick="toMd5()"><br/> </form> </body> <script type="text/javascript"> function toMd5(){ var passwordNode=document.getElementById("password"); //加密前 alert(passwordNode.value); var hash=hex_md5(passwordNode.value); passwordNode.value=hash; //加密後 alert(passwordNode.value); } </script> </html>
用戶登錄模塊進行必要的安全處理(MD5加密、加鹽和傳輸過程加密)