不使用mcrypt进行加密/解密

在一般的 PHP 社区成员中,一个鲜为人知的事实是,作为大多数基于 PHP 的加密的核心的 mcrypt 扩展被认为是安全的,但它并不安全。从安全的角度来看,最大的问题之一是 mcrypt 扩展需要高级的密码学知识才能成功运行,而很少有程序员具备这些知识。这就导致了严重的误操作,最终导致数据损坏的几率为256分之一等问题。这可不是什么好机会。此外,开发人员对libmcrypt的支持,即mcrypt扩展所基于的核心库,已于2007年被放弃,这意味着代码库已经过时,错误百出,并且没有应用补丁的机制。因此,了解如何在不使用mcrypt的情况下进行强加密/解密是非常重要的。

如何做...

1.之前提出的问题的解决方案,如果你想知道,就是使用openssl。这个扩展维护得很好,并且具有现代的、非常强大的加密/解密功能。

为了使用任何openssl*函数,openssl PHP扩展必须被编译并启用!此外,你需要在你的Web服务器上安装最新的OpenSSL包。

2. 首先,你需要确定你的安装中哪些密码方法是可用的。为此,您可以使用 openssl_get_cipher_methods() 命令。例子包括基于高级加密标准(AES)、BlowFish(BF)、CAMELLIA、CAST5、数据加密标准(DES)、Rivest密码(RC)(也被亲切地称为Ron's Code)和SEED的算法。你会注意到,这个方法显示的密码方法是大写和小写重复的。

3. 接下来,你需要弄清楚哪种方法最适合你的需求。下面是一个表格,对各种方法进行了快速的总结。

方法

发布时间

密钥大小(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的工作

4. 另一个考虑因素是你喜欢的块密码操作模式是什么。常见的选择总结在这个表中。

模式

代表

备注

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年)且速度较快;使用两把钥匙;增加了可作为一个区块安全加密的数据量

5. 在选择加密方法和模式之前,你还需要确定加密的内容是否需要在你的PHP应用之外解密。例如,如果你是将数据库凭证加密存储到一个独立的文本文件中,你是否需要具备从命令行解密的能力?如果是这样,请确保你选择的加密方法和操作模式是目标操作系统所支持的。

6. 为IV提供的字节数根据选择的密码方法而变化。为了得到最好的结果,可以使用 random_bytes()(PHP 7 中新增),它返回一个真正的 CSPRNG 字节序列。IV的长度有很大的不同。试着从16开始。如果产生警告,将显示该算法所需提供的正确字节数,因此要相应调整大小。

$iv  = random_bytes(16);

7. 要执行加密,使用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。

8. 举个例子,假设你想选择AES加密方式,密钥大小为256,XTS模式。下面是用于加密的代码。

$plainText = 'Super Secret Credentials';
$key = random_bytes(16);
$method = 'aes-256-xts';
$cipherText = openssl_encrypt($plainText, $method, $key, 0, $iv);

9. 要解密,对$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。

对于一些较新的模式,如果要加密的数据小于块大小,openssl_decrypt()将不返回任何值。如果你把数据填充到至少是块大小,问题就会消失。大多数的模式都实现了内部填充,所以这不是问题。对于一些较新的模式(即xts),你可能会看到这个问题。在将你的代码投入生产之前,一定要对小于8个字符的短数据串进行测试。

最后更新于