PHP 带恢复码的加密/解密

PHP encryption / deryption with recovery code

我尝试重建加密流程解释 here

加密

$password = 'pass123456';  //user password
$message = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.';

$Ku = openssl_random_pseudo_bytes(256 / 8); //user-specific symmetric key
$Ru = openssl_random_pseudo_bytes(256 / 8); //recovery code
$Su = openssl_random_pseudo_bytes(256 / 8); //user salt ###STORED ON SERVER###

$hash = hash_pbkdf2('sha256', $password, $Su, 5000, 256 / 8, true);

$split = str_split($hash, 16);
$Vu = $split[0]; //password verification token ###STORED ON SERVER###
$Ek = $split[1]; //key to encrypt Ku


$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);

$Eu = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ek, $Ku, MCRYPT_MODE_CBC, $iv); ###STORED ON SERVER###
$Fu = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ru, $Ku, MCRYPT_MODE_CBC, $iv); ###STORED ON SERVER###

$encryptedMessage = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $Ku, $message, MCRYPT_MODE_CBC, $iv);

解密

$hash = hash_pbkdf2('sha256', ###ENTERED PASSWORD###, ###STORED Su###, 5000, 256 / 8, true);
$split = str_split($hash, 16);
$Vu = $split[0];
$Ek = $split[1];


if($Vu != ###STORED Vu###) {
   => wrong password
} else {
   => correct password
}


$Ku = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $EK, ###STORED Eu###, MCRYPT_MODE_CBC, ###STORED iv###);
$message = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $Ku, $encryptedMessage, MCRYPT_MODE_CBC, ###STORED iv###);

如果我丢失了密码,我可以找回 "Ku":

$Ku = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, ###REVOVERY KEY###, ###STORED Fu###, MCRYPT_MODE_CBC, ###STORED iv###);

上面的代码安全吗? openssl_random_pseudo_bytes 足够随机还是有更好的可能性,即使用鼠标移动来生成初始化数字?

如何处理 mcrypt 的 "initialization vector"? this 正确吗?

分部分析

看起来不错,但您可能想要

  • 增加 PBKDF2 函数的迭代次数。现在5000已经很低了。尝试一百万。
  • 具有 256 位块大小的 Rijndael 不如具有 128 位块大小(AES)的 Rijndael 分析得好。你应该使用 MCRYPT_RIJNDAEL_128。这也将提高与其他实现的兼容性。
  • 您没有使用可以检测(恶意)对您的密文的操纵的密文身份验证。有关执行此操作的简单 OpenSSL 方法,请参阅 this example
  • 您正在使用 MCrypt 的默认零填充,如果您的明文以 0x00 字节结尾,这就不好了。从解密的消息中删除这些内容时,您需要小心。

Is openssl_random_pseudo_bytes random enough or is there a better possibility i.e. using mouse movement to generate a initialization number?

初始化向量只需要是不可预测的,而不是秘密的。如果 IV 生成是可预测的,则存在可以利用系统的攻击,但 openssl_random_pseudo_bytes 应该是好的。有很小的机会它没有正确初始化并且你将使用非 "strong" 随机字节(检查第二个参数),但即使那样取决于你的系统架构,IV 的可预测性可能不是可利用。

我也想知道你想如何在 PHP 中获得鼠标移动,因为我假设它在服务器上运行。

整体分析

该代码似乎实现了a good protocol described by Thomas Pronin,但协议略有不完整。因为没有什么可以阻止攻击者提交随机 Ru 并用它覆盖所有用户的值。

在创建帐户(和更改密码)期间,您还需要为恢复密钥创建一个验证值。我认为创建

就足够了
$VRu = hash_pbkdf2('sha256', $Ru, $Su, 1000, 256 / 8, true);

并将其存储在服务器上以备恢复时使用。

由于Ru是随机生成的,迭代次数可以少一些,但仍然是一个代价高昂的操作。您应该限制恢复操作的使用以减少拒绝服务攻击。