对前文的一个小小的补充。
在评论区有人提出用上文所述的脚本提取出来的封面和试阅版的封面hash不一致。我一开始没当回事,说不定两张图根本只是单纯在服务器压缩了2次而已,hash不一样的可能性太多了。不过为了谨慎起见,还是测试了一下。没想到一测,就发现还真的有点东西。
留言的网友询问的是 BW 台湾站,那就拿台湾站来测试。现在角川所有的服务器都已经升级到了最新版的JS,所以直接改下US的@match就行。
检查无混淆的图
我随便找了本漫画,先是下载了试读版——试读版和正式版不同,是所有页面都没有混淆的,所以其实你控制台直接下载也行,和用我的脚本是一样的。因为试读版不登录就可以查看,所以应该是没有什么账号信息的。
然后使用网友提供的账号下载同一本作品的完整版——但是我偷懒没有下所有页数啦,得益于脚本的更新,现在可以选择页数了,我就下了前10页。
和BW日本站一样,封面是无混淆的图,后面的页面混淆。我们的重点是这个没有混淆的cover页。理论上,这个图应该和试读版一样,然而两者的文件大小错了有整整20KB。
那么就是一系列的对比啦,我用了下面这些步骤。
第一步最简单的,对比图像数据。我手头有一个我自己写的Py小脚本来对比两张图的像素,也可以用拖进PS->两个图层叠加->计算差值->合并->查看对比度的笨方法。嗯,两者确实是逐像素相等的。
那么第二部就是打开XnView MP查看元数据:

(我一般properties和ExifTool都看一遍)也没有任何区别。
于是我打开Beyond Compare想直接二进制对比,结果发现两者有巨大差别,打了个我个措手不及。明明逐像素相等,为什么图像数据的部分字节也对不上?没什么头绪的时候,突然想起之前研究JPEG spec的神器,JPEGsnoop,赶紧掏出来。
一比就发现了为什么二进制错那么多:原来两张图的霍夫曼表完全不一样——一个“优化”了(正式版),一个没有优化(试读版),具体区别可以查看旧文。难道这就是两者的唯一区别了吗?在我即将关闭JPEGSnoop之时,看到一个非常重要的信息:

好家伙,在EOF后面藏东西,那基本可以猜到是啥了:

基本不用猜,这32bytes 的数据肯定是用户的ID了。我赶紧测试了下其他的图,结果很意外地发现那些混淆后的图反而没有这串水印——不过确实也没啥意义吧?毕竟你加什么水印只要不是加到图像内容里,被我们重新拼图之后都消失了。
我又进行了以下测试:
- 用同账号下载别的完整版书籍——封面图依然有水印而且ID一致;
- 用我自己申请的新账号购买了同一本书——果然水印ID不一样。
那么基本可以99%肯定这就是角川加进来用来反查用户的数字水印了。而且这种加法很容易,服务器给文件的时候直接在文件后面append就行。他服务器也不需要存多份文件。
移除水印
移除这水印很简单,可以直接二进制删掉最后32 bytes。如果不熟悉二进制操作,我测试了下XnView MP自带的清理元数据功能:


其实这里勾啥都行,因为任何操作都会导致他重新生成一次JPEG文件架构,从而删掉EOF后面的多余字节。这里如果勾选第一个优化霍夫曼列表,最后出来的文件大小就是类似于完整版,否则就是类似于试读版。我们正好可以验证一下,圈中所有三个文件(试读版,账号1的完整版,账号2的完整版)clean一波,出来的三个文件 md5 完全一致。
当然,你也可以直接再存成PNG,那肯定啥水印都没了。虽然没必要,徒增体积。
混淆过的图的检查
上面已经说过混淆过的图并没有这个二进制的ID水印。不过内容我们还是检查下吧。检查很简单——先把账号1和账号2的图都解码,然后互相对比:结果是逐像素相等。那就说明没有任何可以识别出下载者账号的内容水印。
我然后和预览版的对比——预想来说是不可能逐像素相等的,因为毕竟服务器给的资源是把原图(JPEG)拆成32px的块之后混淆后再存了一次JPEG,那自然有新的JPEG artifact引入。虽然我们还原时是用了bmp/png没有再引入新的JPEG artifact,但是也无法去掉第二次的。
结果一对比还挺出乎我意料的:居然除了最右边一排别的都是逐像素相等?怎么做到再存了一次JPEG没引入新的JPEG artifact的?如果认真学习过(误)我之前写的JPEG spec文章的应该就懂,JPEG的最小编码单元(MCU)是8×8的,所以是可以以最小8×8的尺度对原图进行无损重新组合的(或者旋转)。而角川的混淆的块儿是32×32,所以完全可以。没想到角川居然真的用到了JPEG的这个特性,混淆图片时直接是对原始JPEG流的MCU进行的swap操作!太低估他们了。
这还意味着什么呢?我们目前在 Python 里进行的还原混淆的canvas操作,其实也完全可以采用直接交换JPEG流的MCU的方式,来实现完美还原原始JPEG——好吧应该说90%完美,因为边缘非8的倍数的部分还是没法100%还原,这部分在角川生成混淆JPEG的时候已经给padding到了8的倍数了(也因此引入了二次压缩,所以上面也观察到最右边一排还是没能逐像素相等)。
Hmm,想了想意义很小而且好像很麻烦,就懒得折腾了。留作以后的课题。
结语
虽然上面都是说台湾站,但是日本站也是一样的,至少我随便看了下,无混淆图也是有ID的。
虽然我自己是无所谓,因为我dump BW全都只是自己收藏而已,但是如果要拿出去分享尤其是大范围分享,那确实得小心一点了。我更新了下之前的bw.py,现在可以自动移除数字水印了。













