尧图精选

PHP 8.2中新增的openssl_cipher_iv_length函数怎么获取加密向量长度

🕒 发布时间:2026/10/1 22:20:07 📁 来源:尧图网络
前言用openssl_encrypt()写加解密时最常见的两类故障是加密时报openssl_encrypt(): IV passed is 16 bytes long which is longer than the 12 expected by selected cipher, truncating这样的警告或者解密出来是一串乱码。两次都指向同一个变量初始化向量initialization vectorIV。IV 的长度由算法决定多一字节少一字节都不行硬编码一个看起来像的值迟早出问题。正确做法是让 PHP 自己告诉你该算法需要多长的 IV这就是openssl_cipher_iv_length()的用途。这里必须先纠正标题里的版号问题openssl_cipher_iv_length()不是 PHP 8.2 新增的它是 PHP 5.3.3 引入的已经有十多年历史了。PHP 8.2 在 OpenSSL 这一块真正新增的是另一个函数——openssl_cipher_key_length()用来查算法的密钥长度。两者的名字很像经常被记混本文会把两个都讲清楚凡是用到 8.2 专属能力的地方都会单独标注。本文按IV 为什么有长度要求 → 怎么查 IV 长度 → 8.2 新增的密钥长度函数 → AEAD 模式还多出哪些参数 → 完整可运行示例的顺序展开。文中所有函数名、参数顺序都以 PHP 官方手册为准算法相关的具体数值不在正文里写死而是让示例程序直接打印出来避免因为 OpenSSL 版本差异给出错误结论。一、IV 是什么为什么长度必须精确分组密码block cipher有个特点相同的明文块 相同的密钥一定得到相同的密文块。这带来两个问题一是攻击者能看出这两段数据相同二是同样的开头会被识别出来。IV 的作用就是在每次加密前给算法注入一个随机的起始状态让相同的明文每次都产生不同的密文。对称加密的 IV 有三条铁律长度由算法固定比如 AES 的分组是 16 字节但不同工作模式要求的 IV 长度不一样GCM 模式通常是 12 字节。写死一个值换算法就崩。必须随机不能用一个固定常量str_repeat(0, 16)这种写法等于自欺欺人。可以公开但不能复用。IV 通常和密文拼在一起存储或传输它不需要保密但同一个密钥下重复使用同一个 IV在部分模式下会直接泄漏明文信息。只要其中一条没做到代码能跑但加密强度已经打了折扣而且不会报错——这是最危险的一类问题。二、用 openssl_cipher_iv_length() 查 IV 长度函数签名PHP 5.3.3 起可用openssl_cipher_iv_length(string $cipher): int|false传入算法名返回 IV 长度字节算法不存在时返回false。算法名必须来自openssl_get_cipher_methods()自己拼一个字符串是不会生效的。?php declare(strict_types1); var_dump(openssl_cipher_iv_length(aes-256-cbc)); // int(16) 左右的实际值 var_dump(openssl_cipher_iv_length(不存在的算法)); // bool(false) 或抛错注意最后一行未知算法名的确切行为在不同小版本上不完全一致可能返回false也可能抛出异常所以生产代码不要依赖对错误输入的处理而应该先校验算法名?php declare(strict_types1); function isSupportedCipher(string $cipher): bool { return in_array($cipher, openssl_get_cipher_methods(), true); }openssl_get_cipher_methods(bool $aliases false)返回当前 OpenSSL 支持的全部算法名数组。它的内容随 OpenSSL 版本变化所以在能用哪些算法这件事上唯一的可靠来源是运行时调用它而不是文档里的固定清单。三、PHP 8.2 真正新增的openssl_cipher_key_length()这才是 8.2 带来的东西。在此之前想知道某个算法需要多长的密钥只能靠查表或者数自己hash()出来的字节数PHP 8.2 加了配套函数openssl_cipher_key_length(string $cipher): int|false它和openssl_cipher_iv_length()是一对一个告诉你 IV 多长一个告诉你密钥多长。?php declare(strict_types1); if (function_exists(openssl_cipher_key_length)) { echo key length , openssl_cipher_key_length(aes-256-cbc), PHP_EOL; } else { echo 当前 PHP 版本低于 8.2没有 openssl_cipher_key_length()\n; }写库或组件时建议用function_exists()兜底这样同一份代码在 8.1 和 8.2 上都能跑在低版本上退回到用hash(sha256, $password, true)得到 32 字节密钥的写法。四、AEAD 模式除了 IV 还有 tag 和 aadGCM、CCM 这类 AEADAuthenticated Encryption with Associated Data带认证的加密模式比 CBC 多两个概念认证标签authentication tag简称 tag加密时由openssl_encrypt()通过引用参数输出解密时必须原样传回。不校验 tag等于没有认证密文可以被随意篡改而不被发现。附加认证数据additional authenticated dataAAD参与完整性校验但不被加密的数据比如请求 ID、协议头。可以为空。openssl_encrypt()从 PHP 7.1 起支持这两者openssl_encrypt( string $data, string $cipher_algo, string $passphrase, int $options 0, string $iv , $tag null, string $aad , int $tag_length 16 ): string|falseopenssl_decrypt()的签名与之对应。注意$tag是引用参数加密时由函数写入解密时需要传入。实战完整可运行示例下面这段程序不依赖任何扩展之外的东西只要有 openssl 扩展就能跑。它会打印三个算法的 IV 长度与密钥长度然后完整走一遍 AES-256-GCM 的加密与解密并演示篡改密文导致校验失败。?php declare(strict_types1); if (!extension_loaded(openssl)) { exit(需要 openssl 扩展\n); } echo PHP 版本: , PHP_VERSION, PHP_EOL; echo openssl 版本: , OPENSSL_VERSION_TEXT, PHP_EOL, PHP_EOL; // ---------- 1. 先看当前环境支持哪些算法 ---------- $all openssl_get_cipher_methods(); echo 支持的算法数量: , count($all), PHP_EOL; $candidates [aes-128-cbc, aes-256-cbc, aes-256-gcm, aes-128-gcm, chacha20-poly1305]; foreach ($candidates as $cipher) { if (!in_array($cipher, $all, true)) { printf(%-20s 当前环境不支持跳过\n, $cipher); continue; } $ivLen openssl_cipher_iv_length($cipher); $keyLen function_exists(openssl_cipher_key_length) ? openssl_cipher_key_length($cipher) : null; printf( %-20s IV%-3s Key%-4s\n, $cipher, var_export($ivLen, true), $keyLen null ? n/a(8.2) : var_export($keyLen, true) ); } echo PHP_EOL; // ---------- 2. AES-256-GCM 加解密 ---------- function encryptGcm(string $plaintext, string $key, string $aad ): array { $cipher aes-256-gcm; $ivLen openssl_cipher_iv_length($cipher); if ($ivLen false) { throw new RuntimeException(无法获取 IV 长度); } // IV 每次都必须重新随机生成绝不能复用 $iv random_bytes($ivLen); $tag ; $ciphertext openssl_encrypt( $plaintext, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, $aad, 16 ); if ($ciphertext false) { throw new RuntimeException(加密失败); } return [iv $iv, tag $tag, ct $ciphertext]; } function decryptGcm(array $payload, string $key, string $aad ): string { $plain openssl_decrypt( $payload[ct], aes-256-gcm, $key, OPENSSL_RAW_DATA, $payload[iv], $payload[tag], $aad ); if ($plain false) { throw new RuntimeException(解密或认证失败密文/标签/AAD 被篡改); } return $plain; } // 密钥长度应当与算法匹配AES-256 需要 32 字节 $key random_bytes(32); $aad order-id10086; $payload encryptGcm(这是一段需要保护的明文 secret, $key, $aad); echo IV (hex) : , bin2hex($payload[iv]), PHP_EOL; echo TAG(hex) : , bin2hex($payload[tag]), PHP_EOL; echo 密文(hex): , bin2hex($payload[ct]), PHP_EOL; echo 解密结果 : , decryptGcm($payload, $key, $aad), PHP_EOL; // 篡改一个字节认证应当失败 $tampered $payload; $tampered[ct][0] chr(ord($tampered[ct][0]) ^ 0x01); try { decryptGcm($tampered, $key, $aad); echo 篡改后仍然解密成功 —— 说明没有做认证必须排查\n; } catch (RuntimeException $e) { echo 篡改检测 : , $e-getMessage(), PHP_EOL; } // AAD 不一致同样应当失败 try { decryptGcm($payload, $key, order-id99999); echo AAD 不一致却解密成功 —— 必须排查\n; } catch (RuntimeException $e) { echo AAD 校验 : , $e-getMessage(), PHP_EOL; }输出类似IV、密文每次都不同这是预期行为PHP 版本: 8.2.20 openssl 版本: OpenSSL 3.0.x ... 支持的算法数量: 60 aes-128-cbc IV16 Key16 aes-256-cbc IV16 Key32 aes-256-gcm IV12 Key32 aes-128-gcm IV12 Key16 chacha20-poly1305 IV12 Key32 IV (hex) : 3f9c... TAG(hex) : 9a1b... 密文(hex): 7c04... 解密结果 : 这是一段需要保护的明文 secret 篡改检测 : 解密或认证失败密文/标签/AAD 被篡改 AAD 校验 : 解密或认证失败密文/标签/AAD 被篡改具体的数字以你自己机器上的输出为准——不同 OpenSSL 版本支持的算法集合可能略有差异这正是程序里先调openssl_get_cipher_methods()再判断的原因。常见坑点❌ 硬编码 IV 长度$iv str_repeat(0, 16);换成 GCM 算法后长度不对PHP 只会给一条警告并把 IV 截断密文依然能生成解密时才发现不对。✅永远用openssl_cipher_iv_length($cipher)取长度用random_bytes()生成。❌ IV 复用把 IV 定义为常量或从用户 ID 派生。相同明文会得到相同密文攻击者据此可以比对出信息。✅每次加密都random_bytes($ivLen)IV 不需要保密随密文一起存数据库里单独一列或拼在密文前部。❌ 用短口令当密钥openssl_encrypt($data, aes-256-cbc, 123456, ...)。OpenSSL 会用零字节补齐到 32 字节实际密钥强度远低于 AES-256 应有的水平。✅用random_bytes(openssl_cipher_key_length($cipher))PHP 8.2或hash(sha256, $password, true)派生定长密钥。❌ 加密时拿到$tag却不存GCM 加密后把$tag丢掉解密时只能传空字符串认证形同虚设被篡改的数据照样能解密成功。✅把 IV、tag、密文三者一起持久化解密时原样传回。❌ 用openssl_random_pseudo_bytes()生成 IV这个函数在新代码里已经不是首选它返回的是旧接口且历史上在部分平台上不可靠。✅用random_bytes()PHP 7.0CSPRNG。❌ 忘了OPENSSL_RAW_DATA默认返回 base64 编码的字符串再自己base64_encode()一次就是双重编码反向解密时多一次base64_decode()长度对不上。✅二进制存储场景统一加OPENSSL_RAW_DATA需要可读文本时再单独base64_encode()一次并且全链路保持一致。❌ 只存密文不存算法名上线时用aes-256-cbc后来想换成aes-256-gcm旧数据完全无法解密因为不知道当初用的哪个算法、IV 多长、tag 放哪。✅持久化结构里带上算法标识与版本号例如按版本:算法:IV:tag:密文的顺序拼接为将来算法迁移留出空间。❌ 只在加密侧校验算法是否存在加密时检查了in_array($cipher, openssl_get_cipher_methods())解密侧没检查换机器后算法可用性不同解密静默返回false被当成密码错误。✅加解密两侧都做同样的算法可用性判断失败时明确区分数据错误和环境不支持。总结需求用哪个 API引入版本查算法支持的 IV 长度openssl_cipher_iv_length()PHP 5.3.3不是 8.2查算法的密钥长度openssl_cipher_key_length()PHP 8.2列出当前环境支持的算法openssl_get_cipher_methods()PHP 5.3 起生成随机 IV / 密钥random_bytes()PHP 7.0AEAD 的 tag 与 AADopenssl_encrypt()的$tag、$aad参数PHP 7.1标题把openssl_cipher_iv_length()说成 PHP 8.2 新增是不准确的它是 5.3.3 的老函数8.2 新增的是名字只差一个词的openssl_cipher_key_length()。记住这对函数的用途划分——IV 长度和密钥长度各查各的再配上random_bytes()和 AEAD 的 tag 校验加解密代码就不会再出现能跑但不对的情况。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →