☰
文件藏进PNG图片?FileImgSwap隐写原理与玩法全解析
2026/9/26 4:10:06 网站建设 项目流程

把文件塞进 PNG 图片里,这个工具我用了好久,今天把原理和玩法一次说透。

很多人第一次听说 FileImgSwap 的时候都觉得挺玄乎——文件还能塞进图片里?PNG 图不是打开后就只能看到一张图吗?实际上这属于隐写技术(Steganography)的一个典型应用场景。和加密不同,隐写的核心思路不是让文件"读不懂",而是让文件"看不见":把数据藏在一个看似无害的载体里,别人看到的只是一张再普通不过的图片。

这套思路在数据隐私保护、内部资料传递、数字水印、甚至是备份归档里都有实际价值。FileImgSwap 做的就是这件事:把任意格式的文件(压缩包、PDF、文本、表格、脚本)编码进一张 PNG 图片的像素数据里,之后再用工具把文件从图片里原样还原出来。整条链路完全本地完成,不依赖任何第三方服务。

这篇文章我会从 PNG 的底层结构讲起,说清楚为什么偏偏是 PNG 能干这事而不是 JPG,然后把 FileImgSwap 的常用操作、容量计算、踩坑记录一次讲透。不管你是冲"怎么用"来的,还是想搞清楚"为什么能这么干",这里都有答案。

1. 为什么偏偏是 PNG,而不是 JPG 或 WebP

这是 FileImgSwap 选择 PNG 做载体的根本原因,理解了这点,后面所有操作都顺理成章。

1.1 无损压缩是隐写的地基

先看 PNG 的压缩特性。PNG 用的是 DEFLATE 无损压缩算法,再加上一套滤波(Filtering)预处理。它压缩图片数据,但压缩过程是完全可以逆的——解压后拿到的像素数据和压缩前逐字节一致。

这意味着你可以随意修改 PNG 的像素字节,然后再次保存为 PNG,理论上图片还能被完整解析和显示,因为像素数据没有被"重新计算"过。

JPG 就不行。JPG 是离散余弦变换加量化表的有损压缩,每保存一次,它都会把像素块变换到频域、丢掉一部分人眼不敏感的高频细节。你嵌入文件时改动的那些像素值,保存时很可能被当成"画面噪声"直接抹掉。就算你运气好藏进去了,图片任何一次缩放、旋转、再保存,嵌入数据就废了。

所以一个很反直觉的结论是:越是"平庸"的载体图片,越适合用来藏文件。纯色背景、细节简单的图片,宽容度反而更高,嵌入后几乎看不出任何变化。

1.2 PNG 的数据块结构天然适合做手脚

再往下挖一层。PNG 文件不只是"一堆像素",它由一系列 chunk(数据块)组成:文件头 IHDR 记录宽高和位深,IDAT 存放经过压缩的像素流,IEND 是文件结束标记,中间还可能夹着 tEXt、iTXt、tIME 等元数据块。

IDAT 里的数据解压后,就是一行一行紧密排列的像素点,每个像素点由若干通道组成。常见的 PNG 是 RGBA 四通道,也就是红、绿、蓝、透明,每个通道占 8 bit,取值 0 到 255。

FileImgSwap 对图片的改造,本质上就是改写 IDAT 解压后的字节流。它不会破坏 chunk 结构,反而会非常小心地保留 IHDR、IEND 等关键标签,这样图片在任何标准解码器里都能正常打开。

1.3 顺带说一句:PS 里为什么有时导出的文件不一样

有人拿这个问题结合热搜词问过我:PS 切图导出的时候,为什么有时候是 JPG,有时候是 PNG?其实这正好对应了上面讲的两种格式定位——JPG 适合照片这类色彩连续、允许有损的内容,PNG 适合图标、界面截图这类需要保留透明通道、边缘锐利的图形。

