两种采用 PHP 双向加密——哪一种更可取?

信息安全 加密 密码学 php
2021-09-04 14:22:09

我需要加密一些数据并在稍后的时间点解密。数据与特定用户相关联。我收集了两种可能的解决方案......

1:第一个来自官方文档(示例#1 @ http://php.net/manual/en/function.mcrypt-encrypt.php):

function encrypt($toEncrypt)
{
    global $key;
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
    return base64_encode($iv . mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $toEncrypt, MCRYPT_MODE_CBC, $iv));
}

function decrypt($toDecrypt)
{
    global $key;
    $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
    $toDecrypt = base64_decode($toDecrypt);
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, substr($toDecrypt, $iv_size), MCRYPT_MODE_CBC, substr($toDecrypt, 0, $iv_size)));
}

使用以下命令生成一次密钥:

pack('H*', bin2hex(openssl_random_pseudo_bytes(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC))));

1.1:我注意到加密结果总是以两个等号('==')结尾。为什么?- 在 encrypt() 和 decrypt() 中分别使用 bin2hex() 和 hex2bin() 而不是 base64_encode()/base64_decode() 不会产生这些结果。

1.2:使用 bin2hex()/hex2bin() 会对结果产生任何影响(长度除外)吗?

1.3:似乎有一些讨论是否在解密时对返回结果调用修剪函数(这也适用于下面的解决方案)。为什么这是必要的?


2:第二种解决方案来自这里,Stackoverflow(https://stackoverflow.com/questions/9262109/php-simplest-two-way-encryption):

function encrypt($key, $toEncrypt)
{
    return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, md5($key), $toEncrypt, MCRYPT_MODE_CBC, md5(md5($key))));
}

function decrypt($key, $toDecrypt)
{
    return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, md5($key), base64_decode($toDecrypt), MCRYPT_MODE_CBC, md5(md5($key))), "\0");
}

我知道这两种按键处理方法是可以互换的,我特意让它们在这方面有所不同,以突出可能的解决方案,请随意混合搭配。

我个人觉得第一个提供了更严格的安全性,因为密钥和初始化向量都是正确随机的。然而,第二种解决方案确实提供了某种形式的不可预测性,因为每个加密数据的密钥都是唯一的(即使它受到 md5() 的弱随机化的影响)。例如,密钥可以是用户名。

3:那么,哪一个更可取?自从 Stackoverflow 的答案获得了 105 票以来,我有点不知所措。其他想法,提示?

4:额外的问题!:我在服务器安全方面并不是非常聪明,但显然获得对 PHP 文件的访问权限会暴露密钥,这直接导致加密无用,假设攻击者也可以访问D B。有什么办法可以隐藏钥匙吗?

感谢您的阅读,祝您有美好的一天!

2个回答

这两种加密是等价的。您可以添加一个包装器以使用一种方法作为另一种方法的替代:

/**
* Uses the one-parameter method as if it was the 2-parameter one.
*/
function buhlencrypt($keya, $data) {
   global $key;
   $key = $keya;
   return encrypt($data);
}

/**
* Uses the two-parameter method as if it was the one-parameter.
*/
function weidencrypt($data) {
   global $key;
   return encrypt($key, $data);
}

如果“不安全”,MD5 的“可预测性”会增加一个微不足道的水平 - 99.99999% 的用户将因为他们选择了一个微不足道的密码或将其写在地址簿中或将其留在键盘下方的黄色便笺上而被黑客入侵。专业攻击将破解服务器并收集所有密码,而不是暴力破解 MD5(见下文,回答问题 4)。

1.1:我注意到加密结果总是以两个等号('==')结尾。为什么?- 在 encrypt() 和 decrypt() 中分别使用 bin2hex() 和 hex2bin() 而不是 base64_encode()/base64_decode() 不会产生这些结果。

Base64 编码通过将三个二进制字节组(3 x 8 = 24 位)替换为四个 ASCII-6 字符(4 x 6 = 24 位)来工作。这意味着输入文本的长度必须是三的倍数,而输出的长度总是四的倍数。

为了确保这一点,Base64 在其输出中添加了一个特殊的填充,以表示是否存在一个或两个“可忽略”字符。由于 Rijndael 产生的输出是 16 的倍数,所以最后的 16 块总是填充到长度 18。

1.2:使用 bin2hex()/hex2bin() 会对结果产生任何影响(长度除外)吗?

没有任何。

1.3:解密时是否在返回结果上调用trim-function似乎有一些讨论

这是因为 Rijndael 也使用固定大小的块,所以如果要加密 17 个字节,它将被分成两个 16 字节的块,给出 32 个字节,其中最后 15 个是填充。由于 Rijndael 没有提供指示填充剥离的规定(例如,它不存储明文的长度),输出将具有可能需要删除的零填充。在某些情况下,这可能不是必需的,因为无论如何都可能忽略零填充。

4:额外的问题!:我在服务器安全方面并不是非常聪明,但显然获得对 PHP 文件的访问权限会暴露密钥,这直接导致加密无用,假设攻击者也可以访问D B。有什么办法可以隐藏钥匙吗?

如果您对称地执行此操作,则访问 PHP 文件也将允许消除遮挡。验证拥有 PHP 文件后可能具有的访问级别。如果他的访问即将完成,那么混淆密钥就没有什么意义了。

但是,您可以这样做,尽管是以迂回的方式。

向您的用户模型添加三个属性(即两个列表),称为 UserKey、RootKey 和 RootKeyOK。每个用户得到一个三元组。

当用户注册时,您生成一个随机 IV,并使用用户的密码对其进行加密,并将其存储在 UserKey 中。您还将它以明文形式存储在 RootKey 中,并将 RootKeyOK 设置为 FALSE。

当用户更改密码时,您使用旧密码解密 UserKey 并使用新密码重新加密。您根本不需要触摸 RootKey。

每当管理员登录时,它都会浏览用户数据库以查找不正确的密钥;对于它们中的每一个,它使用管理员的密码加密 RootKey,并将 RootKeyOK 设置为 TRUE。这可以自动完成。

现在在您的数据库中,您有:

  • HashedPassword --- 对攻击者不可用,因为它是一个散列。
  • UserKey --- 无法使用,因为它已加密
  • RootKey --- 可用,如果仍然是明确的,但仅用于该用户的数据

当用户登录并且您需要加密只有他(和管理员)可以读取的内容时,使用他的密码(他已登录,因此它在内存中)解密 userKey,并使用 userKey 加密数据。

当然,如果你需要在用户和管理员都没有登录的情况下加密某些东西,这种方法是行不通的——你必须用一个告诉它需要加密的标志来存储它加密的内容,并尽快执行用户或管理员登录。虽然您可以对两者透明地执行此操作,但实际上数据可能会在服务器上长时间保持清晰,容易受到数据库捕获(从远程闯入到硬盘盗窃)的影响。

我喜欢这个openssl_encrypt,有不同的,你可以试试:

http://www.the-art-of-web.com/php/two-way-encryption/