目錄:
1.SHA-256和隨機鹽值
2.前端實現
3.后端實現
1.SHA-256和Salt
1.1.什么是SHA-256
SHA-256是一種信息摘要算法,也是一種密碼散列函數。對于任意長度的消息,SHA256都會產生一個256bit長的散列值(哈希值),用于確保信息傳輸完整一致,稱作消息摘要。這個摘要相當于是個長度為32個字節的數組,通常用一個長度為64的十六進制字符串來表示。
SHA-256的具備以下幾個關鍵特點:
- 固定長度輸出:無論輸入數據的大小,SHA-256都會產生一個256位(32字節)的固定長度散列值。
- 不可逆性:SHA-256的設計使得從生成的散列值無法還原原始輸入數據。這種不可逆性在安全性上是非常重要的。
- 抗碰撞性:找到兩個不同的輸入數據具有相同的散列值(碰撞)是極其困難的。雖然理論上碰撞可能發生,但SHA-256被設計得非常抗碰撞。
除了SHA-256之外,還有一個密碼散列函數MD5,過去也常被用于密碼加密,但MD5在安全性上低于SHA-256,現在已經很少用于密碼加密了,本文不做考慮。
SHA-256 和 MD5 的比較:
特性 | SHA-256 | MD5 |
---|---|---|
輸出長度 | 256 位(64 個十六進制字符) | 128 位(32 個十六進制字符) |
安全性 | 高 | 低 |
計算速度 | 較慢 | 快 |
抗碰撞能力 | 強 | 弱 |
應用場景 | 數據完整性校驗、數字簽名、密碼存儲、區塊鏈 | 曾用于文件校驗、密碼存儲 |
推薦使用 | 是 | 否 |
1.2.什么是隨機鹽值
鹽值(salt) 是一種在密碼學和安全計算中常用的隨機數據,用于增強密碼散列的安全性。
隨機鹽值(random salt)是一種用于增強密碼散列安全性的技術。它是一個隨機生成的數據塊,在將密碼輸入散列函數之前,將鹽值與密碼組合。通過引入隨機鹽值,可以有效地防止彩虹表攻擊和相同密碼散列值重復的問題。
鹽值的作用:
- 防止彩虹表攻擊: 彩虹表是一個預計算的哈希值數據庫,用于快速查找常見密碼的哈希值。通過在密碼哈希之前加入隨機鹽值,即使密碼相同,其最終的哈希值也會不同,從而使彩虹表無效。
- 避免散列值重復: 如果兩個用戶使用相同的密碼,在沒有鹽值的情況下,他們的哈希值會相同。加入鹽值后,即使密碼相同,生成的哈希值也會不同,這有助于防止攻擊者通過觀察哈希值來推測用戶是否使用了相同的密碼。
- 增加攻擊難度: 鹽值增加了密碼哈希的復雜性。即使攻擊者獲取了存儲的哈希值和鹽值,他們仍需對每個鹽值進行單獨的暴力破解,顯著增加了破解的時間和計算成本。
1.3.如何進行加密操作
本文采用的加密方式是在前端采用md加密防止明文傳輸,后端對密碼二次加密后再進行隨機鹽值的混入。
2.前端實現
引入md5.min.js
<!DOCTYPE html>
<html lang="en"xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>登錄</title><!-- jquery --><script type="text/javascript" th:src="@{/js/jquery.min.js}"></script><!-- bootstrap --><link rel="stylesheet" type="text/css" th:href="@{/bootstrap/css/bootstrap.min.css}"/><script type="text/javascript" th:src="@{/bootstrap/js/bootstrap.min.js}"></script><!-- jquery-validator --><script type="text/javascript" th:src="@{/jquery-validation/jquery.validate.min.js}"></script><script type="text/javascript" th:src="@{/jquery-validation/localization/messages_zh.min.js}"></script><!-- md5.js --><script type="text/javascript" th:src="@{/js/md5.min.js}"></script><!-- common.js --><script type="text/javascript" th:src="@{/js/common.js}"></script>
</head>
<body>
<form name="loginForm" id="loginForm" method="post" style="width:50%; margin:0 auto"><h2 style="text-align:center; margin-bottom: 20px">用戶登錄</h2><div class="form-group"><div class="row"><label class="form-label col-md-4">請輸入手機號碼</label><div class="col-md-5"><input id="mobile" minlength="11" maxlength="11" name="mobile" class="form-control" type="text" placeholder="手機號碼" required="true"/></div></div></div><div class="form-group"><div class="row"><label class="form-label col-md-4">請輸入密碼</label><div class="col-md-5"><input id="password" name="password" class="form-control" type="password" placeholder="密碼" required="true" /></div></div></div><div class="row"><div class="col-md-5"><button class="btn btn-primary btn-block" type="reset" onclick="reset()">重置</button></div><div class="col-md-5"><button class="btn btn-primary btn-block" type="submit" onclick="login()">登錄</button></div></div>
</form>
</body>
<script>function login() {$("#loginForm").validate({submitHandler: function (form) {doLogin();}});}function doLogin() {var inputPass = $("#password").val();var salt = "1a2b3c4d";var str = "" + salt.charAt(0) + salt.charAt(2) + inputPass + salt.charAt(5) + salt.charAt(4);var password = md5(str);$.ajax({url: "/login/doLogin",type: "POST",data: {mobile: $("#mobile").val(),password: password},success: function (data) {layer.closeAll();if (data.code == 200) {layer.msg("成功");console.log(data);document.cookie = "userTicket=" + data.object;window.location.href = "/goods/toList";} else {layer.msg(data.message);}},error: function () {layer.closeAll();}});}
</script>
</html>
3.后端實現
3.1.導入Maven依賴
<dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.15</version>
</dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version>
</dependency>
3.2.密碼加密
3.2.1.密碼加鹽
首先使用Apache的RandomStringUtils
工具類,生成16位的鹽值。然后將鹽拼接到明文后面,進行SHA256加密。
這個加密后的SHA256是個固定64長度的字符串。
// 生成一個16位的隨機數,也就是鹽
String salt = RandomStringUtils.randomAlphanumeric(16);// 將鹽拼接到明文后,并生成新的sha256碼
String sha256Hex = DigestUtils.sha256Hex(password + salt);
3.2.2.隨機鹽值混合
加鹽后的SHA256碼長度為80位,這里我們采用的鹽值混合規則:將SHA-256散列值的每四個字符中間插入一個鹽值字符,依次交替排列。
// 將鹽混到新生成的SHA-256碼中,之所以這樣做是為了后期解密,校驗密碼
StringBuilder sb = new StringBuilder(80); // SHA-256是64個字符,加16個字符的鹽,總共80個字符
for (int i = 0; i < 16; i++) {sb.append(sha256Hex.charAt(i * 4));sb.append(salt.charAt(i));sb.append(sha256Hex.charAt(i * 4 + 1));sb.append(sha256Hex.charAt(i * 4 + 2));sb.append(sha256Hex.charAt(i * 4 + 3));
}
return sb.toString();
這樣就完成了加密的操作:密碼加鹽 + 鹽值混合。
3.3.密碼解密
3.3.1.提取鹽值和加鹽密碼
按照加密時采用的規則:將SHA-256散列值的每四個字符中間插入一個鹽值字符,依次交替排列。
我們可以將鹽值和加鹽后的SHA-256碼
// 提取鹽值和加鹽后的SHA-256碼
StringBuilder sb1 = new StringBuilder(64);
StringBuilder sb2 = new StringBuilder(16);for (int i = 0; i < 16; i++) {sb1.append(encrypted.charAt(i * 5));sb1.append(encrypted.charAt(i * 5 + 2));sb1.append(encrypted.charAt(i * 5 + 3));sb1.append(encrypted.charAt(i * 5 + 4));sb2.append(encrypted.charAt(i * 5 + 1));
}String sha256Hex = sb1.toString();
String salt = sb2.toString();
3.3.2.比較密碼
最后,將取出的鹽值與原始密碼再次加鹽,再次得到加鹽密碼,與sha256Hex比較即可判斷密碼是否相同。
// 比較二者是否相同
return DigestUtils.sha256Hex(password + salt).equals(sha256Hex);
3.4.完整工具類
public class SHA256Util {/*** 加密* 生成鹽和加鹽后的SHA-256碼,并將鹽混入到SHA-256碼中,對SHA-256密碼進行加強**/public static String encryptPassword(String password) {// 生成一個16位的隨機數,也就是鹽String salt = RandomStringUtils.randomAlphanumeric(16);// 將鹽拼接到明文后,并生成新的sha256碼String sha256Hex = DigestUtils.sha256Hex(password + salt);// 將鹽混到新生成的SHA-256碼中,之所以這樣做是為了后期解密,校驗密碼StringBuilder sb = new StringBuilder(80); // SHA-256是64個字符,加16個字符的鹽,總共80個字符for (int i = 0; i < 16; i++) {sb.append(sha256Hex.charAt(i * 4));sb.append(salt.charAt(i));sb.append(sha256Hex.charAt(i * 4 + 1));sb.append(sha256Hex.charAt(i * 4 + 2));sb.append(sha256Hex.charAt(i * 4 + 3));}return sb.toString();}/*** 解密* 從混入鹽的SHA-256碼中提取鹽值和加鹽后的SHA-256碼**/public static boolean verifyPassword(String password, String encrypted) {// 提取鹽值和加鹽后的SHA-256碼StringBuilder sb1 = new StringBuilder(64);StringBuilder sb2 = new StringBuilder(16);for (int i = 0; i < 16; i++) {sb1.append(encrypted.charAt(i * 5));sb1.append(encrypted.charAt(i * 5 + 2));sb1.append(encrypted.charAt(i * 5 + 3));sb1.append(encrypted.charAt(i * 5 + 4));sb2.append(encrypted.charAt(i * 5 + 1));}String sha256Hex = sb1.toString();String salt = sb2.toString();// 比較二者是否相同return DigestUtils.sha256Hex(password + salt).equals(sha256Hex);}
}