哈希算法(Hash)又稱摘要算法(Digest),它的作用是:對任意一組輸入數據進行計算,得到一個固定長度的輸出摘要。
哈希算法最重要的特點就是:
- 相同的輸入一定得到相同的輸出;
- 不同的輸入大概率得到不同的輸出。
- 哈希算法的目的就是為了驗證原始數據是否被篡改。
我們來簡單實現一個用於用戶注冊和登錄最基本的功能。
在登錄中,要檢查是否存在某個用戶信息,每個用戶信息都是唯一的,所以可以借助Set的特性來操作用戶信息的存放。
在注冊中,要檢查用戶名是否已經被注冊,而每個用戶名也是唯一的,所以在這里也利用Set來操作用戶名的存放。
當然,也可以用Map來存放用戶名和用戶密碼,K存放用戶名,對應的V存放密碼。但是為了讓用戶名和密碼的關聯度盡可能的小一些,所以利用兩個Set來分別存放用戶名和用戶信息。
由於Set是無序的,所以當黑客獲取到這兩個數據文件的時候也很難將用戶名對應到相應的用戶信息。
這里的用戶信息指的是將用戶名和密碼混合后的信息,例如某個用戶的用戶名是"admin",密碼是"password",那么可以將這兩個字段混合來達到增長信息量的目的。
當然,為了讓安全性更高,可以利用特定的排列組合將兩個字符串混合,比如可以將兩個字符串拆解成字符數組,按照數組下標的奇偶數來排列兩個字符串。
例如"admin"的長度小於"password",因此以"admin"為基准,'a'為起始,"admin"占奇數位,"passw"占偶數位,剩余字符連接在生成字段后,即"apdamsisnword",就像把用戶名插入到了密碼中。
還有一種方法是對每個生成的用戶信息添加隨機字符,這個方法被稱為“加鹽”。
例如,用戶名和密碼依然是"admin"和"password",我們設置一個隨機salt = "aRandomSalt",然后將這個salt加入到用戶名和密碼之中,比如"admin" + salt + "password",salt + "admin" + "password"或是其他更復雜的組合。
后續的代碼中,簡單的將用戶名和密碼連接在了一起,即"adminpassword"
package service;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Set;
import dao.Dao; // 儲存相關配置文件
import dao.UserInfoDao; // 用於將用戶信息存盤
import entity.UserInfo; // 用戶信息實體類,其中的兩個類成員是userName和userPassword,即用戶名和密碼
public class UserInfoService extends Dao {
private String userInfoPath; // 用戶信息保存的文件路徑
private String userNamePath; // 用戶名保存的文件路徑
private UserInfoDao dao = new UserInfoDao();
public UserInfoService() {
super();
userInfoPath = super.getResource().getString("userInfo");
userNamePath = super.getResource().getString("userName");
}
/**
* 用戶登錄。若用戶信息存在,則登錄成功;若用戶信息不存在,則登錄失敗
*
* @param userInfo
* @return 提示信息
*/
public String userSignIn(UserInfo userInfo) {
Set<String> userInfoSet = null;
String tips;
userInfoSet = dao.readInfo(userInfoPath); // 從相關文件中讀取用戶信息
if (userInfoSet == null) { // 若尚無用戶注冊,則new HashSet<String>(),避免NullPointerException
userInfoSet = new HashSet<String>();
}
if (userInfoSet.contains(getUserInfoHashCode(userInfo))) { // 判斷是否含有相關用戶信息
tips = "登錄成功!";
} else {
tips = "登錄失敗!請檢查用戶名或密碼";
}
return tips;
}
/**
* 用戶注冊。若用戶名不存在,則注冊成功;若用戶名存在,則注冊失敗
*
* @param userInfo
* @return 提示信息
*/
public String userSignUp(UserInfo userInfo) {
Set<String> userInfoSet = null;
Set<String> userNameSet = null;
String tips;
userInfoSet = dao.readInfo(userInfoPath); // 從相關文件中讀取用戶信息
userNameSet = dao.readInfo(userNamePath); // 從相關文件中讀取用戶名
if (userInfoSet == null) { // 若尚無用戶注冊,則new HashSet<String>(),避免NullPointerException
userInfoSet = new HashSet<String>();
}
if (userNameSet == null) { // 若尚無用戶注冊,則new HashSet<String>(),避免NullPointerException
userNameSet = new HashSet<String>();
}
if (userNameSet.add(userInfo.getUserName())) { // 判斷用戶名是否已注冊
userInfoSet.add(getUserInfoHashCode(userInfo)); // 若用戶名未注冊,則將用戶信息添加至Set中
dao.saveInfo(userInfoSet, userInfoPath); // 保存用戶信息到相關文件
dao.saveInfo(userNameSet, userNamePath); // 保存用戶名到相關文件
tips = "注冊成功!";
} else {
tips = "注冊失敗!用戶已存在";
}
return tips;
}
/**
* 以預設算法SHA-1加密用戶名和密碼,以預設基數36位保存
*
* @param userInfo
* @return 加密后的用戶信息
*/
public String getUserInfoHashCode(UserInfo userInfo) {
return getUserInfoHashCode(userInfo, "SHA-1", 36); // 用SHA-1算法生成用戶信息密鑰,進制為36進制
}
/**
* 以指定算法algorithm加密用戶名和密碼,以指定基數radix長度保存
*
* @param userInfo
* @param algorithm
* @param radix
* @return 加密后的用戶信息
*/
public String getUserInfoHashCode(UserInfo userInfo, String algorithm, int radix) {
try {
MessageDigest md = MessageDigest.getInstance(algorithm); // 用指定算法algorithm創建一個MessageDigest實例
md.update((userInfo.getUserName() + userInfo.getUserPassword()).getBytes("UTF-8")); // 將用戶名和密碼合並,調用update()輸入數據
byte[] res = md.digest(); // 將生成的信息摘要存放在byte[]中
return new BigInteger(1, res).toString(radix); // 返回一個指定進制基數為radix的字符串
} catch (Exception e) {
e.printStackTrace();
return ""; // 若異常則返回空字符串
}
}
}