如何通过 OpenSSL 在 PHP 中使用 AES-256 算法实现数据的对称加密?
- 内容介绍
- 文章标签
- 相关推荐
本文共计968个文字,预计阅读时间需要4分钟。
直接使用`openssl_encrypt`和`openssl_decrypt`可以实现加密和解密,但密钥长度、IV生成方式和模式选择这三者如果不匹配,解密将失败。
为什么 AES-256-CBC 解密总返回 false
常见错误是密钥长度不对或 IV 长度错配。AES-256 要求密钥正好 32 字节,不是字符串长度 32,而是二进制长度 32 —— 如果你传入 'my-secret-key-256' 这种 ASCII 字符串,它只有 17 字节,openssl_encrypt 不报错但会静默截断或填充,导致解密失败。
- 用
strlen($key) === 32检查密钥字节长度,不是mb_strlen - CBC 模式下 IV 必须是 16 字节,可通过
openssl_cipher_iv_length('aes-256-cbc')动态获取,别硬写 16 - 加密和解密必须用完全相同的
$method字符串,大小写敏感:'AES-256-CBC'和'aes-256-cbc'在某些 OpenSSL 版本中不等价
如何安全生成和复用密钥与 IV
密钥绝不能写死,IV 绝不能复用。每次加密都该新生成 IV,并和密文一起存储/传输;密钥则应从环境变量或 KMS 获取,而不是拼接字符串或哈希固定值。
- 生成密钥:用
random_bytes(32)(PHP 7.0+)或openssl_random_pseudo_bytes(32, $strong),确认$strong === true - 生成 IV:用
random_bytes(openssl_cipher_iv_length('aes-256-cbc')),不要用md5(time())或uniqid() - 存储格式建议:把 IV 拼在密文前(如
$iv . $ciphertext),解密时用substr($data, 0, $ivLen)提取,避免 JSON 或 base64 嵌套带来的解析歧义
AES-256-GCM 比 CBC 更值得优先考虑
GCM 模式自带完整性校验,能发现密文是否被篡改,而 CBC 完全不提供这个能力。只要 PHP ≥ 7.1 且 OpenSSL ≥ 1.0.1,就该默认选 GCM。
立即学习“PHP免费学习笔记(深入)”;
- GCM 的 IV 推荐 12 字节(96 位),不是 16 字节;用
openssl_cipher_iv_length('aes-256-gcm')获取最稳妥 - 加密时必须接收
$tag引用参数:openssl_encrypt($data, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag) - 解密时必须传入
$tag:openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag),漏掉就解密失败 - 可选加 AAD(附加认证数据),比如用户 ID 或时间戳,它不加密但参与认证,防止密文被挪用到其他上下文
base64 编码时机和位置很关键
OpenSSL 函数默认返回原始二进制数据,直接 echo 或存数据库会出乱码甚至截断。但编码不能早于 IV 和密文组装完成。
- 错误做法:分别对 IV 和密文做
base64_encode,再拼 JSON —— 增加解析负担且易出错 - 推荐做法:先拼好
$iv . $ciphertext(GCM 还要拼$tag),再整体base64_encode - 解密时反向操作:
base64_decode后用substr分离,不要依赖json_decode或正则提取 base64 片段 - 如果走 HTTP API 传输,确保 header 设置
Content-Type: application/octet-stream或明确约定 base64 编码,避免网关自动转码
最常被忽略的一点:GCM 的 $tag 是 16 字节二进制数据,不是字符串,不能用 urlencode 或直接当文本处理;CBC 虽然没 tag,但 IV 若重复使用,攻击者可能通过观察密文块关系还原部分明文 —— 这类风险不会抛异常,只会悄悄失效。
本文共计968个文字,预计阅读时间需要4分钟。
直接使用`openssl_encrypt`和`openssl_decrypt`可以实现加密和解密,但密钥长度、IV生成方式和模式选择这三者如果不匹配,解密将失败。
为什么 AES-256-CBC 解密总返回 false
常见错误是密钥长度不对或 IV 长度错配。AES-256 要求密钥正好 32 字节,不是字符串长度 32,而是二进制长度 32 —— 如果你传入 'my-secret-key-256' 这种 ASCII 字符串,它只有 17 字节,openssl_encrypt 不报错但会静默截断或填充,导致解密失败。
- 用
strlen($key) === 32检查密钥字节长度,不是mb_strlen - CBC 模式下 IV 必须是 16 字节,可通过
openssl_cipher_iv_length('aes-256-cbc')动态获取,别硬写 16 - 加密和解密必须用完全相同的
$method字符串,大小写敏感:'AES-256-CBC'和'aes-256-cbc'在某些 OpenSSL 版本中不等价
如何安全生成和复用密钥与 IV
密钥绝不能写死,IV 绝不能复用。每次加密都该新生成 IV,并和密文一起存储/传输;密钥则应从环境变量或 KMS 获取,而不是拼接字符串或哈希固定值。
- 生成密钥:用
random_bytes(32)(PHP 7.0+)或openssl_random_pseudo_bytes(32, $strong),确认$strong === true - 生成 IV:用
random_bytes(openssl_cipher_iv_length('aes-256-cbc')),不要用md5(time())或uniqid() - 存储格式建议:把 IV 拼在密文前(如
$iv . $ciphertext),解密时用substr($data, 0, $ivLen)提取,避免 JSON 或 base64 嵌套带来的解析歧义
AES-256-GCM 比 CBC 更值得优先考虑
GCM 模式自带完整性校验,能发现密文是否被篡改,而 CBC 完全不提供这个能力。只要 PHP ≥ 7.1 且 OpenSSL ≥ 1.0.1,就该默认选 GCM。
立即学习“PHP免费学习笔记(深入)”;
- GCM 的 IV 推荐 12 字节(96 位),不是 16 字节;用
openssl_cipher_iv_length('aes-256-gcm')获取最稳妥 - 加密时必须接收
$tag引用参数:openssl_encrypt($data, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag) - 解密时必须传入
$tag:openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag),漏掉就解密失败 - 可选加 AAD(附加认证数据),比如用户 ID 或时间戳,它不加密但参与认证,防止密文被挪用到其他上下文
base64 编码时机和位置很关键
OpenSSL 函数默认返回原始二进制数据,直接 echo 或存数据库会出乱码甚至截断。但编码不能早于 IV 和密文组装完成。
- 错误做法:分别对 IV 和密文做
base64_encode,再拼 JSON —— 增加解析负担且易出错 - 推荐做法:先拼好
$iv . $ciphertext(GCM 还要拼$tag),再整体base64_encode - 解密时反向操作:
base64_decode后用substr分离,不要依赖json_decode或正则提取 base64 片段 - 如果走 HTTP API 传输,确保 header 设置
Content-Type: application/octet-stream或明确约定 base64 编码,避免网关自动转码
最常被忽略的一点:GCM 的 $tag 是 16 字节二进制数据,不是字符串,不能用 urlencode 或直接当文本处理;CBC 虽然没 tag,但 IV 若重复使用,攻击者可能通过观察密文块关系还原部分明文 —— 这类风险不会抛异常,只会悄悄失效。

