什么叫给密码“加盐”?如何安全的为你的用户密码“加盐”?
在面对这个网络世界的时候,密码安全总是各个公司和用户都非常关心的一个内容,毕竟现在大家不管是休闲娱乐还是学习购物都是通过网上的帐号来进行消费的,所以我们通常会给用户的密码进行加密。在加密的时候,经常会听到“加盐”这个词,这是什么意思呢?
我们通常会将用户的密码进行 Hash 加密,如果不加盐,即使是两层的 md5 都有可能通过彩虹表的方式进行破译。彩虹表就是在网上搜集的各种字符组合的 Hash 加密结果。而加盐,就是人为的通过一组随机字符与用户原密码的组合形成一个新的字符,从而增加破译的难度。就像做饭一样,加点盐味道会更好。
接下来,我们通过代码来演示一种比较安全的加盐方式。
首先,我们建一个简单的用户表。这个表里只有四个字段,在这里仅作为测试使用。
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 密码。
/**
* 随机生成四位字符串的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() 方法很简单,就是生成一个随机的四位字符的字符串,我们使用大小写加数字的形式生成这个字符串。这就是传说中的“盐”。
接下来我们就可以使用 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;
}
// 登录成功!