在面對這個網絡世界的時候,密碼安全老是各個公司和用戶都很是關心的一個內容,畢竟如今你們不論是休閒娛樂仍是學習購物都是經過網上的賬號來進行消費的,因此咱們一般會給用戶的密碼進行加密。在加密的時候,常常會聽到「加鹽」這個詞,這是什麼意思呢?php
咱們一般會將用戶的密碼進行 Hash 加密,若是不加鹽,即便是兩層的 md5 都有可能經過彩虹表的方式進行破譯。彩虹表就是在網上搜集的各類字符組合的 Hash 加密結果。而加鹽,就是人爲的經過一組隨機字符與用戶原密碼的組合造成一個新的字符,從而增長破譯的難度。就像作飯同樣,加點鹽味道會更好。mysql
接下來,咱們經過代碼來演示一種比較安全的加鹽方式。git
首先,咱們建一個簡單的用戶表。這個表裏只有四個字段,在這裏僅做爲測試使用。程序員
CREATE TABLE `zyblog_test_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '用戶名', `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '密碼', `salt` char(4) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '鹽', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
而後定義兩個方式,一個用來生成鹽,一個用來生成加鹽後的 Hash 密碼。github
/** * 隨機生成四位字符串的salt * 也能夠根據實際狀況使用6位或更長的salt */ function generateSalt() { // 使用隨機方式生成一個四位字符 $chars = array_merge(range('A', 'Z'), range('a', 'z'), range('0', '9')); for ($i = 0; $i < 4; $i++) { $str .= $chars[mt_rand(0, count($chars) - 1)]; } return $str; } /** * 密碼生成 * 使用兩層hash,將salt加在第二層 * sha1後再加salt而後再md5 */ function generateHashPassword($password, $salt) { return md5(sha1($password) . $salt); }
generateSalt() 方法很簡單,就是生成一個隨機的四位字符的字符串,咱們使用大小寫加數字的形式生成這個字符串。這就是傳說中的「鹽」。sql
接下來咱們就可使用 generateHashPassword() 方法爲用戶的原密碼加鹽。在這裏咱們第一層先使用 sha1() 對原密碼進行一次 Hash ,而後使用這個 Hash 值拼接鹽字符串後再進行 md5() 加密。最後加密出來的 Hash 值就很難在彩虹表中找到了。即便找到,也只是上層 sha1() 拼接鹽字符串的內容,用戶的原文密碼畢竟還有一層加密。數據庫
剩下的就是咱們進行出入庫的註冊登陸測試了。安全
$pdo = new PDO('mysql:host=localhost;dbname=blog_test;charset=utf8mb4', 'root', ''); $username = 'ZyBlog1'; $password = '123456'; // 註冊 function register($username, $password) { global $pdo; // 首先判斷用戶是否已註冊 $pre = $pdo->prepare("SELECT COUNT(id) FROM zyblog_test_user WHERE username = :username"); $pre->bindParam(':username', $username); $pre->execute(); $result = $pre->fetchColumn(); // 若是用戶名存在,則沒法註冊 if ($result > 0) { echo '用戶名已註冊!', PHP_EOL; return 0; } // 生成salt $salt = generateSalt(); // 密碼進行加鹽hash處理 $password = generateHashPassword($password, $salt); // 插入新用戶 $pre = $pdo->prepare("insert into zyblog_test_user(username, password, salt) values(?, ?, ?)"); $pre->bindValue(1, $username); $pre->bindValue(2, $password); $pre->bindValue(3, $salt); $pre->execute(); return $pdo->lastInsertId(); } $userId = register($username, $password); if ($userId > 0) { echo '註冊成功!用戶ID爲:' . $userId, PHP_EOL; } // 註冊成功!用戶ID爲:1 // 查詢數據庫中的數據 $sth = $pdo->prepare("SELECT * FROM zyblog_test_user"); $sth->execute(); $result = $sth->fetchAll(PDO::FETCH_ASSOC); print_r($result); // Array // ( // [0] => Array // ( // [id] => 1 // [username] => ZyBlog1 // [password] => bbff8283d0f90625015256b742b0e694 // [salt] => xOkb // ) // ) // 登陸時驗證 function login($username, $password) { global $pdo; // 先根據用戶名查表 $pre = $pdo->prepare("SELECT * FROM zyblog_test_user WHERE username = :username"); $pre->bindParam(':username', $username); $pre->execute(); $result = $pre->fetch(PDO::FETCH_ASSOC); // 用戶名存在並得到用戶信息後 if ($result) { // 根據用戶表中的salt字段生成hash密碼 $password = generateHashPassword($password, $result['salt']); // 比對hash密碼確認登陸是否成功 if ($password == $result['password']) { return true; } } return false; } $isLogin = login($username, $password); if ($isLogin) { echo '登陸成功!', PHP_EOL; } else { echo '登陸失敗,用戶名或密碼錯誤!', PHP_EOL; } // 登陸成功!
代碼仍是比較簡單的,在註冊的時候,咱們直接對用戶密碼進行加密後入庫。主要關注的地方是在登陸時,咱們先根據用戶名查找出對應的用戶信息。而後將用戶登陸提交上來的原文密碼進行加密,與數據庫中的原文密碼進行對比驗證,密碼驗證成功便可判斷用戶登陸成功。網絡
另外還須要注意的是,咱們的鹽字符串也是要存到數據庫中的。畢竟在登陸的時候咱們仍是須要將用戶的原文密碼與這個鹽字符串進行組合加密以後才能進行密碼的匹配。學習
這樣加密後的代碼其實想經過彩虹表來破解基本上是很難了。在幾年前 CSDN 的賬號泄露事件中,你們發現做爲中文程序員世界最大的網站居然是明文存儲的密碼,這就爲攻擊者提供了一大堆用戶的明文經常使用密碼。由於你們都喜歡用同一個用戶名和密碼註冊不一樣的網站,因此無論其餘怎麼加鹽都是沒用的,畢竟原文密碼是對的,拿到這樣一個網站的數據庫中的用戶明文密碼後,就能夠經過這些密碼去嘗試這些用戶在其餘網站是否是用了相同的賬號名和密碼註冊了賬號。因此在平常生活中,咱們重要的一些網站賬號、密碼儘可能仍是使用不一樣的內容,若是記不住的話,可使用一些帶加密能力的記事本軟件進行保存,這樣會更加安全。而咱們程序員,則應該始終都將用戶的密碼及重要信息進行加密處理,這是一種基本的職業規範。
測試代碼:
各自媒體平臺都可搜索【硬核項目經理】