不使用mcrypt进行加密/解密
在一般的 PHP 社区成员中,一个鲜为人知的事实是,作为大多数基于 PHP 的加密的核心的 mcrypt
扩展被认为是安全的,但它并不安全。从安全的角度来看,最大的问题之一是 mcrypt
扩展需要高级的密码学知识才能成功运行,而很少有程序员具备这些知识。这就导致了严重的误操作,最终导致数据损坏的几率为256分之一等问题。这可不是什么好机会。此外,开发人员对libmcrypt
的支持,即mcrypt
扩展所基于的核心库,已于2007年被放弃,这意味着代码库已经过时,错误百出,并且没有应用补丁的机制。因此,了解如何在不使用mcrypt
的情况下进行强加密/解密是非常重要的。
如何做…
1.之前提出的问题的解决方案,如果你想知道,就是使用openssl
。这个扩展维护得很好,并且具有现代的、非常强大的加密/解密功能。
{% hint style=”danger” %}
为了使用任何openssl*
函数,openssl PHP扩展必须被编译并启用!此外,你需要在你的Web服务器上安装最新的OpenSSL
包。
{% endhint %}
首先,你需要确定你的安装中哪些密码方法是可用的。为此,您可以使用
openssl_get_cipher_methods()
命令。例子包括基于高级加密标准(AES)、BlowFish(BF)、CAMELLIA、CAST5、数据加密标准(DES)、Rivest密码(RC)(也被亲切地称为Ron’s Code)和SEED的算法。你会注意到,这个方法显示的密码方法是大写和小写重复的。接下来,你需要弄清楚哪种方法最适合你的需求。下面是一个表格,对各种方法进行了快速的总结。
方法 | 发布时间 | 密钥大小(bits) | 密钥块大小 (bytes) | 备注 |
---|---|---|---|---|
camellia
|
2000 | 128, 192, 256 | 16 | 由三菱和NTT共同开发 |
aes
|
1998 | 128, 192, 256 | 16 | 由Joan Daemen和Vincent Rijmen开发。最初以Rijndael的名义提交 |
seed
|
1998 | 128 | 16 | 由韩国信息安全局开发 |
cast5
|
1996 | 40-128 | 8 | 由Carlisle Adams和Stafford Tavares开发 |
bf
|
1993 | 1 - 448 | 8 | 由Bruce Schneier设计 |
rc2
|
1987 |
8 - 1,024 默认 64 |
8 | 由Ron Rivest(RSA的核心创始人之一)设计 |
des
|
1977 | 56 (+8 奇偶校验位) | 8 | 由IBM开发,基于Horst Feistel的工作 |
- 另一个考虑因素是你喜欢的块密码操作模式是什么。常见的选择总结在这个表中。
模式 | 代表 | 备注 |
---|---|---|
ECB | Electronic Code Book | 不需要初始化向量(IV);加密和解密都支持并行化;简单快速;不隐藏数据模式;不推荐!!! |
CBC | Cipher Block Chaining | 需要IV;后续的块,即使是相同的,也会与前一个块进行XOR编辑,从而获得更好的整体加密效果;如果IV是可预测的,第一个块可以被解密,剩下的信息就会暴露出来;信息必须被填充到密码块大小的倍数;只支持解密的并行化 |
CFB | Cipher Feedback | CBC的近亲,只是加密方式是反向的 |
OFB | Output Feedback | 非常对称:加密和解密相同;完全不支持并行化 |
CTR | Counter | 与OFB的操作类似;支持加密和解密的并行化 |
CCM | Counter with CBC-MAC | CTR的衍生物;仅设计为128位的块长;提供认证和保密性;CBC-MAC代表密码块链-信息认证码 |
GCM | Galois/Counter Mode | 基于CTR模式;应该对每个流使用不同的IV进行加密;特别高的吞吐量(与其他模式相比);支持加密和解密的并行化 |
XTS | XEX-based Tweaked-codebook mode with ciphertext Stealing | 相对较新(2010年)且速度较快;使用两把钥匙;增加了可作为一个区块安全加密的数据量 |
在选择加密方法和模式之前,你还需要确定加密的内容是否需要在你的PHP应用之外解密。例如,如果你是将数据库凭证加密存储到一个独立的文本文件中,你是否需要具备从命令行解密的能力?如果是这样,请确保你选择的加密方法和操作模式是目标操作系统所支持的。
为IV提供的字节数根据选择的密码方法而变化。为了得到最好的结果,可以使用
random_bytes()
(PHP 7 中新增),它返回一个真正的 CSPRNG 字节序列。IV的长度有很大的不同。试着从16开始。如果产生警告,将显示该算法所需提供的正确字节数,因此要相应调整大小。
$iv = random_bytes(16);
- 要执行加密,使用
openssl_encrypt()
。以下是需要传递的参数。
参数 | 备注 |
---|---|
Data | 纯文本你需要加密的内容 |
Method | 你用openssl_get_cipher_methods() 确定的一个方法,确定如下。 方法 - key_size - cipher_mode 所以,举例来说,如果你想采用AES的方法,密钥大小为256,并采用GCM模式,你可以输入aes-256-gcm 。 |
Password | 虽然这个参数被记录为密码,但它可以被看作是一个密钥。使用random_bytes() 来生成一个与所需密钥大小相匹配的字节数的密钥。 |
Options | 在你获得更多openssl 加密经验之前,建议你坚持使用默认值0。 |
IV | 使用random_bytes() 来生成一个具有与密码方法相匹配的字节数的IV。 |
- 举个例子,假设你想选择AES加密方式,密钥大小为256,XTS模式。下面是用于加密的代码。
$plainText = 'Super Secret Credentials';
$key = random_bytes(16);
$method = 'aes-256-xts';
$cipherText = openssl_encrypt($plainText, $method, $key, 0, $iv);
- 要解密,对
$key
和$iv
使用相同的值,以及openssl_decrypt()
函数。
$plainText = openssl_decrypt($cipherText, $method, $key, 0, $iv);
如何运行…
为了查看可用的密码方法,创建一个名为chap_12_openssl_encryption.php
的PHP脚本,然后运行这个命令。
<?php
echo implode(', ', openssl_get_cipher_methods());
输出应该是这样的。
接下来,可以为要加密的纯文本、方法、密钥和IV添加值。举个例子,试试使用XTS操作模式下的AES,密钥大小为256。
$plainText = 'Super Secret Credentials';
$method = 'aes-256-xts';
$key = random_bytes(16);
$iv = random_bytes(16);
要加密,你可以使用openssl_encrypt()
,指定之前配置的参数。
$cipherText = openssl_encrypt($plainText, $method, $key, 0, $iv);
你可能还想对结果进行base64编码,以使其更加可用。
$cipherText = base64_encode($cipherText);
解密时,使用相同的$key
和$iv
值。不要忘了先解密base64值。
$plainText = openssl_decrypt(base64_decode($cipherText),
$method, $key, 0, $iv);
这里的输出显示的是base64编码的密码文本,然后是解密后的明文。
如果你为IV提供的字节数不正确,对于所选择的密码方法,将显示一条警告信息。
更多…
在 PHP 7 中,当使用 open_ssl_encrypt()
和 open_ssl_decrypt()
以及所支持的认证数据加密 (AEAD) 模式时出现了一个问题——GCM 和 CCM。因此,在 PHP 7.1 中,这些函数增加了三个额外的参数,如下所示。
参数 | 备注 |
---|---|
$tag |
通过引用传递的认证标签;如果认证失败,变量值保持不变。 |
$aad |
额外的认证数据 |
$tag_length |
GCM模式为4-16;CCM模式无限制;仅适用于open_ssl_encrypt() |
更多信息,您可以参考https://wiki.php.net/rfc/openssl\_aead。
更多…
关于为什么在 PHP 7.1 中取消 mcrypt 扩展的优秀讨论,请参考 https://wiki.php.net/rfc/mcrypt-viking-funeral 的文章。关于构成各种加密方法基础的块加密的良好描述,请参考 https://en.wikipedia.org/wiki/Block\_cipher 的文章。关于AES的优秀描述,请参考https://en.wikipedia.org/wiki/Advanced\_Encryption\_Standard。关于描述加密操作模式的优秀文章,可以参考https://en.wikipedia.org/wiki/Block\_cipher\_mode\_of\_operation。
{% hint style=”info” %}
对于一些较新的模式,如果要加密的数据小于块大小,openssl_decrypt()
将不返回任何值。如果你把数据填充到至少是块大小,问题就会消失。大多数的模式都实现了内部填充,所以这不是问题。对于一些较新的模式(即xts),你可能会看到这个问题。在将你的代码投入生产之前,一定要对小于8个字符的短数据串进行测试。
{% endhint %}