ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PHP hex2bin()函数用法讲解

PHP hex2bin()函数用法讲解 前言hex2bin()有一个非常容易误会的名字。看到「hex to bin」很多人的第一反应是「把十六进制数字转成二进制数字」比如把FF变成11111111。这是错的而且官方手册在hex2bin()页面里专门用一条 Caution警告把这件事写了出来这个函数不会把十六进制数转换成二进制数那个需求要用base_convert()来做。hex2bin()真正做的事是把一串十六进制文本按「每两个字符一组」解包成原始字节byte。48656c6c6f解出来不是数字而是 5 个字节恰好组成字符串Hello。它是bin2hex()的逆操作两个函数合起来完成「二进制数据 ↔ 可打印文本」的互转。为什么要做这种转换因为二进制数据加密后的密文、随机令牌、图片片段、压缩结果直接存进文本字段、日志或 URL 里常常会出问题里面可能出现 NUL 字节、换行、引号这些「会破坏外层格式」的字符。十六进制是它们的安全表示方式——只用 0-9 和 a-f 两种字符集长度翻倍但任何文本环境都能安全承载。另外要强调一句hex2bin()/bin2hex()是编码与解码不是加密与解密。十六进制文本不含任何保密性它只是换了一种写法。一、签名与错误处理官方手册给出的签名是hex2bin(string $string): string|false参数是要解码的十六进制字符串返回值是解出的原始字节串失败时返回false返回类型里明确带着|false。函数自PHP 5.4.0起可用。手册的「错误异常」一栏写得很明确输入字符串长度为奇数或者含有非十六进制的字符时会产生一个 E_WARNING 级别的错误并返回false。注意这里的措辞——是警告加返回值不是抛异常。这一点和很多现代 PHP 函数不同容易被记错所以拿到返回值必须显式判断?php // 适用于 PHP 8.0$result hex2bin($someHex);if ($result false) {// 必须用 判断解出的字节串可能是 之外的任何内容// 而且 false 松散比较时会与 相等用 会漏判echo 解码失败, PHP_EOL;}用判断在这里尤其危险空字符串与false松散相等用会把「解码成功但结果为空」和「解码失败」混为一谈。几组典型的输入输出输入结果说明48656c6c6fHello正常的偶数长度十六进制48656C6C6FHello大写同样接受空串长度为 0是偶数返回空串abcfalseE_WARNING奇数长度zzfalseE_WARNING含非十六进制字符0x41falseE_WARNING不接受0x前缀且长度是偶数也照样失败48 65falseE_WARNING不接受空格等分隔符大小写都能接受这一点可以从 PHP 源码里确认解码时对每个字符做了一次大小写归一把字节与0x20取反后比较所以a和A等价。而空格、换行、0x前缀这些「看起来人畜无害」的字符都会被判定为非法直接返回false。如果数据来源不可控先做清洗再解码?php // 适用于 PHP 8.0function safe_hex_decode(string $hex): string|false{$hex str_replace([ , \t, \r, \n], , $hex); // 去掉常见空白$hex strtolower($hex);if (str_starts_with($hex, 0x)) {$hex substr($hex, 2); // 去掉 0x 前缀}if ($hex || strlen($hex) % 2 ! 0) {return false; // 长度必须是偶数}if (!ctype_xdigit($hex)) {return false; // 只允许 0-9 a-f}return hex2bin($hex); // 前面都校验过了这里一定成功}str_starts_with()需要PHP 8.0 及以上在 7.x 上可以改用strpos($hex, 0x) 0必须用第 0 位命中时strpos()返回整数0。ctype_xdigit()属于 ctype 扩展用于判断字符串是否全部为十六进制字符。二、它到底做了什么按字节解包bin2hex()的规则是「逐字节转换高四位在前」把每个字节拆成两个十六进制字符hex2bin()就是反过来每两个字符合成一个字节。用一个例子走一遍原字符串 : H e l l oUTF-8 字节 : 0x48 0x65 0x6C 0x6C 0x6Fbin2hex() : 48 65 6c 6c 6f - 48656c6c6fhex2bin() : 48-0x48 65-0x65 ... - Hello?php // 适用于 PHP 8.0$text Hello world!;$hex bin2hex($text);var_dump($hex); // string(24) 48656c6c6f20776f726c6421var_dump(hex2bin($hex)); // string(12) Hello world!// 往返一致var_dump(hex2bin(bin2hex($text)) $text); // bool(true)这段往返在任意字节序列上都成立包括中文UTF-8 多字节、二进制文件内容、含 NUL 的数据?php // 适用于 PHP 8.0$binary random_bytes(16); // 16 个随机字节可能含 NUL$hex bin2hex($binary);echo $hex, PHP_EOL; // 32 个十六进制字符var_dump(strlen($hex)); // int(32)长度正好翻倍var_dump(hex2bin($hex) $binary); // bool(true)完整还原这里体现了「长度翻倍」这个固定关系bin2hex()的输出长度恒等于输入的 2 倍hex2bin()的输出长度恒等于输入的 1/2因此输入长度必须是偶数。hex2bin()是二进制安全的它按长度处理不会因为内容里出现 NUL 字节而截断返回的字符串里也可能包含 NUL。这意味着把解码结果写进数据库时字段类型必须是二进制安全的例如VARBINARY、BLOB写进文本字段VARCHAR 非二进制排序规则容易被截断或被转义规则改写。三、和 bin2hex()、base_convert()、hexdec() 的分工这四个函数名字都带 hex 或 bin功能完全不同混用会直接得到错误结果需求用哪个函数输入与输出的关系字节串 → 十六进制文本bin2hex(string $string): string每个字节变两个小写十六进制字符长度 ×2十六进制文本 → 字节串hex2bin(string $string): string\false十六进制文本 → 十进制数值hexdec(string $hex_string): int\float数值进制转换base_convert(string $num, int $from_base, int $to_base): string任意 236 进制互转最典型的混淆是前两个和第三个?php // 适用于 PHP 8.0$bytes hex2bin(ff); // 一个字节其值为 0xFFvar_dump(strlen($bytes)); // int(1)长度为 1var_dump(bin2hex($bytes)); // string(2) ff可以原样转回去var_dump(hexdec(ff)); // int(255)这是一个整数hex2bin(ff)返回的是一个长度为 1 的字符串它的那一个字节值是 0xFF这个字节本身不是一个合法的 UTF-8 字符直接回显到页面上通常显示不出来所以上面用strlen()和bin2hex()来观察它而不是直接打印。hexdec(ff)返回的则是整数 255可以直接参与算术。两者名字相似结果毫无关系。至于手册 Caution 里提到的base_convert()用法是这样?php // 适用于 PHP 8.0echo base_convert(ff, 16, 2), PHP_EOL; // 11111111echo base_convert(11111111, 2, 16), PHP_EOL; // ff这里要注意base_convert()手册里的一条 Warning大数会丢精度因为内部用浮点做中转。位数较多的数值不适合用它那种场景应该用base_convert()之外的方案例如gmp_*或bcmath系列。四、实战两个真会用到的场景场景一把随机令牌变成可安全传输的文本生成令牌时用random_bytes()密码学安全的随机源PHP 7.0再用bin2hex()转成十六进制文本就能直接放进 URL、Cookie 或表单里?php // 适用于 PHP 7.0$token bin2hex(random_bytes(16)); // 16 字节 - 32 个十六进制字符echo $token, PHP_EOL;// 校验时把用户提交的值解回二进制再比较// 实践中直接比较十六进制文本也安全因为两边格式相同$decoded hex2bin($token);var_dump(strlen((string) $decoded)); // int(16)关于这段代码有两点要说明白十六进制文本并不隐藏任何信息。bin2hex(random_bytes(16))的安全性来自random_bytes()的随机性不是来自「看起来像乱码」。不要把它当成「加密」。存令牌到数据库时直接比较字符串更省事不必解码后再比。要用哈希保存令牌例如只存hash(sha256, $token)则属于令牌管理的设计问题和编码无关。如果对体积敏感base64_encode()比十六进制更紧凑同样 16 字节Base64 是 24 个字符十六进制是 32 个字符代价是多出、/、这几个在 URL 里需要额外编码的字符此时可用base64url变体或rtrim(strtr(base64_encode($x), /, -_), )这类改写。场景二在日志和配置里保存二进制数据?php // 适用于 PHP 8.0$blob random_bytes(8); // 假设这是某段二进制数据$record bin2hex($blob); // 存进日志/配置文件file_put_contents(__DIR__ . /token.log, $record . PHP_EOL, FILE_APPEND | LOCK_EX);// 读回来还原$line trim((string) file_get_contents(__DIR__ . /token.log));$restored hex2bin($line);var_dump($restored $blob);示例里的trim()很关键从文件读到的内容通常带着行尾的换行符而换行符不是合法的十六进制字符直接丢给hex2bin()会告警并返回false。FILE_APPEND | LOCK_EX是file_put_contents()的追加与加锁标志避免多进程同时写时相互覆盖。常见坑点❌ 以为hex2bin(FF)会得到11111111✅ 它得到的是一个字节值 0xFF不是二进制数字的文本。要做「十六进制数 → 二进制数字文本」的转换用base_convert(FF, 16, 2)。❌ 不判断返回值直接把结果当字符串用✅ 返回类型是string|false。失败时奇数长度或含非法字符只产生E_WARNING并返回false不抛异常。必须用 false判断。❌ 用if (hex2bin($hex) false)判断失败✅ 空字符串与false松散相等会误判。统一用。❌ 把带空格、换行或0x前缀的字符串直接丢给hex2bin()✅ 这些都是非法字符会返回false。先清洗去掉空白、去掉前缀、统一小写再用ctype_xdigit()校验。❌ 传入奇数长度的十六进制串✅ 奇数长度必然失败因为两个字符才构成一个字节。拼接输入时尤其容易出现这种错误务必先判断strlen($hex) % 2 0。❌ 把hex2bin()当成加密或混淆手段✅ 十六进制只是编码。任何拿到文本的人都能一行代码解回来。要保密必须用加密如sodium_crypto_secretbox()要防篡改用hash_hmac()。❌ 把解码出的二进制数据写进普通文本字段或直接输出到页面✅ 结果可能含 NUL 和非法 UTF-8 字节写库要用二进制字段类型输出到 HTML 前必须转义输出到日志要再做一次编码。❌ 用base_convert()处理位数较多的十六进制数值✅ 手册明确提示它在大数上会因内部浮点表示而丢失精度长数值应改用gmp_*或bcmath系列函数。总结项目说明签名hex2bin(string $string): string\作用把十六进制文本每两个字符解包成一个原始字节可用版本PHP 5.4.0 起输入要求长度为偶数只允许0-9、a-f、A-F不接受空格与0x前缀失败行为产生E_WARNING级别的错误并返回false不抛异常长度关系输出长度恒为输入长度的一半bin2hex()则恰好翻倍逆函数bin2hex()是否加密不是只是编码常见误用把它当成「十六进制数转二进制数」那个需求属于base_convert()hex2bin()的用法就这么几行真正的风险全在「想当然」上想当然以为它在做进制转换想当然以为失败会抛异常想当然以为带空格和前缀的输入也能解。记住它是bin2hex()的逆操作、返回类型带|false、输入必须是「偶数长度 纯十六进制字符」这三条再配合一个先清洗后校验的包装函数这个函数就再也不会给你添麻烦了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进