前言
从表定义开始:- UserID- Fname- Lname- Email- Password- IV
以下是一些变化:田野
Fname, Lname和
Email将使用对称密码加密,该密码由
这个
IV字段将存储用于加密。存储需求取决于所使用的密码和模式;稍后将详细介绍。
这个
Password字段将使用
单程密码散列,
加密
密码与模式
选择最佳的加密密码和模式超出了这个答案的范围,但最终的选择会影响加密密钥和初始化向量的大小;对于这篇文章,我们将使用AES-256-CBC,它的固定块大小为16字节,密钥大小为16、24或32字节。
加密密钥
一个好的加密密钥是由一个可靠的随机数生成器生成的二进制BLOB。建议采用以下示例(>=5.3):$key_size = 32; // 256 bits$encryption_key = openssl_random_pseudo_bytes($key_size, $strong);// $strong will be true if the key is crypto safe
这可以执行一次或多次(如果您希望创建一个加密密钥链)。把这些尽量保密。
四、四
初始化向量增加了加密的随机性,并且是CBC模式所必需的。理想情况下,这些值应该只使用一次(从技术上讲,每个加密密钥只使用一次),因此对行的任何部分的更新都应该重新生成它。
提供了一个函数来帮助您生成IV:$iv_size = 16; // 128 bits$iv = openssl_random_pseudo_bytes($iv_size, $strong);
例
让我们使用前面的$encryption_key和$iv要做到这一点,我们必须将数据填充到块大小:function pkcs7_pad($data, $size){
$length = $size - strlen($data) % $size;
return $data . str_repeat(chr($length), $length);}$name = 'Jack';$enc_name = openssl_encrypt(
pkcs7_pad($name, 16), // padded data
'AES-256-CBC', // cipher and mode
$encryption_key, // secret key
0, // options (not used)
$iv // initialisation vector);
存储要求
与IV一样,加密的输出是二进制的;在数据库中存储这些值可以通过使用指定的列类型来完成,例如BINARY或VARBINARY.
与IV一样,输出值是二进制的;要将这些值存储在MySQL中,请考虑使用BINARY或VARBINARY柱子。如果这不是一个选项,则还可以使用以下方法将二进制数据转换为文本表示。base64_encode()或bin2hex()这样做需要33%到100%的存储空间。
解密
存储值的解密类似于:function pkcs7_unpad($data){
return substr($data, 0, -ord($data[strlen($data) - 1]));}$row = $result->fetch(PDO::FETCH_ASSOC); // read from database result// $enc_name = base64_decode($row['Name']);// $enc_name = hex2bin($row['Name']);$enc_name = $row['Name'];// $iv = base64_decode($row['IV']);// $iv = hex2bin($row['IV']);$iv = $row['IV'];$name = pkcs7_unpad(openssl_decrypt(
$enc_name,
'AES-256-CBC',
$encryption_key,
0,
$iv));
认证加密
您可以通过附加从秘密密钥(不同于加密密钥)和密码文本生成的签名来进一步提高生成的密码文本的完整性。在解密密码文本之前,首先验证签名(最好采用常数时间比较方法)。
例// generate once, keep safe$auth_key = openssl_random_pseudo_bytes(32, $strong);// authentication$auth = hash_hmac('sha256', $enc_name, $auth_key, true);$auth_enc_name = $auth . $enc_name;// verification$auth = substr($auth_enc_name, 0, 32);$enc_name = substr($auth_enc_name, 32);$actual_auth = hash_hmac('sha256', $enc_name, $auth_key, true);if (hash_equals($auth, $actual_auth)) {
// perform decryption}
散列
必须尽可能避免在数据库中存储可逆密码;您只希望验证密码,而不希望知道其内容。如果用户丢失了密码,最好让他们重新设置密码,而不是发送给他们原来的密码(确保密码重置只能在有限的时间内完成)。
应用哈希函数是一种单向操作;之后,它可以安全地用于验证而不泄露原始数据;对于密码,蛮力方法是一种可行的方法来发现它,因为它的长度相对较短,而且很多人的密码选择都很差。
哈希算法(如MD5或SHA 1)用于根据已知的哈希值验证文件内容。他们进行了极大的优化,使这一验证尽可能快,同时仍然是准确的。考虑到它们相对有限的输出空间,使用已知密码和各自的散列输出彩虹表来构建数据库是很容易的。
在密码哈希之前添加一个盐将使彩虹表变得无用,但是最近的硬件进步使得蛮力查找成为一种可行的方法。这就是为什么你需要一个杂凑算法,这是刻意缓慢和根本不可能优化的。它还应该能够为更快的硬件增加负载,而不影响验证现有密码散列的能力,使其成为未来的证明。
目前有两种流行的选择:PBKDF 2(基于密码的密钥派生函数v2)
bcrypt(又名龙虾)
这个答案将使用bcrypt的例子。
世代
可以生成这样的密码哈希:$password = 'my password';$random = openssl_random_pseudo_bytes(18);$salt = sprintf('$2y$%02d$%s',
13, // 2^n cost factor
substr(strtr(base64_encode($random), '+', '.'), 0, 22));$hash = crypt($password, $salt);
盐是用openssl_random_pseudo_bytes()形成一个随机的数据块,然后通过base64_encode()和strtr()匹配所需的字母表[A-Za-z0-9/.].
这个crypt()函数根据以下算法执行散列($2y$对于Blowfish),成本因素(在一台3 GHz的机器上约为0.40)和22个字符的盐分(因子为13)。
验证
一旦获取了包含用户信息的行,就可以以下方式验证密码:$given_password = $_POST['password']; // the submitted password$db_hash = $row['Password']; // field with the password hash$given_hash = crypt($given_password, $db_hash);if (isEqual($given_hash, $db_hash)) {
// user password verified}// constant time string comparefunction isEqual($str1, $str2){
$n1 = strlen($str1);
if (strlen($str2) != $n1) {
return false;
}
for ($i = 0, $diff = 0; $i != $n1; ++$i) {
$diff |= ord($str1[$i]) ^ ord($str2[$i]);
}
return !$diff;}
要验证密码,请调用crypt()同样,但是您将先前计算的散列作为盐分值传递。如果给定密码与哈希匹配,则返回值将产生相同的哈希。为了验证哈希,通常建议使用常数时间比较函数来避免定时攻击。
用PHP5.5对密码进行散列
PHP 5.5引入了密码散列函数可以用来简化上述散列方法:$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 13]);
并核实:if (password_verify($given_password, $db_hash)) {
// password valid}