1. 程式人生 > >用戶登錄模塊進行必要的安全處理(MD5加密、加鹽和傳輸過程加密)

用戶登錄模塊進行必要的安全處理(MD5加密、加鹽和傳輸過程加密)

sele clas 抓包 index.jsp sql語句 new 不存在 別人 com

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">
&nbsp;號:<input type="text" name="username"/><br/>&nbsp;碼:<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">&nbsp;號:<input type="text" name="username"/><br/>&nbsp;碼:<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加密、加鹽和傳輸過程加密)