魔数与文件头识别:一眼看穿文件类型

本节目标

魔数:很多文件格式在开头放几个固定字节作为"身份证"。改后缀名骗不了它。

// 运行环境: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 按内容判文本)。魔数就是内容的指纹。

名词解释

课后练习

  1. 为什么"改后缀"骗不过魔数识别?
    • 答案:后缀只是文件名的一部分,文件内部前几字节(魔数)没变;按内容读取头部即可识破伪装。
  2. 如果两种格式魔数前几字节恰好相同怎么办?
    • 答案:需读更长的特征或后续结构字段(如版本号、特定偏移处的标记)来区分;魔数只是第一道快速筛选。

总结

魔数识别是"相信内容而非标签"这一工程哲学的最小体现。它教给我们一个朴素却常被忽视的道理:名字会骗人,结构不会。当你把 a.png 改成 a.jpg,双击可能在某些程序里出错,但任何按魔数判断的工具都一眼看穿。这种"以内容为准"的思路贯穿整个计算机世界——浏览器 MIME sniff、杀毒软件特征码、git 文本判定,底层都是同一逻辑。我特别想强调:在处理用户上传文件时,永远别只信 file.name 的后缀,要读真实头部判断类型,否则就给恶意 .exe 改名 .png 上传留了后门。手写这个 detect 函数,你得到的不仅是个小工具,更是"不轻信表面、查证据"的工程习惯。