如何通过 OpenSSL 在 PHP 中使用 AES-256 算法实现数据的对称加密?

2026-04-27 20:351阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计968个文字,预计阅读时间需要4分钟。

如何通过 OpenSSL 在 PHP 中使用 AES-256 算法实现数据的对称加密?

直接使用`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)
  • 解密时必须传入 $tagopenssl_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 若重复使用,攻击者可能通过观察密文块关系还原部分明文 —— 这类风险不会抛异常,只会悄悄失效。

标签:PHPSSL

本文共计968个文字,预计阅读时间需要4分钟。

如何通过 OpenSSL 在 PHP 中使用 AES-256 算法实现数据的对称加密?

直接使用`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)
  • 解密时必须传入 $tagopenssl_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 若重复使用,攻击者可能通过观察密文块关系还原部分明文 —— 这类风险不会抛异常,只会悄悄失效。

标签:PHPSSL