☰
什么是文件「魔数」?从is-png-cj看懂PNG文件签名识别原理入门
2026/9/25 6:00:35 网站建设 项目流程

什么是文件「魔数」?从is-png-cj看懂PNG文件签名识别原理入门

【免费下载链接】is-png-cj一个判断图片格式的库,根据图片的文件数据,判断图片是否为png格式项目地址: https://gitcode.com/Cangjie-TPC/is-png-cj

is-png-cj是一个用仓颉语言(Cangjie)编写的轻量级文件识别库,它通过读取文件头部的 8 个字节——也就是「魔数」(Magic Number)——来判断一个文件是否真的是PNG 图片格式。本文带你从 0 理解什么是文件魔数、PNG 签名长什么样,以及 is-png-cj 如何用极简代码实现可靠的图片格式识别(核心文件仅 26 行),适合刚接触文件处理的新手阅读。

一句话先记住:魔数 = 写在文件开头、用来"自报家门"的一串固定字节。

为什么不能只看文件后缀?🤔

我们平时靠.png、.jpg这类后缀区分图片,但后缀其实是"可以被随意改掉的标签":

  • 把一个可执行文件改名为可爱猫咪.png,只校验后缀的程序照样会放行;
  • 上传接口若只看后缀,就可能被"伪装成图片"的文件绕过;
  • 文件被截断或重命名后,后缀与真实内容可能完全对不上。

真正可靠的判断方式是打开文件,看它开头的数据。这正是魔数登场的地方。

对比项看文件后缀看文件头部魔数
能否被重命名欺骗❌ 能✅ 不能
需要读取多少数据0 字节(只看文件名)前几个字节
判断依据命名习惯格式规范定义的固定签名

什么是魔数:文件自带的"身份证"

魔数(Magic Number),也叫文件签名(File Signature / Magic Bytes),是每种文件格式在规范中约定的、出现在文件起始位置的固定字节序列。它就像身份证号——文件一"出生"就带着,改名字也改不掉。

先睹为快,几个常见格式的魔数(十六进制表示):

格式魔数(十六进制)可读部分
PNG89 50 4E 47 0D 0A 1A 0A..PNG\r\n..
JPEGFF D8 FF—
GIF47 49 46GIF
ZIP / JAR50 4B 03 04PK..

💡 彩蛋:ZIP 魔数里的50 4B正好是英文 "PK"——压缩软件创始人 Phil Katz 名字的首字母。

PNG 的 8 字节签名长什么样?

PNG 规范在文件最开始定义了 8 字节的签名,也就是 is-png-cj 用来比对的那份魔数模板,定义在 src/is_png.cj:

字节位置十六进制对应 ASCII
189(非打印字符)
250P
34EN
447G
50D回车符\r
60A换行符\n
71A(非打印字符)
80A换行符\n

注意中间 3 个字节50 4E 47拼出来正好是"PNG"三个字母;签名里又特意混入回车换行(0D 0A 1A 0A),是为了在早期的 ASCII 文本传输场景中降低文件被破坏的概率。这种"可读 + 不可读"混合的签名设计,正是魔数的经典套路。

从源码看懂 is-png-cj 的识别原理 🔍

is-png-cj 的核心思路朴素到可以一眼看穿:只读文件开头 8 个字节,与魔数逐位比对,全部一致才是 PNG。

1. 用"规则模板"描述魔数

在 src/util.cj 中,库定义了Rule规则类型和Pattern(规则数组):

  • ByteMatch(b):这一位必须是指定字节b;
  • AnyByte:这一位任意字节都可以。

这样设计的好处是模板天然可扩展——将来遇到"某字节位允许变化"的格式时,直接把对应位改成AnyByte即可。当前 PNG 魔数 8 位全部采用ByteMatch精确匹配。

2. 逐字节比对,只读 8 个字节

真正的比对逻辑封装在startsWith函数里,提供两个版本,分别对应两种输入:

  • 输入流版本 src/util.cj:从InputStream中按需读取头部字节;
  • 字节序列版本 src/util.cj:直接遍历Iterable<Byte>。

它从第一个字节开始逐个与模板比对:一旦发现不一致立即返回false,8 位全部通过才返回true。这意味着:

  • ✅ 最多只读 8 个字节,哪怕是一个几百 MB 的大图,判断成本几乎为零;
  • ✅ 空文件、被截断的文件(不足 8 字节)都会被正确判为"不是 PNG"。

3. 两个开箱即用的接口

对外只暴露一个函数isPng,通过参数类型自动区分两种用法,见 src/is_png.cj:

  • isPng(inputStream: InputStream)—— 判断文件流;
  • isPng(iter: Iterable<Byte>)—— 判断内存中的字节序列(比如从网络请求拿到的二进制数据)。

3 步上手:判断一个文件是不是 PNG 🚀

第 1 步:添加依赖。在 cjpm.toml 的[dependencies]中添加is_png_cj依赖即可(当前版本 0.2.0,完整写法参考 README.md 的"引入依赖"一节,版本演进见 CHANGELOG.md)。

第 2 步:调用isPng。官方示例 examples/is_png/src/main.cj 演示了最简用法,核心就几行:

import is_png import std.io.* import std.fs.* main() { let f = File("test.png", Open(true, false)) println(if (isPng(f)) { "是 PNG 格式" } else { "不是 PNG 格式" }) f.close() }

第 3 步:看结果。只要test.png开头的 8 个字节与 PNG 签名一致,程序就会输出:

是 PNG 格式

一行判断,替代了"看后缀、用软件打开"的土办法。

单元测试:100% 覆盖是怎么做到的

is-png-cj 的测试覆盖率是 100%。测试文件 src/is_png_test.cj 把正例和反例都钉死了:

测试场景输入期望结果
合法 PNG 签名完整 8 字节魔数true
真实 PNG 图片1×1 像素 PNG(Base64 解码)true
空输入空数组 / 空流false
签名被截断只有前 4 字节89 50 4E 47false
末字节被篡改第 8 字节从0A改成00false

哪怕只有1 个字节不对,is-png-cj 也会果断返回false——这就是魔数识别"要么全对,要么不认"的严谨性。

魔数在开发中的实际应用场景 🌍

理解了原理,你会发现魔数判断在工程中无处不在:

  • 上传接口安全校验:先验魔数再落盘,防止伪装成图片的恶意文件混入服务器;
  • 未知文件"验明正身":拿到一个没有后缀的文件,读前几个字节对照签名表,就能认出它的真实格式;
  • 文件恢复:文件丢失后缀后,扫描数据块开头的魔数,可以把散落的图片"捡"回来。

is-png-cj 正是这一类工具的最小可用实现:只聚焦 PNG 一种格式,把"读 8 字节 + 比对"这件事做到极致简单。

常见问题 FAQ

Q1:魔数判断为true,文件就一定是一张完好的 PNG 图片吗?不完全是。魔数只能证明"文件声称自己是 PNG",即头部签名合法;后续数据块是否完整、能否正常解码,还要交给图像解码器验证。但作为"第一步过滤",它已经排除了绝大多数非 PNG 文件。

Q2:用流判断会不会把整个文件读完?不会。isPng最多只读取开头的 8 个字节就返回。但注意它确实会推进流的读取位置,若之后还要继续读原文件,需要先处理流位置——接口注释里也专门提醒了这一点。

Q3:能不能用它判断 JPEG、GIF?目前 is-png-cj 只内置了 PNG 魔数模板。不过 src/util.cj 里的Pattern+startsWith是一套通用的"魔数匹配引擎",理论上换一份签名模板就能扩展到其他格式。

写在最后

回到开头的问题:文件魔数,就是写在文件最前面、用来"自报家门"的固定字节签名。

is-png-cj 用极简的公开接口展示了这套机制的完整闭环:定义 8 字节模板 → 逐字节比对 → 输出true/false,并配上 100% 覆盖的单元测试。下次再遇到"改后缀就能骗过程序"的场景,记得先看看文件开头的几个字节——那才是它真正的"身份证"。

关键文件作用
src/is_png.cj核心入口:PNG 魔数模板与isPng接口
src/util.cj通用魔数匹配引擎(规则、模板、比对)
src/is_png_test.cj单元测试(正例/反例全覆盖)
examples/is_png/src/main.cj可运行的功能示例
README.md项目介绍与完整使用说明

【免费下载链接】is-png-cj一个判断图片格式的库,根据图片的文件数据,判断图片是否为png格式项目地址: https://gitcode.com/Cangjie-TPC/is-png-cj

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询