FileImgSwap 选择 PNG,还有一个现实层面的理由:PNG 在现代操作系统和浏览器里的兼容性极好,双击能打开,网页能显示,聊天工具能发送,而且绝大多数看图软件不会因为它"长得怪"就去重编码它。这为隐藏文件的传播提供了天然的掩护。

2. 文件是怎么"融化"进像素里的

理解了 PNG 为什么能当载体,接下来拆 FileImgSwap 的核心原理。说实话,原理本身不复杂,但实现细节决定成败。

2.1 一张图能装多少字节

先建立一个基本概念:一张 PNG 图片解压后的像素总量是固定的,就是宽 × 高 × 通道数。比如一张 1920×1080 的 RGBA 图片,总字节数是:

1920 × 1080 × 4 = 8294400 字节 ≈ 7.91 MB

也就是说,这张图的"像素数据空间"接近 8 MB。FileImgSwap 有两种写入策略,对应两种容量和隐蔽性的取舍。

策略 A:整字节替换模式(High-Capacity)

把文件的二进制字节直接写进像素通道的整字节里。假如文件的前 4 个字节是0x48 0x65 0x6C 0x6C,那图片第一个像素的 RGBA 四通道就变成 R=0x48、G=0x65、B=0x6C、A=0x6C,依次类推。

这个模式容量最大,几乎能把整张图片的像素空间全部利用起来。但副作用也很明显——图片的像素分布会直接变成"文件数据的可视化",画面会出现大量雪花噪点一样的杂色。适合不需要在意图片外观的场合。

策略 B:低位嵌入模式(Stealth Mode)

这是默认更推荐的做法。修改每个通道的最低 1 到 4 个比特位,把文件数据"撒"进去。以最低 2 bit 为例,一个通道原本的值是 8 bit,高 6 bit 保留原始画面的核心信息,低 2 bit 用来携带文件数据。

每个像素 RGBA 四个通道各拿 2 bit,就是 8 bit,正好凑出一个字节。所以在最低 2 bit 模式下,每个像素可以含蓄地藏 1 个字节。

这种模式最大优势是肉眼几乎无法察觉。纯色的天空变成了轻微噪点,深色的背景里嵌入点细微变化,人眼基本分辨不出来。我用一张纯蓝色 #0000FF 的图片做过实验,嵌入 1 MB 文件后,放到 4K 显示器上全屏观察,边缘和渐变区域还是有些痕迹,但普通尺寸预览完全看不出来。

模式每像素可用字节隐蔽性典型用途
整字节替换4 字节极差,画面完全破坏不关心外观,只求最大容量
低 4 bit 嵌入2 字节较差,灰度渐变出现色带中容量快速传递
低 2 bit 嵌入1 字节较好,正常查看不易察觉默认推荐
低 1 bit 嵌入0.5 字节极好,肉眼基本无差别高隐蔽需求,容量受限

2.2 头部标记与长度编码:怎么把文件找回

光把数据写进去是不够的,FileImgSwap 还得保证解图时能知道三件事:文件从哪里开始、到哪里结束、还原后的文件名和扩展名是什么。

实现上它会在图片开头的位置写入一段自定义的头部标记。通常是固定魔数(比如FISWAP,4 字节)+ 原始文件名长度 + 原始文件名 + 文件数据长度 + 文件数据。解码器扫描图片像素流,先验证魔数存在,再读元数据,按长度挤出文件部分。

这里有一个很关键的工程细节:为什么文件名要一起存进去?

因为文件还原出来后,系统需要根据扩展名决定用什么软件打开。你还记得接收方是谁、文件是什么格式,不代表交付时能手动改名。把文件名和扩展名嵌入进去,导出时就能一步还原成原始文件名,省去大量人工纠错。

2.3 可选的 Base64 包装

FileImgSwap 在编码层还支持一种"文本化"模式:先把文件的二进制数据做 Base64 编码,再按字符 ASCII 值嵌入像素。这和有些人把图片转成data:image/png;base64,...数据 URI 的动机是一样的——让二进制数据变成纯文本,方便在某些特殊通道中传输。

但代价很直接:Base64 会让体积膨胀约 33%,嵌入 1 MB 文件实际要写入约 1.33 MB 数据。我一般只在确实需要把嵌入文件抠出来作为文本粘贴的场合才用这个模式,日常文件传递用二进制直写就够。

3. FileImgSwap 实际操作流程

考虑到网上不少描述都只说"能转来转去",但没有人把完整流程写清楚,我这边基于一个相当典型的 Python 实现(CLI 工具)来演示。核心依赖是 Pillow 库和一个标准终端环境。

3.1 安装与命令行基础

工具本身是命令行程序,安装方式常规:

pip install fileimgswap

或者如果你下载的是源码,直接进目录跑:

python setup.py install

装好后命令行里会多出fis和fis-swap两个入口命令,功能一样。

一条最基本的隐藏命令长这样:

fis hide --input ./report.pdf --carrier ./cover.png --output ./out.png --bits 2

参数含义:

  • --input:要被隐藏的文件,这里是一个 PDF 报告
  • --carrier:载体图片,也就是承载文件的 PNG
  • --output:输出的伪装图片路径
  • --bits:低位嵌入位数,填 1 到 4,默认 2

执行完,out.png就是一张表面上完全正常的图片,实际上 PDF 的所有字节已经分散隐藏在每个像素的低 2 bit 里。

提取文件的反向操作:

fis extract --input ./out.png --output ./extracted/

工具会自动扫描out.png的像素数据,找到嵌入头部标记,把文件按原始文件名导出到指定目录。还原出来的report.pdf和原始文件字节级一致,SHA-256 校验值相同。

3.2 为什么默认要选 2 bit 而不是 1 bit

这个问题当时我纠结了一阵子。1 bit 隐蔽性最好,但容量只有 0.5 字节/像素;2 bit 隐蔽性依然够用,容量翻倍。实测下来,用一张 2048×1536 的风景照(共 314万像素),1 bit 模式大约只能塞 1.5 MB 文件,2 bit 模式能塞 3 MB。对绝大多数办公文档、配置文件、小体积压缩包来说,2 bit 是性价比最好的平衡点。

如果是传图纸、ISO 镜像这类大文件,我建议反过来思考——不是硬往小图里塞,而是换一张分辨率更高的载体图,或者干脆用整字节替换模式。

3.3 带加密的完整链路

FileImgSwap 同时也支持在隐藏前先对目标文件做 AES-GCM 加密。需要注意这不是什么自己发明的加密,而是调用常见的密码学库完成,密钥以参数形式传入。

一步到位的命令:

fis hide --input ./secret.xlsx --carrier ./cover.png --output ./out.png --bits 2 --encrypt --password 'YourStrongPassphrase'

这一步会在写入像素前,先把secret.xlsx用密码加密成密文,再把密文塞进图片。别人就算用工具扫描出图片里有异常嵌入数据,没有密码也只能拿到一堆加密垃圾。解密提取:

fis extract --input ./out.png --output ./restored/ --password 'YourStrongPassphrase'

这里要郑重提醒一句:忘了密码,文件就彻底没了,不存在什么后门。AES-GCM 本身就是带认证加密,密码错一点都会导致认证失败直接拒绝输出。建议密码用 Bitwarden 这类密码管理器托管,不要依赖记忆。

3.4 批量场景怎么办

单文件交互还好,真正日常用的是批量隐藏。比如你手头有一个文件夹,想整体打包藏进一组图片里,用一句话可以做到:

tar -czf bundle.tar.gz ./docs/ fis hide --input ./bundle.tar.gz --carrier ./cover.png --output ./out.png

