加密服务
重要
请勿使用本 Encryption 类或其他任何加密类来存储密码!密码应当进行哈希处理, 可通过 PHP 的 Password Hashing 扩展 完成。
加密服务提供双向对称(密钥)数据加密。该服务会根据你的参数实例化和/或初始化加密 handler (处理程序),具体说明如下。
加密服务的 handler 必须实现 CodeIgniter 的 EncrypterInterface 接口。
使用适当的 PHP 加密扩展或第三方库可能需要在服务器上安装额外的软件,
和/或需要在 PHP 实例中显式启用。
目前支持以下 PHP 扩展:
这不是一个完整的加密解决方案。如需更多功能(如公钥加密),建议直接使用 OpenSSL 或其他 加密扩展。 也可考虑使用更全面的包,如 Halite (基于 libsodium 构建的面向对象包)。
备注
已放弃对 MCrypt 扩展的支持,因为该扩展自 PHP 7.2 起已被弃用。
使用 Encryption 类
与 CodeIgniter 的所有服务一样,可通过 Config\Services 加载:
<?php
$encrypter = service('encrypter');
设置好起始密钥后(参见 配置类),加密和解密数据很简单——将适当的字符串传递给 encrypt() 和/或 decrypt() 方法:
<?php
$plainText = 'This is a plain-text message!';
$ciphertext = $encrypter->encrypt($plainText);
// Outputs: This is a plain-text message!
echo $encrypter->decrypt($ciphertext);
完成!Encryption 类会自动处理所有必要步骤,确保整个过程的加密安全性,开箱即用。
配置类
上述示例使用 app/Config/Encryption.php 中的配置设置。
选项 |
可选值(括号内为默认值) |
|---|---|
key |
加密密钥起始值 |
driver |
首选 handler,如 OpenSSL 或 Sodium( |
digest |
消息摘要算法( |
blockSize |
[SodiumHandler 专用] 填充字节长度( |
cipher |
[OpenSSLHandler 专用] 使用的加密算法( |
encryptKeyInfo |
[OpenSSLHandler 专用] 加密密钥信息( |
authKeyInfo |
[OpenSSLHandler 专用] 认证密钥信息( |
rawData |
[OpenSSLHandler 专用] 密文是否为原始格式( |
可以通过向 Services 调用传递自定义配置对象来替换配置文件中的设置。$config 变量必须是 Config\Encryption 类的实例。
<?php
use Config\Encryption;
$config = config(Encryption::class);
$config->key = 'aBigsecret_ofAtleast32Characters';
$config->driver = 'OpenSSL';
$encrypter = service('encrypter', $config);
与 CI3 保持兼容的配置
Added in version 4.3.0.
自 v4.3.0 起,可以解密使用 CI3 加密的数据。如需解密此类数据,请使用以下设置以保持兼容。
<?php
use Config\Encryption;
$config = new Encryption();
$config->driver = 'OpenSSL';
// Your CI3's 'encryption_key'
$config->key = hex2bin('64c70b0b8d45b80b9eba60b8b3c8a34d0193223d20fea46f8644b848bf7ce67f');
// Your CI3's 'cipher' and 'mode'
$config->cipher = 'AES-128-CBC';
$config->rawData = false;
$config->encryptKeyInfo = 'encryption';
$config->authKeyInfo = 'authentication';
$encrypter = service('encrypter', $config);
支持的 HMAC 认证算法
对于 HMAC 消息认证,Encryption 类支持使用 SHA-2 系列算法:
算法 |
原始长度(字节) |
十六进制编码长度(字节) |
|---|---|---|
SHA512 |
64 |
128 |
SHA384 |
48 |
96 |
SHA256 |
32 |
64 |
SHA224 |
28 |
56 |
不包含 MD5 或 SHA1 等其他流行算法的原因是它们已不再被视为足够安全,因此不鼓励使用。 如果确实需要使用,可通过 PHP 原生的 hash_hmac() 函数轻松实现。
当然,随着更安全算法的出现和普及,未来会添加更多的算法支持。
默认行为
默认情况下,Encryption 类使用 OpenSSL handler。该 handler 使用 AES-256-CTR 算法、你配置的 key 和 SHA512 HMAC 认证进行加密。
设置加密密钥
加密密钥 必须 与所用加密算法允许的长度一致。对于 AES-256,即 256 位或 32 字节(字符)长。
密钥应尽可能随机,绝不能 是普通文本字符串,也不能是哈希函数的输出等。要创建合适的密钥,可使用 Encryption 类的 createKey() 方法。
<?php
// $key will be assigned a 32-byte (256-bit) random key
$key = \CodeIgniter\Encryption\Encryption::createKey();
// for the SodiumHandler, you can use either:
$key = sodium_crypto_secretbox_keygen();
$key = \CodeIgniter\Encryption\Encryption::createKey(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
密钥可以存储在 app/Config/Encryption.php 中,也可以设计自己的存储机制,在加密/解密时动态传递密钥。
要将密钥保存到 app/Config/Encryption.php,打开文件并设置:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Encryption extends BaseConfig
{
public $key = 'YOUR KEY';
// ...
}
编码密钥或结果
createKey() 方法输出的是二进制数据,难以处理(例如复制粘贴可能会损坏),因此可以使用 bin2hex() 或 base64_encode 以更易用的方式处理密钥。例如:
<?php
// Get a hex-encoded representation of the key:
$encoded = bin2hex(\CodeIgniter\Encryption\Encryption::createKey(32));
// Put the same value with hex2bin(),
// so that it is still passed as binary to the library:
$key = hex2bin('your-hex-encoded-key');
同样的技巧也适用于加密结果:
<?php
// Encrypt some text & make the results text
$encoded = base64_encode($encrypter->encrypt($plaintext));
使用前缀存储密钥
存储加密密钥时可以使用两个特殊前缀:hex2bin: 和 base64:。当这些前缀紧接在密钥值之前时,Encryption 会智能解析密钥,并仍将二进制字符串传递给库。
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Encryption extends BaseConfig
{
// In Encryption, you may use
public $key = 'hex2bin:<your-hex-encoded-key>';
// or
public $key = 'base64:<your-base64-encoded-key>';
// ...
}
同样,也可以在 .env 文件中使用这些前缀!
// 用于 hex2bin
encryption.key = hex2bin:<your-hex-encoded-key>
// 或
encryption.key = base64:<your-base64-encoded-key>
加密密钥轮换
Added in version 4.7.0.
基于安全最佳实践或合规要求需轮换加密密钥时,可利用 previousKeys 配置项。这能在新加密操作使用新密钥的同时,确保仍可解密旧密钥加密的数据。
工作原理
加密 始终使用当前的
key值解密 首先尝试当前的
key解密失败时,程序会自动回退并逐一尝试
previousKeys中的密钥由此可实现无缝密钥轮换,避免数据丢失
配置
将旧密钥添加到 app/Config/Encryption.php 的 $previousKeys 属性中:
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class Encryption extends BaseConfig
{
public string $key = 'hex2bin:your_new_encryption_key_in_hex';
public array|string $previousKeys = [
'hex2bin:your_old_encryption_key_in_hex',
'hex2bin:another_old_key_if_needed',
];
// ... other config options
}
使用 .env 文件
亦可在 .env 文件(推荐做法)中使用逗号分隔的列表配置旧密钥:
encryption.key = hex2bin:your_new_key
encryption.previousKeys = hex2bin:old_key_1,hex2bin:old_key_2
框架会自动将逗号分隔的字符串解析为数组并处理每个密钥。
密钥轮换流程
轮换前:使用
key加密和解密数据。开始轮换:将当前
key值移入previousKeys数组,并为key设置新值。轮换期间:新数据由新
key加密,旧数据仍可通过previousKeys解密。重新加密数据(可选):使用新密钥解密并重新加密现有数据。
完成轮换:所有数据重新加密后,从
previousKeys中移除旧密钥。
重要
previousKeys 特性仅用于 解密回退。所有新加密操作始终使用当前的 key。若通过 encrypt() 或 decrypt() 的 $params 参数传递了显式密钥,则不会触发 previousKeys 回退机制。
填充
有时,消息长度本身可能泄露很多关于其性质的信息。如果消息是"yes"、"no"和"maybe"之一,加密消息并无帮助:知道长度就足以判断消息内容。
填充是一种缓解此问题的技术,通过将长度变为给定块大小的倍数来实现。
SodiumHandler 使用 libsodium 原生的 sodium_pad 和 sodium_unpad 函数实现填充。这需要在加密前将填充长度(以字节为单位)添加到明文消息,解密后移除。填充可通过 Config\Encryption 的 $blockSize 属性配置。该值应大于零。
重要
不建议自行设计填充实现。应始终使用库提供的更安全实现。此外,密码不应进行填充。 不推荐通过填充来隐藏密码长度。客户端向服务器发送密码时应先进行哈希处理(即使只对哈希函数进行一次迭代)。 这确保传输数据长度恒定,且服务器不会轻松获得密码副本。
加密处理程序说明
OpenSSL 说明
OpenSSL 扩展长期以来一直是 PHP 的标准组成部分。
CodeIgniter 的 OpenSSL handler 使用 AES-256-CTR 加密算法。
配置提供的 key 用于派生另外两个密钥:一个用于加密,一个用于认证。这是通过 基于 HMAC 的密钥派生函数 (HKDF)技术实现的。
Sodium 说明
Sodium 扩展自 PHP 7.2.0 起默认捆绑在 PHP 中。
Sodium 使用 XSalsa20 算法加密、Poly1305 进行 MAC 认证、XS25519 进行密钥交换,用于端到端场景中的秘密消息传输。要使用共享密钥(如对称加密)加密和/或认证字符串,Sodium 使用 XSalsa20 算法加密,HMAC-SHA512 进行认证。
消息长度
加密后的字符串通常比原始明文字符串更长(取决于加密算法)。
这受加密算法本身、添加到密文前部的初始化向量(IV)以及添加的 HMAC 认证消息的影响。此外,加密消息还会进行 Base64 编码,以便无论使用何种字符集都能安全存储和传输。
选择数据存储机制时请考虑此信息。例如,Cookie 只能容纳 4K 信息。
直接使用加密服务
除了(或作为替代)使用 使用 Encryption 类 中描述的 Services 外,还可以直接创建"Encrypter",或更改现有实例的设置。
<?php
// create an Encryption instance
$encryption = new \CodeIgniter\Encryption\Encryption();
// reconfigure an instance with different settings
$encrypter = $encryption->initialize($config);
记住,$config 必须是 Config\Encryption 类的实例。
类参考
- class CodeIgniter\Encryption\Encryption
- static createKey([$length = 32])
- 参数:
$length (
int) -- 输出长度
- 返回:
指定长度的伪随机加密密钥,失败时返回
false- 返回类型:
string
通过从操作系统源(即
/dev/urandom)获取随机数据创建加密密钥。
- initialize([Encryption $config = null])
- 参数:
$config (
Config\Encryption) -- 配置参数
- 返回:
CodeIgniter\Encryption\EncrypterInterface实例- 返回类型:
CodeIgniter\Encryption\EncrypterInterface- Throws:
CodeIgniter\Encryption\Exceptions\EncryptionException
初始化(配置)库以使用不同的设置。
示例:
<?php $encrypter = $encryption->initialize(['cipher' => 'AES-256-CTR']);
详细信息请参见 配置类 部分。
- interface CodeIgniter\Encryption\CodeIgniter\Encryption\EncrypterInterface
- encrypt($data[, $params = null])
- 参数:
$data (
string) -- 要加密的数据$params (
array|string|null) -- 配置参数(密钥)
- 返回:
加密后的数据
- 返回类型:
string- Throws:
CodeIgniter\Encryption\Exceptions\EncryptionException
加密输入数据并返回密文。
如果将参数作为第二个参数传递,当
$params为数组时,key元素将用作本次操作的起始密钥;或者可以将起始密钥作为字符串传递。如果使用 SodiumHandler 并希望在运行时传递不同的
blockSize,请在$params数组中传递blockSize键。示例:
<?php $ciphertext = $encrypter->encrypt('My secret message'); $ciphertext = $encrypter->encrypt('My secret message', ['key' => 'New secret key']); $ciphertext = $encrypter->encrypt('My secret message', ['key' => 'New secret key', 'blockSize' => 32]); $ciphertext = $encrypter->encrypt('My secret message', 'New secret key'); $ciphertext = $encrypter->encrypt('My secret message', ['blockSize' => 32]);
- decrypt($data[, $params = null])
- 参数:
$data (
string) -- 要解密的数据$params (
array|string|null) -- 配置参数(密钥)
- 返回:
解密后的数据
- 返回类型:
string- Throws:
CodeIgniter\Encryption\Exceptions\EncryptionException
解密输入数据并以明文返回。
如果将参数作为第二个参数传递,当
$params为数组时,key元素将用作本次操作的起始密钥;或者可以将起始密钥作为字符串传递。如果使用 SodiumHandler 并希望在运行时传递不同的
blockSize,请在$params数组中传递blockSize键。示例:
<?php echo $encrypter->decrypt($ciphertext); echo $encrypter->decrypt($ciphertext, ['key' => 'New secret key']); echo $encrypter->decrypt($ciphertext, ['key' => 'New secret key', 'blockSize' => 32]); echo $encrypter->decrypt($ciphertext, 'New secret key'); echo $encrypter->decrypt($ciphertext, ['blockSize' => 32]);