跳到内容

文件签名(魔术字节)一览

文件开头若干字节中包含的签名(魔术字节)一览,可用于判断文件类型与实现校验。

什么是魔术字节

多数文件格式在开头都有固定的字节序列(签名),称为魔术字节或文件签名。操作系统和应用程序据此判断文件类型。扩展名可以轻易伪造,而魔术字节属于二进制层面的标识,因此校验可靠性更高。

Linux 的 file 命令和 PHP 的 finfo 类都用这种方式判断 MIME 类型。

常见格式的魔术字节(前 N 字节) PNG JPEG GIF PDF ZIP WebP MP4 89 50 4E 47 .PNG FF D8 FF SOI marker 47 49 46 38 "GIF8" 25 50 44 46 "%PDF" 50 4B 03 04 "PK\x03\x04" 52 49 46 46 "RIFF" + WEBP at offset 8 66 74 79 70 "ftyp" at offset 4
图:常见格式的起始字节(魔术字节)

图像文件

格式扩展名魔术字节(十六进制)ASCII 表示偏移量
PNG.png89 50 4E 47 0D 0A 1A 0A‰PNG\r\n\x1a\n0
JPEG.jpg/.jpegFF D8 FF0
GIF87a.gif47 49 46 38 37 61GIF87a0
GIF89a.gif47 49 46 38 39 61GIF89a0
WebP.webp52 49 46 46 ?? ?? ?? ?? 57 45 42 50RIFF????WEBP0
BMP.bmp42 4DBM0
TIFF(小端序).tiff/.tif49 49 2A 00II*\00
TIFF(大端序).tiff/.tif4D 4D 00 2AMM\0*0
AVIF / HEIF.avif/.heif66 74 79 70ftyp4
ICO.ico00 00 01 000
SVG.svg<?xml<svg文本0(无 BOM)

文档与压缩包

格式扩展名魔术字节(十六进制)备注
PDF.pdf25 50 44 46 2D%PDF-
ZIP.zip50 4B 03 04PK\x03\x04
ZIP(空文件).zip50 4B 05 06
GZIP.gz1F 8B
7-Zip.7z37 7A BC AF 27 1C7z\xBC\xAF'\x1C
RAR4.rar52 61 72 21 1A 07 00Rar!\x1A\x07\x00
RAR5.rar52 61 72 21 1A 07 01 00
TAR.tar75 73 74 61 72ustar(偏移 257)
DOCX / XLSX / PPTX.docx 等50 4B 03 04基于 ZIP(靠扩展名区分)
XLS(旧格式).xlsD0 CF 11 E0 A1 B1 1A E1OLE2 容器
DOC(旧格式).docD0 CF 11 E0 A1 B1 1A E1OLE2 容器

文本与数据格式

格式扩展名魔术字节 / BOM备注
带 BOM 的 UTF-8.txt / .csv 等EF BB BFBOM(Byte Order Mark)
UTF-16 LE BOM.txt 等FF FE小端序
UTF-16 BE BOM.txt 等FE FF大端序
UTF-32 LE BOM.txt 等FF FE 00 00
JSON(常规).json7B5B{[
CSV(常规).csv文本(无 BOM)无签名
XML.xml3C 3F 78 6D 6C<?xml

音频与视频

格式扩展名魔术字节(十六进制)备注
MP3.mp3FF FB / FF F3 / FF F2或 ID3 标签:49 44 33
MP4 / M4A / M4V.mp4 等66 74 79 70ftyp(偏移 4)
WAV.wav52 49 46 46 ?? ?? ?? ?? 57 41 56 45RIFF????WAVE
OGG.ogg/.ogv4F 67 67 53OggS
FLAC.flac66 4C 61 43fLaC
AVI.avi52 49 46 46 ?? ?? ?? ?? 41 56 49 20RIFF????AVI
MKV / WebM.mkv/.webm1A 45 DF A3EBML 头

可执行文件与其他

格式扩展名魔术字节(十六进制)备注
ELF(Linux 可执行文件)无 / .elf7F 45 4C 46\x7FELF
PE(Windows 可执行文件).exe/.dll4D 5AMZ
Mach-O(macOS 可执行文件)CF FA ED FE64bit
PHP 脚本.php3C 3F 70 68 70<?php
SQLite.sqlite/.db53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00SQLite format 3\0
ISO.iso43 44 30 30 31偏移 32769:CD001

PHP 中的魔术字节校验示例

 ['offset' => 0, 'bytes' => "\x89PNG\r\n\x1a\n", 'length' => 8],
        'jpg'  => ['offset' => 0, 'bytes' => "\xFF\xD8\xFF",       'length' => 3],
        'gif'  => ['offset' => 0, 'bytes' => "GIF8",               'length' => 4],
        'pdf'  => ['offset' => 0, 'bytes' => "%PDF-",              'length' => 5],
        'zip'  => ['offset' => 0, 'bytes' => "PK\x03\x04",         'length' => 4],
        'exe'  => ['offset' => 0, 'bytes' => "MZ",                 'length' => 2],
    ];

    if (!isset($signatures[$expectedType])) {
        throw new \InvalidArgumentException("未知的文件类型: $expectedType");
    }

    $sig = $signatures[$expectedType];
    $handle = fopen($filePath, 'rb');
    if ($sig['offset'] > 0) fseek($handle, $sig['offset']);
    $header = fread($handle, $sig['length']);
    fclose($handle);

    if (substr($header, 0, $sig['length']) !== $sig['bytes']) {
        throw new \RuntimeException("魔数字节不匹配(期望: $expectedType)");
    }
}

// 使用示例
validateMagicBytes($_FILES['upload']['tmp_name'], 'png');
echo "这是合法的 PNG 文件";

相关资源

❓ 常见问题

为什么只检查扩展名不够?
扩展名只是文件名的一部分,攻击者可以随意更改:把 shell.php 改名为 shell.jpg,扩展名检查就通过了。魔数查看的是数据的真实开头,改名骗不过它。但它对伪造前几个字节的多态文件仍然薄弱,因此请把扩展名、MIME 类型与魔数三者交叉验证。
识别 PNG 与 JPEG 需要读取多少字节?
PNG 需要 8 字节(89 50 4E 47 0D 0A 1A 0A),JPEG 只需前 3 字节(FF D8 FF)。实现上读取约 12 字节即可一次覆盖主要格式,ZIP(50 4B 03 04)与 PDF(25 50 44 46)也在这一范围内。无需把整个文件读入内存。
Office 文档和 APK 被判定为 ZIP。
这个判定是正确的。docx、xlsx、pptx、APK、JAR、EPUB 都是 ZIP 容器,开头均为 50 4B 03 04。要区分它们必须解开压缩包查看内部:docx 看 [Content_Types].xml,APK 看 AndroidManifest.xml。仅凭魔数无法做到放行 docx 而拦截 APK。