先打成 tar.gz 压缩包,再藏进一张图。接收方拿到图后先fis extract解出压缩包,再解压。这套流程对于"一个文件多张图分发"的场景特别合适。甚至你可以把一张大图切成若干份,分别存进一批小图里,实现图片文件的分片传输——当然这个操作需要一点脚本能力,FileImgSwap 本身没有提供专门的分片命令。

4. 容量极限计算与实际选图经验

很多人在真正用起来之前最关心:到底能往一张图里塞多大文件?我直接把计算方法和实测结果讲清楚,方便你在自己的场景里提前做判断。

4.1 容量公式

以 RGBA 模式的 PNG 为例,记图片宽度为W、高度为H,最低Bbit 嵌入,那么理论容量:

容量 = W × H × 4 × (B / 8)

比如一张 3840×2160 的 4K 图,RGBA 四通道,B = 2 时:

容量 = 3840 × 2160 × 4 × (2/8) = 8294400 字节 ≈ 7.91 MB

同样的图,整字节替换模式下容量为:

容量 = 3840 × 2160 × 4 = 33177600 字节 ≈ 31.6 MB

在 B = 2 模式下,一张 4K 图存一份 7 MB 的 PDF 绰绰有余。

4.2 实际测试:不同位数与图片尺寸的对应关系

我拿一组真实文件做过压测,列个表给你参考:

载体图片分辨率嵌入位数文件大小耗时肉眼观感
纯蓝底图1024×10242 bit512 KB0.8 s完全无感
风景照片2560×14402 bit1.8 MB2.1 s仔细看有轻微噪点
深色海报1920×10801 bit1.0 MB1.4 s无感
复杂纹理4000×30004 bit18 MB6.5 s渐变区域出现色带
任意照片800×600整字节替换1.9 MB1.1 s画面被完全破坏,像花屏

建议直接照着这个量级规划自己的用途:日常文档、表格、代码包,用 2 bit 模式挂一张手机拍的照片就够;跨设备传大安装包装镜像,尽量选高分辨率纯色/渐变背景图,然后把位数调高,甚至用整字节替换。

4.3 选载体图片的三个原则

第一,优先选无压缩痕迹的原始 PNG。从微信里二次保存的图片、经过网页压缩工具处理过的图,本身已经丢过一轮信息,再用作载体虽然也能塞,但可能让嵌入数据的容错性下降。理想载体是相机直出后转存的 PNG,或者设计软件导出的原始 PNG。

第二,避免大面积纯色但带有强边缘的图。纯色区域嵌入数据后噪声感相对明显。更推荐有自然纹理的图——树叶、墙面、水面、云层,这些地方的像素值天然跳动,多塞点数据人眼根本看不出规律变化。

第三,统一用 RGBA 或 RGB 模式的 PNG。如果载体是索引用色板(Indexed Color)的 PNG,很多工具处理时会先转成 RGBA 再嵌入,这时候压缩方式可能被改变,出图后在某些看图器里反而会有问题。FileImgSwap 本身会在加载图片时自动转成 RGBA 模式处理,但出图后如果被二次保存为 PALETTE 模式,数据是有可能被破坏的,这个放进下一节展开讲。

5. 实测过的一堆坑:这些情况会让嵌入文件报废

用 FileImgSwap 做真正的数据传递时,栽过的跟头得单开一章讲,因为很多坑不在工具本身,而是在周边环境。

5.1 图片在文件夹里不显示缩略图/预览

有人反映,藏完文件的 PNG 在 Windows 资源管理器里不显示缩略图,图标变成空白。这其实是因为我上面提到的——资源管理器生成缩略图时,会对 PNG 做一次解码,并读取某些辅助数据块。FileImgSwap 处理过的图片,如果元数据块被修改过、或像素流和某些解码器的快速预览逻辑不兼容,就可能被"跳过预览"。

