今天我了解到“密码” 倾向于表示一个可记住的任意数量字符的字符串,而“ key” 则表示一个高度随机的位字符串(具体长度取决于所使用的加密算法。
所以今天我首先听说了Key derivation function的概念。
我对如何从任意长度的密码(在PHP中)中派生32字节的密钥感到困惑。
以下方法可行,但忽略了{[3]应该随机生成的盐”中的the instruction(Sodium也是如此):
$salt = 'this salt remains constant';
$iterations = 10;
$length = 32;
$aesKey = hash_pbkdf2('sha256', $somePasswordOfArbitraryLength, $salt, $iterations, $length, false);
以下方法也可以使用,但感觉也不正确:
$hash = password_hash($somePasswordOfArbitraryLength, PASSWORD_BCRYPT, ['cost' => $iterations]);
$aesKey = substr($hash, -$length);//this smells weird
在所有搜索中,我很惊讶我没有在PHP中找到确定性地从密码中派生32字节密钥的“正式”方式。
我应该怎么做?
P.S。我在PHP中使用Laravel,并想使用AES-256-CBC加密,如下所示:
$encrypter = new \Illuminate\Encryption\Encrypter($aesKey, 'AES-256-CBC');
$encryptedText = $encrypter->encrypt($text);
Laravel的encryption helper(例如Crypt::encryptString('Hello world.')
)似乎不符合我的要求,因为我希望根据每个人的密码分别加密每个用户的数据。
我使用的任何密钥派生函数每次都需要生成相同的密钥,因为我将使用对称加密来解密该用户使用该密钥加密的字符串。
答案 0 :(得分:3)
对于hash-pbkdf2,您说:
“以下方法有效,但忽略了“ [盐]应该随机生成”的指示。
好吧,解决方法是随机生成盐,并将其与密文一起存储。有关如何在PHP中生成安全的随机字节的方法,请参见this question。然后可以将输出用作加密的密钥。当然,密钥将始终使用存储的盐和已存储的密码来重新生成,并且不需要存储。请注意,密钥由原始字节组成;最好是从hash-pbkdf2
(最后一个参数)中检索原始密钥。
请注意,迭代次数应尽可能多。如今,通常认为100,000左右是最佳选择,但是越高越安全。攻击者需要花费大量时间来计算每个密码的结果密钥,并且由于密码仅包含30到60位(对于好的密码而言),确实有助于抵御字典攻击。
答案 1 :(得分:2)
如果希望每次解密或加密存储的字符串时让他们重新发送密码,则必须使用一致的密码哈希并将盐和迭代存储在某个位置。
如果使用password_hash函数,由于随机生成的盐,您将永远不会获得相同的值。
>>> password_hash('abc', PASSWORD_BCRYPT)
=> "$2y$10$xR8tZQd0ljF5Ks3QrQt7i.vAbv.xVUc97uh.fX4w0mi/A647HlEWS"
>>> password_hash('abc', PASSWORD_BCRYPT)
=> "$2y$10$KzZWeg.o/4TyJVryWrz/oeWQ6VGj0JnPDW.d.Cp0svu8k6qKBcbWu"
您可以在选项中添加一个空格,但是password_hash已弃用,因此建议您坚持使用第一个解决方案。
您不需要为每个人使用相同的盐,您可以生成随机盐并将其存储在某个地方,例如users表。
请记住,通过这种类型的密钥派生,您每次用户更改密码时都需要重新加密所有值。
答案 2 :(得分:0)
但是我仍然希望得到专家的回答,因为这是非常非正式的和本土的,并且使我怀疑它是否违反了安全性的“最佳实践”。
我很惊讶没有找到内置在PHP中的简单函数。
/**
* It seems like we just need a way of getting a 32-byte key when all we have is a human-memorizable password and a salt for that user. But this function feels home-grown; what is the most secure way to do PBE (password-based encryption)?
*
* @param string $password
* @param string $salt Since this function must be deterministic (return a value consistently based on the inputs), it must accept a salt as an argument rather than generate a random salt every time. Storing a different salt for each user improves security.
* @param int $length
* @return string
*/
public static function deriveKey($password, $salt, $length = self::KEY_BYTES) {
$iterations = max([intval(config('hashing.bcrypt.rounds')), 15]);
$chars = 2 * $length; //In hex, a byte is always expressed as 2 characters. See more comments below and https://stackoverflow.com/a/43132091/.
$rawOutput = false; //Default is false. When set to TRUE, outputs raw binary data. FALSE outputs lowercase hexits. Hexit = hexadecimal digit (like "bit" = binary digit). There are 16 hexits: the numbers 0 to 9 and the letters A to F.
$key = hash_pbkdf2('sha256', $password, $salt, $iterations, $chars, $rawOutput); //A sha256 is 256 bits long (32 bytes), but the raw_output argument will determine how many characters the result has. https://stackoverflow.com/a/2241014/ and https://crypto.stackexchange.com/q/34995/
return $key;
}
我想知道Halite或Libsodium-php是否提供这种功能。
似乎Libsodium具有crypto_pwhash函数,这可能正是我正在寻找的(并使用Argon2)。