当前位置: 首页 > 知识库问答 >
问题:

在PHP中确定地从密码派生32字节的密钥

郎建章
2023-03-14

今天我了解到,“密码”往往意味着任意数量的字符的可记忆字符串,而“密钥”意味着高度随机的位字符串(基于所使用的加密算法的特定长度)。

所以今天我第一次听说了密钥派生函数的概念。

我对如何从任意长度的密码(在PHP中)派生32字节密钥感到困惑。

以下方法有效,但忽略了“[盐]应随机生成”的指示(钠也是如此):

$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的加密助手(例如Crypt::encryptString('Hello world.'))似乎不适合我的要求,因为我希望每个用户的数据根据每个人的密码单独加密。

无论我使用什么密钥派生函数,每次都需要生成相同的密钥,因为我将使用对称加密来解密用户用该密钥加密的字符串。

附言:感谢问题1、2和3向我介绍了某些概念。

共有3个答案

亢胤运
2023-03-14

但我仍然希望得到专家的回答,因为这感觉非常非官方和土生土长,让我怀疑它是否违反了任何安全“最佳实践”。

我很惊讶没有找到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或Lib钠php是否提供这种功能。

似乎Libsodium具有crypto_pwhash功能,这可能是我正在寻找的(并使用Argon2)。

养俊驰
2023-03-14

如果您希望在每次解密或加密存储的字符串时让他们重新发送密码,那么您必须使用一致的密码散列,并将salt和迭代存储在某个地方。

如果您使用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弃用,因此我建议您坚持使用第一个解决方案。

您不需要对每个人使用相同的salt,您可以生成一个随机的salt并存储在某个地方,比如users表。

请记住,使用这种类型的密钥派生,每次用户更改其密码时,都需要重新加密所有值。

伯博
2023-03-14

对于哈希-pbkdf2,你说:

“以下方法有效,但忽略了”[盐]应随机生成“的指令

解决办法是随机生成盐,并将其与密文存储在一起。关于如何在PHP中生成安全随机字节的方法,请参见这个问题。然后,输出可以用作加密的密钥;当然,密钥将总是使用存储的盐和记忆的密码重新生成,并且不需要存储。注意,键由原始字节组成;最好从< code>hash-pbkdf2(最后一个参数)中检索原始密钥。

请注意,迭代计数应尽可能高。通常,100,000左右被认为是当今最佳的,但越高越安全。攻击者计算每个密码的最终密钥所需的时间大致相同,并且由于密码仅包含30到60位(对于好密码),因此它确实有助于防止字典攻击

 类似资料:
  • 我将生成的salt前缀存储到加密文件。密码不存储,只有用户知道。 解决方案奏效了。我的问题是,今天这个解决方案是否足够好和安全?

  • 问题内容: 我正在尝试在php中生成一个随机密码。 但是我得到所有的’a’s,返回类型是数组类型,我希望它是一个字符串。有关如何更正代码的任何想法? 谢谢。 问题答案: 安全警告 :不是加密安全的伪随机数生成器。寻找其他地方以在PHP中生成加密安全的伪随机字符串。 试试这个(使用代替,因为在字符串上总是):

  • 给定一个任意的Java字节数组,例如1024字节数组,我想导出一个AES-256位密钥。该数组由ECHD通过使用字节[]秘密=keyAgreement.generate秘密()通过生成 我目前的解决方案是将输入字节数组视为密码。使用PBKDF2密钥派生函数将输入数组用作密码和salt,如下所示。 更新:我已经将UTF-8设置为编码,以解决评论和答案中指出的问题。 有没有更好的方法在Java中获取字

  • 我从这里使用解决方案: 问题是当我这样做时: 我得到一个非常奇怪的字符串,类似于,但我想要一个可以保存在DB中的普通字符串。我错在哪里?

  • 问题内容: 我需要解密密码。密码已使用功能加密。 现在,我们假设它存储在数据库中(有一个“用户”表,其中包含用户名,密码等),我需要登录:我必须查看用户输入的密码是否与存储在其中的加密密码匹配。数据库。 这是SQL代码… …但未加密,因此不等于表用户的密码字段中存储的内容… 所以,有一个使用?后解密的功能。还是应该更改我的加密方法?或者还有什么? 问题答案: Bcrypt是一种单向哈希算法,您无法