这不是文件损坏的标志,而是解码器对异常数据过于敏感。实际验证:用系统自带的画图、浏览器打开,图片完全正常;用 FileImgSwap 提取,文件也完整还原。所以如果遇到预览空白,先别慌,正常传输即可。如果实在需要缩略图,可以把图片发给对方前用「画图」工具重新打开再另存一次,这样会重新生成一个规整的 PNG 结构,预览就恢复了——但要注意,另存过程可能会重编码,除非你确认嵌入数据不受影响,否则建议先备份原始藏文图。

5.2 不经意的重压缩:微信/QQ 传送后的图片

这是我踩过最深的坑。用 FileImgSwap 把文件藏进图片后,通过微信发送给同事,对方提取时报错、数据损坏。排查到最后发现微信在发送图片时,自动对图片做了二次压缩和格式转换,嵌入数据被当成"无用信息"清掉了。

所以这里给一条铁律:藏了文件的图片,不要通过任何会"优化图片体积"的聊天软件直接发送。要么用原图模式发送,要么把它打包进 ZIP/RAR 再传,再或者放到网盘让对方原样下载。任何经过在线压缩的图片,都有数据丢失风险。

同理,某些网站的"图片瘦身"工具、笔记软件的剪藏功能,都会在后台重编码。 你无法预判这些服务会不会对像素做手脚,最稳的方案就是走文件通道。

5.3 透明通道的隐患:RGBA 被丢弃

PNG 的 A 通道(Alpha,透明通道)在很多转码流程里会被特别处理。比如 iOS 的相册、某些 Android 平台的图片处理库,读取 PNG 时可能会丢弃 Alpha 通道,强制把图转成 RGB。如果 FileImgSwap 嵌入文件时把数据分散存放在 Alpha 通道里(常见的 4 bit 模式会这么干),一旦 Alpha 被剥掉,部分数据就没了。

在跨平台传递时我会特意选择 RGB 三通道模式的图片(即使原图是 RGBA,也可以先转成 RGB 再作为载体),或者用--bits 2这种低位模式,让数据尽量集中在 RGB 通道。这样即使传输过程中 Alpha 被丢弃,数据依然完整。

5.4 索引用色板的二次保存

如果你用 PS 或某些优化工具把藏文图保存成 PNG-8(索引色,256 色),问题就大了。索引色 PNG 的像素存储方式和 RGBA 完全不同,每个像素存的是调色板索引号,不是直接的 RGB 值。FileImgSwap 的解码器是按 RGBA 字节流解析的,拿到索引色 PNG 就很难还原数据。

记住一条:载体图片统一准备成 PNG-24(RGB)或 PNG-32(RGBA)。操作上最简单的方法是,藏文前用convert或者 Pillow 一次性统一格式:

from PIL import Image img = Image.open('cover.png').convert('RGBA') img.save('cover_rgba.png')

5.5 data:image 数据 URI 场景里的特殊表现

顺便聊一个和热搜词相关的场景:在 Excel 公式、HTML 代码、报表系统里,经常见到data:image/png;base64,...这种字符串。它本质上是把图片文件编码成 Base64 字符串,嵌入到文档或网页中,避免外部图片链接失效。

FileImgSwap 藏完文件的 PNG,其实也可以被转成这种data:image/png;base64,...形式进行文本化传递。步骤是:

fis hide --input ./secret.docx --carrier ./cover.png --output ./out.png --base64

注意我在命令里显式加了--base64,这样工具会在像素数据里额外写入一层 Base64 包装,生成后的 PNG 可以转成 data URI 传给任何支持文本粘贴的通道。接收方把那串文本还原成 PNG 文件,再用fis extract解析,文件就能复原。

我在实测中发现这招在"需要把隐藏文件贴进在线表单、API 文档、IM 对话框"时特别好用,配合前面的加密参数,可以在纯文本通道里安全传递小体积文件。

6. 进阶用法与安全边界

FileImgSwap 不是只能做"藏文件"这一步,配合一些外部工具链,能玩出更多花样。

6.1 把多份文件分段藏进一批图片

