使用者登入模組進行必要的安全處理(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
比如資料庫有多個密碼的明文是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>