魔数与文件头识别:一眼看穿文件类型
本节目标
- 理解"魔数(Magic Number)"如何标识文件类型
- 手写读取文件头、按魔数识别类型的工具
- 识破"改后缀伪装"的把戏
魔数:很多文件格式在开头放几个固定字节作为"身份证"。改后缀名骗不了它。
// 运行环境:Node.js 14+
// 保存为 aj-l6.js,执行:node aj-l6.js <某文件路径>(或见下方演示)
const fs = require('fs')
// 常见魔数(十六进制)
const SIGNS = [
{ type: 'PNG', sig: [0x89, 0x50, 0x4e, 0x47] },
{ type: 'JPEG', sig: [0xff, 0xd8, 0xff] },
{ type: 'GIF', sig: [0x47, 0x49, 0x46] },
{ type: 'PDF', sig: [0x25, 0x50, 0x44, 0x46] }, // %PDF
{ type: 'ZIP', sig: [0x50, 0x4b, 0x03, 0x04] }, // PK..
{ type: 'ELF', sig: [0x7f, 0x45, 0x4c, 0x46] }, // .ELF
]
function detect(filePath) {
const buf = fs.readFileSync(filePath).slice(0, 8) // 只读前 8 字节足够
for (const { type, sig } of SIGNS) {
if (sig.every((b, i) => buf[i] === b)) return type
}
return 'UNKNOWN'
}
// 调用示例:把任意文件拖进来识别(这里演示逻辑,不依赖真实文件)
console.log(detect.name, '— 传入路径即可,如 detect(\'a.png\')')
// 真伪对比:即便把 a.png 改名 a.jpg,detect 仍返回 'PNG'为什么可靠:操作系统/浏览器常"不看后缀看内容"(如浏览器按 MIME sniff、git 按内容判文本)。魔数就是内容的指纹。
名词解释
- 魔数(Magic Number):文件开头用于标识格式的若干固定字节。类比:身份证号,改名不改号。
- 文件头(File Header):文件最前面的元数据区,常含魔数、版本、长度等。解析任何二进制格式都先读头。
- 后缀名(Extension):文件名末尾的
.xxx,只是给人看的提示,可被随意篡改,不能信任。
课后练习
- 为什么"改后缀"骗不过魔数识别?
- 答案:后缀只是文件名的一部分,文件内部前几字节(魔数)没变;按内容读取头部即可识破伪装。
- 如果两种格式魔数前几字节恰好相同怎么办?
- 答案:需读更长的特征或后续结构字段(如版本号、特定偏移处的标记)来区分;魔数只是第一道快速筛选。
总结
魔数识别是"相信内容而非标签"这一工程哲学的最小体现。它教给我们一个朴素却常被忽视的道理:名字会骗人,结构不会。当你把 a.png 改成 a.jpg,双击可能在某些程序里出错,但任何按魔数判断的工具都一眼看穿。这种"以内容为准"的思路贯穿整个计算机世界——浏览器 MIME sniff、杀毒软件特征码、git 文本判定,底层都是同一逻辑。我特别想强调:在处理用户上传文件时,永远别只信 file.name 的后缀,要读真实头部判断类型,否则就给恶意 .exe 改名 .png 上传留了后门。手写这个 detect 函数,你得到的不仅是个小工具,更是"不轻信表面、查证据"的工程习惯。