如何在 PHP 中正确加密?

信息安全 加密 密码学 php
2021-08-24 20:44:55

如何使用对称密钥加密正确加密 PHP 中的数据?我有一条消息 M 和一个秘密 S。我正在寻找一种能够正确使用密码学且不会犯任何常见错误的解决方案。特别是,该解决方案应使用经过身份验证的加密,正确选择 IV,并使用合适的慢散列从秘密 S 生成实际的加密密钥(以防 S 恰好是密码而不是实际的加密密钥)。

你能为此目的推荐 PHP 代码吗?


我的动机:我想给 PHP 程序员如何做到这一点的好建议,而不是坏建议。 StackOverflow上的这个问题确实令人失望:它充满了令人震惊的糟糕答案(ECB 模式加密?重复 IV?没有身份验证的加密?)。让我们找出正确的答案——一个正确的代码片段——然后在 StackOverflow 上修复那个坏掉的窗口。为了与 PHP 程序员的精神保持一致,我更喜欢一段代码(适用于尽可能多的设置;可能带有限制/警告的解释和/或解释),而不仅仅是关于算法和概念的建议。

3个回答

你能为此目的推荐 PHP 代码吗?

按优先顺序。

1. PECL 钠

如果你还没有听说过 libsodium,现在就去了解它如果可能,我强烈建议您使用 libsodium

这些说明应该让您开始在您的计算机上安装 libsodium 和 PECL 扩展。该电子书的其余部分应让您了解如何使用特定组件,并包含大量示例代码。如果你需要一个 PHP 密码学的起点来指向非专家,libsodium 是当今最好的。

有关密钥认证加密,请参阅电子书的这一章

2. paragonie/石盐

(免责声明:我写了 Halite。)

libsodium 的所有功能,以及如下所示的界面:

use \ParagonIE\Halite\Symmetric\Crypto as Symmetric;

$key = KeyFactory::loadEncryptionKey('/path/to/key/file');
$ciphertext = Symmetric::encrypt($plaintext, $key);

看看:paragonie/halite

3. defuse/php-加密

Defuse Security 在 Github 上的defuse/php-encryption上发布了一个加密*库

通过加密,我实际上是指经过身份验证的加密

  • 版本 1:
    • 带有 PKCS#7 填充和随机 IV 的 AES-128-CBC
    • HMAC-SHA-256(以恒定时间验证)
    • 使用 HKDF-SHA256 将您的密钥拆分为加密密钥和身份验证密钥
  • 版本 2:
    • 带有随机 nonce 的 AES-256-CTR(CTR 模式不需要填充)
    • HMAC-SHA-256(以恒定时间验证)
    • 使用 HKDF-SHA256 将您的密钥拆分为加密密钥和身份验证密钥;现在使用随机 HKDF salt 来减轻加密 2^64 块后随机 CTR 中生日冲突的影响

免责声明:我是这个库的合著者之一,尽管我的大部分贡献要等到版本 2 重写之后才会落地,其中包括版本标记和用于安全加密/解密文件的流接口。

4.zend/zend-crypt

我最后推荐这个,因为我没有亲自审核过它,但它Zend 框架的一部分,许多 PHP 安全专家证明了它的质量。如果您已经在使用 Zend 框架,请继续使用它。

更新:由于我最初写了这个答案,我确实在Zend\Crypt\PublicKey\RsaZF2015-10 aka CVE-2015-7503)中发现了一个问题。然而,他们的对称密钥加密实现似乎是安全的。

DW,答案需要更多地以设计模式的形式出现,而不是代码片段。原因之一是 PHP 和 PHP 社区的最新技术。普通 PHP 开发人员可以使用的“正确”示例很​​少。

这种困难的例子:

  1. 如果您在代码中输入 mcrypt,那么您做错了:截至我上次查看时,mycrypt 已不再维护,实际上已被废弃。但是,mycrypt 是能够从 /dev/urandom 获取随机数的扩展,这似乎是在基于 Linux 的服务器上执行此操作的正确方法。

  2. 如何安全地生成随机数:PHP 的 openssl 扩展出错了。Openssl 是用于新开发的扩展,但它并不能完全正确。

这使我们得出结论,我们需要创建自己的密码学代码。任何专家都会告诉我们,“不要那样做!” 但是对于 PHP,可能没有其他选择。有社区提供的加密包,但这是同一个问题:我们如何知道该包是否“正确”?

另一个原因是,对于 PHP,问题是由于特定情况而出现的。您是否尝试在不安全的网络中存储散列密码?一组协议可能是最好的。

您是否正在尝试保护服务器和移动应用程序之间的 Web 服务?不同的方法可能更合适。(例如,当攻击者可以对应用程序进行逆向工程时,您是否可以依赖应用程序对密钥保密?)

作为记录,以下是我考虑使用对称密钥加密的步骤。我正在使用 PHP 5.3、用于访问 /dev/urandom 的 mcrypt 扩展,以及用于 aes-256-cbc 的 openssl。

  1. 认识到一切都取决于保密密钥。如果可以从远程客户端软件(或从您的通信的任一端点)提取密钥,则有权访问加密文本的攻击者可以读取任何内容,并插入虚假消息。

  2. 认识到通过 HTTPS 传输,例如,对某些攻击提供的安全性不足。例如,攻击者可以安装我们的应用程序并观察到我们的应用程序发送/接收的解密 HTTPS 流量。

  3. 换句话说,了解您的加密运行的环境,并找出必须考虑哪些攻击。

  4. 加密的双方都需要相同的共享密钥。双方都需要获得密钥,并且都需要防止密钥被盗、被看到、被逆向工程等。

  5. 我相信(但缺乏足够的专业知识来确定)将您的密钥散列到恰好 256 位(对于 AES-256)是正确的做法,例如,使用 hash('sha256', $secretKey) 创建 256 位密钥输入加密/解密功能。换句话说,纯 ASCII 文本原样没有足够的熵直接用作密钥。因此,使用哈希函数创建(或截断)正确的字节数。

  6. 对于 AES,您需要一个 128 位的输入向量。了解每个加密/传输的输入向量都需要不同。我见过太多带有固定输入向量的代码示例!将 mcrypt 与 MCRYPT_DEV_URANDOM 模式一起用于随机字节。

  7. 末日原则*thanks @mti2935) 指出,如果您在验证其完整性之前对收到的消息执行任何加密操作,它将不可避免地导致厄运。一个最佳实践是使用 HMAC(下一点)。

  8. (没有足够的声誉发布链接):了解加密不是身份验证。HMAC 只能验证消息没有被更改。它还验证消息是由拥有您的密钥的人发送的。您可能会或可能不会认为该事实是足够的身份验证。

  9. HMAC 必须涵盖解密功能的所有输入。这意味着加密消息和输入向量。如果您有一些标识符显示要使用哪种解密算法,那也必须是 HMAC 计算的一部分。否则,攻击者可能会更改该指标并发起某些攻击,而您的 HMAC 验证仍然看起来很干净。

  10. 以上是“先加密后HMAC”的思想流派。有对立的思想流派,尤其是布鲁斯·施奈尔。Bruce 的推理是,要正确地“加密然后 HMAC”是非常困难的。如果布鲁斯说这特别困难,那就特别困难。

我的观点是,如果您以任何顺序进行加密和 HMAC'ing,请找出难以正确处理的内容,并同意您做对了。

编写加密代码不要发布它经过进一步思考,最好不要公开自己滚动的代码作为示例。使用 libsodium。