之前在文件分发场景里提到,如果需要把一个大文件分别藏在若干张图里传递,可以先用split命令把文件分段:

split -b 5m ./large_archive.zip chunk_

每个 chunk 几 MB,然后分别用不同图片做载体。接收方把所有分片提取出来后,再cat拼回去:

cat chunk_aa chunk_ab chunk_ac > restored_archive.zip

这个玩法在绕过"单张图片体积限制"的传输方案里很实用。你把 50 MB 的文件拆成 10 张 5 MB 的伪装图,分发压力小很多。

6.2 图片本身也当把柄:用隐写检测工具自守

强调一下,FileImgSwap 属于隐写工具,不是加密工具,它提供的是"看起来没事"的伪装能力,而不是数学上的不可破解性。了解更多的人用zsteg、binwalk这类检测工具,可以轻松发现 PNG 的像素低位里有大量规律性嵌入痕迹,甚至直接提取出嵌入数据。

所以安全建议是:重要文件务必和--encrypt配合使用。隐写负责"藏",加密负责"即使被发现也读不懂"。两者结合,才是完整的安全链路。

我自己对内的要求是:凡是不是亲测可控的传输通道,一律加密再藏。

6.3 数字水印与版权溯源

另一个正经场景是给图片打数字水印。比如你拍了一张原创摄影图,不希望被别人随便盗用,可以把你的联系方式、版权声明写进一个文本文件,用 FileImgSwap 以 1 bit 模式嵌进图片里。这样即使图片被转发,只要像素没被重度压缩,你都可以随时提取出版权信息,作为原创证明。

这是一个很多人忽视的用法——不需要在图片上打一个显眼的半透明水印影响观感,数据水印是完全隐藏的。

6.4 反向使用:从图片里剥离信息

FileImgSwap 的解码功能不只用来恢复数据,也可以用来检查一张来路不明的 PNG 是否被人动过手脚。跑一下fis extract --input suspicious.png --output ./scan/,如果能解出文件,那这张图基本可以断定是隐写产物;如果解不出来,先看一下图片自身的数据量是否合理(一张完全正常的照片,压缩后很少会达到理论像素容量的 90% 以上)。

这里要提醒一个反向使用的限制:FileImgSwap 的检测流程依赖自己的头部魔数,如果别人用别的隐写工具藏数据,不一定能扫出来。专业检测还是交给zsteg、stegsolve这类工具,FileImgSwap 只是顺手自查的水平。

7. 我的一点总结性实操体会

FileImgSwap 这个工具的设计思路,说穿了就是"用 PNG 的无损特性当容器,用像素冗余当存储空间"。它的价值不在于加密强度,而在于把"传递一个文件"这件事包装成了"传递一张图片",从而避开很多不必要的注意。

如果你只是偶尔传一次配置文件,那它的学习成本几乎为零——安装、隐藏、提取三步走。如果你想把它用成一套日常工具,我的建议是:

  • 建立固定习惯:所有要藏的文件,先tar打包再隐藏,省得一个文件一张图地管理。
  • 指定一台电脑作为"中转站",固定装好工具和密码管理器,不随意在别的机器上安装。
  • 重要的隐藏图给文件名加上约定后缀(比如_fis.png),避免混在普通图片堆里忘记哪张藏了东西。

最后再分享一个小技巧:用 FileImgSwap 藏完文件后,记得顺手用file命令校验一下输出图的文件类型描述。正常情况下它应该输出PNG image data, 1920 x 1080, 8-bit/color RGBA, non-interlaced。如果输出里出现了异常的分辨率描述或色彩模式,说明工具处理时可能遇到了不规范的载体图,尽早换图重新处理,比传到一半才发现文件坏了要强得多。

工具本身不复杂,但它打开了"图片不只是图片"这扇门。下次看到一张平平无奇的 PNG,你可能会多想一秒钟——里面到底藏了什么?

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

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

立即咨询