天空 Blond - 隐写术
type: channel
link: https://www.skyblond.info/tag/%E9%9A%90%E5%86%99%E6%9C%AF/
items: 【代码札记】初探图片隐写术（Steganography）之DWT-DCT-SVD隐写
items: 【代码札记】初探图片隐写术（Steganography）之DFT隐写
items: 【代码札记】初探图片隐写术（Steganography）之LSB隐写

【代码札记】初探图片隐写术（Steganography）之DWT-DCT-SVD隐写
type: item
date: 2023-07-09T16:40:00+08:00
description: 因为论文太贵了，所以我照着论文标题和摘要尝试复现了一篇论文。上一篇文章讨论了DFT隐写，这种隐写方法能够抵抗各种攻击，但总体来说那更像是盲水印，用于标记而非传递信息。这一篇文章本来想写DCT，但后来我发现单纯的DWT和DCT隐写都是在频域上做手脚，并且效果都不是特别好。后来我看到一篇论文讨论DWT-DCT-SVD隐写，但很遗憾，这篇论文收费的，我在sci-hub上也没有找到，所以就照着论文标题和摘要，问了问ChatGPT，然后基于我和他的解读写了这篇文章。这篇文章并没有前几篇写的那么详尽，因为我对这之中提及的概念并不十分熟练，只是现学现卖。同时这种隐写方法的表现也没有那么好，或者说需要大量的尝试才能发挥出最佳水平。因此这篇文章描述的流程可能和论文里完全不一样，也可能是错的，也可能我漏掉了一些细节，总之我不太想在这种不确定（不清晰）的路径上花费太多精力去打磨。所以这篇文章可能在质量上不如前两篇。论隐写容量还得是LSB啊参考论文这里参考的论文是DWT-DCT-SVD based watermarking。摘要如下：Some works are reported in the frequency domain watermarking using Single Value Decomposition (SVD). The two most commonly used methods are based on DCT-SVD and DWT-SVD. The commonly present disadvantages in traditional watermarking techniques such as inability to withstand attacks are absent in SVD based algorithms. They offer a robust method of watermarking with minimum or no distortion. DCT based watermarking techniques offer compression while DWT based compression offer scalability. Thus all the three desirable properties can be utilized to create a new robust watermarking technique. In this paper, we propose a method of non-blind transform domain watermarking based on DWT-DCT-SVD. The DCT coefficients of the DWT coefficients are used to embed the watermarking information. This method of watermarking is found to be robust and the visual watermark is recoverable without only reasonable amount of distortion even in the case of attacks. Thus the method can be used to embed copyright information in the form of a visual watermark or simple text.隐写方案我拿着论文摘要去问ChatGPT，可能是因为我没开会员，所以ChatGPT也没说出来个所以然。虽然是2008年的论文，可能因为没有公开访问，所以ChatGPT的训练资料中没有相关的内容，总之帮助不大。从论文摘要来看，首先需要将图片应用DWT，然后对DWT结果再进行DCT，然后对DCT结果应用SVD，最后在SVD的S矩阵里进行隐写。DWT是离散小波变换；DCT是离散余弦变换，SVD是线性代数中的奇异值分解。话是这么说，在写DFT隐写的时候我就注意到了，除非有诸如FFT这种经过优化的快速算法，否则按照原始的公式定义去处理大图片会非常慢。但是就我在网络上搜索的结果来看，好像DWT和DCT都没有什么特别好用的快速算法，并且我期待这种隐写结果能够抵御JPEG压缩，所以我认为最好的办法就是从JPEG压缩中找灵感。JPEG压缩JPEG压缩是利用人眼对高频信息和色彩不敏感的原理，丢弃图像中不易被人眼辨识的信息，从而达到减少文件大小的效果。总地来说，JPEG压缩的流程如下：将图像从RGB空间转换成YUV空间，其中Y（明度）完全保留，U和V进行下采样（即…

【代码札记】初探图片隐写术（Steganography）之DFT隐写
type: item
date: 2023-07-01T13:57:00+08:00
description: 比起隐写，我认为DFT更像是盲水印。在各种D*T中，DFT看起来应该是相对简单的一个。DCT我有个大概印象，但DWT我实在不知道是什么高端的玩意儿。总之，本文将讨论在DFT变换域隐写数据。什么是DFTDFT全称Discrete Fourier transform，即离散傅里叶变换。那什么是傅里叶变换呢？我不知道该怎么给你解释，但你可以参考这个视频。简单地说，傅里叶变换是一种拆解信号的过程。以音频信号为例，一段录音对于计算机来说就是麦克风在时间尺度上对气压进行采样，一段录音可以看作是气压在时间上的函数。可是对于人的听觉来说，气压并不是特别有效的信息，而气压的变化才是。气压在某个固定周期内重复波动，这近似就算是频率了。我们的耳朵能够感受到频率，但计算机只能收录气压的采样，那怎么才能让计算机分析出人能够感知的频率呢？傅里叶变换就能够做到这件事。对于一维信号（只有一个变量），傅里叶变换将信号拆解成一系列正弦或余弦信号的叠加，而这里的正弦或余弦信号就是纯粹的频率。换句话说，傅里叶变换允许你用一系列纯粹的频率来表示一段音频（例如三倍的440Hz加上五倍的320Hz）。类似地，离散傅里叶变换是作用在离散信号上的。在理想情况下，声音应该是气压在时间上的连续函数，但对于计算机来说，声音则是每隔一段时间对气压的采样，它并不连续。因此将傅里叶变换推广到这种离散信号上，就变成了离散傅里叶变换。这在计算方式上变了，但在原理上没有改变。对于图像这样的二维信号（两个变量，x、y），傅里叶变换同样将其拆解为频率，但这里的频率并不是人耳能听到的频率了，它指的是像素明暗的周期性变化，而这里的频率也具有两个参数，分别表示水平频率（x方向）和垂直频率（y方向）。如果一个频率是(5,0)，那么这个频率看起来应该是水平方向的明暗交替；而频率(0,5)则是竖直方向的明暗交替；而频率(5,5)则是从左上角到右下角方向的明暗交替。如果光看文字难以想象的话，我推荐观看这个视频。从DFT到FFT离散傅里叶变换的计算其实不难，并没有什么特别高神的东西：$$ F(x)=\sum_{n=0}^{N-1}e^{-i\frac{2\pi}{N}nx}f(x) $$其中F(x)是傅里叶变换，f(x)是原来的信号。如果是二维的话，根据二维傅里叶变换的分离性，可以分解为在两个方向上分别进行傅里叶变换：即先以行为单位，计算x方向的傅里叶变换，然后以列为单位，计算y方向的傅里叶变换：suspend fun ComplexChannel.fft2d(): ComplexChannel = coroutineScope { // result: [height, width] val rowTransformed = this@fft2d.indices.map { y -> // feed each horizontal line into fft async { FFT.fft(this@fft2d[y]) } }.awaitAll() // here we calculate based on columns // aka the output shape is [width, height] val columnTransformed = this@fft2d[0].indices.map { x -> // we need to collect each vertical line async { val columnData = Array(this@fft2d.size) { y -> rowTransformed[y][x] } FFT.fft(columnData) } }.awaitAll() // recover the structure back to [height, weight] Array(this@fft2d.size) { y -> Array(this@fft2d[0].size) { x -> columnTransformed[x][y] } } }但如果你仔细观察DCT的定义的话，你会发现这个算法其实并不高效。在处理$e^{-i}$那部分时，需要使用欧拉公式将其变换为三角函数，这对于计算机来说本身就是一个难事了，更加雪上加霜的是，计算傅里叶变换的一个点就要对整个输入空间遍历一次。我们先看按行计算的傅里叶变换，假设图片宽度是w，那么计算一个点的傅里叶变换就要遍历w次，而完整计算一行就需要遍历$w^2$次，这还是一行，如果图片的高为h，那么光是按行计算就要遍历$hw^2$次；接下来再考虑按列计算，类似地，要$wh^2$次遍历。当你看到复杂度里面带着平方的时候，大概就能想到它算起来不会很快。为了解决这个问题，人们想出了FFT，快速傅里叶变换。由于我不是很在行数学，…

【代码札记】初探图片隐写术（Steganography）之LSB隐写
type: item
date: 2023-06-14T22:51:00+08:00
description: 本文介绍如何使用LSB隐写将数据藏匿在图片中。通常来说，要保护你的数据，最简单的方法就是加密。现代加密算法在正确使用的前提下可以认为是非常安全，除非这地球上所有国家决定不惜一切代价就是要干爆你（话是这么说，如果真是到了这种地步，一颗核弹不比破解先进的加密算法来的划算？）。但加密算法有一个问题：他会让别人看出来这是加密的。这一点在大多数情况下没什么问题，但假如你的身家性命就寄托在这份数据上，那除了加密，你最好还要把它好好地藏起来。不止间谍、记者和人权活动家有这种需求，我认为作为一个文明社会的人类个体，每一个人都有正当藏匿数据的权利，每个人的隐私也都值得且应当受到保护，任何其他个人和组织（包括政府）都不应该侵犯这种权利。而为了保护我们的权利，相应的技术也孕育而生。这些技术可能会被滥用于犯罪活动，但我认为从整体而言，隐私权利被侵犯的后果要比一个技术被滥用于犯罪的后果要大得多——任何时候为了预防犯罪而侵犯个人权利都是不恰当的。如果你强烈不认同这一观点，那么这里不欢迎你，请你离开。总之，和加密不同，隐写术是一种帮助你将数据隐藏在另一种数据中的技术。本篇文章以图片隐写术为主题，即以图片为载体藏匿数据。这其中，LSB隐写是一门非常原始但有效的技术（当然，如果使用得当的话）。你可能看出来了，任何宣称安全可靠的东西都有一个前提：使用得当。具体来说，不同的加密算法和隐写术有各自的特性和风险。我不是隐写术和安全方面的专家，本文也没有经过同行评议，因为它压根儿就不是论文。如果你决定采用下文中介绍的技巧和方法藏匿数据，你需要自己评估并决定承担其中的风险。何为LSB？LSB的全称是Least Significant Bit，翻译过来是最低有效位。什么叫最低有效位呢？比如一个数字12345，那它的最低有效位就是个位（数字5），因为它的改变对于值的影响最小。而相对应地，万位（数字1）是最高有效位，即Most Significant Bit，MSB。在计算机中，所有数据都是以二进制形式表示的，因此LSB通常是指bit 0。由于LSB的变化对于值的影响很小，而现代表示图像的方法采用RGB三个通道来表示，每个通道有256个可选的值。对于肉眼来说，这个值增大一点或减小一点，几乎难以区分。不信？看看这张图：这张图的主要颜色是0b87f0，但其中我以0b87ef、0b86f0和0c86f0三种颜色在上面随手花了几笔，你能找到吗？这种细微变化对于计算机来说是很明显的数值变化（你可以把这张图下载下来，放到Photoshop里，使用魔棒工具在容差为0的设置下随便选一个点，你就能看到Photoshop找到的轮廓），可是对于人眼来说，即便是最精确的显示器，人眼也无法辨别出这种细微的差距。因此这就成为了一种藏匿数据的方法，并且这种方法能够藏匿大量数据。但对应的，这种方法也非常脆弱。如果隐写的数据使用恰当的方式编码，也许可以抵挡剪切、平移、拉伸和旋转；但如果攻击者使用压缩算法处理了图片，那无论怎么编码都没办法保留数据——图像压缩算法的目的就是丢弃肉眼不可见的细节，从而减少数据量，而LSB隐写就是依靠这些细节来藏匿数据的。因此LSB隐写只对位图有效，也就是我们常见的bmp和png文件。虽然在二进制层面上设计一个抗剪切、平移、拉伸和旋转的编码方式很难，但如果你把格局打开，把被编码的数据当作图像的话，你甚至可以把一个二维码的黑白像素平铺到一张图片中。只要被裁剪的部分还能拼凑出一个完整的二维码，你就可以还原其中的数据。但这样一来，编码的效率就大大减少了。如果你要在一个图片里藏匿二进制文件，那几乎没有什么特别健壮的编码方式。一些冗余校验算法可以纠正部分损失，但是拉伸和旋转大概率是招架不住的。LSB隐写说到这里，有灵性的读者应该已经能够猜到LSB如何工作的了：一张图片就是有像素构成的二维数组，而每一个像素由三或四个通道构成。如果是BMP，那就是RGB三个通道；如果是PNG，那就是ARGB四个通道，A表示透明度。虽然通道这个说法听起来很高端，但实际上就是告诉你一个像素有几个值。由于表示透明度的Alpha通道不常用，因此这里我们只考虑RGB三个通道。一般而言一个像素有一个4字节的整数表示：0xAARRGGBB，AA表示Alpha通道的值，通常是FF，也就是255；而RR（bit 16到23）代表红色的取值、GG（bit 8到15）代表绿色的取值，而BB（bit 0到7）表示蓝色的取值。我们可以挑一个通道进行隐写，也可以同时在三个通道隐写，增加可以隐写的数据量。假设我们的图片大小为768*768，那么一共就有589824个像素，每个像素隐写1bit信息，我们就可以隐写72KB的信息。如果我们在三个通道同时隐写，那就可以隐写216KB的数据，这大约相当于一张低质量的JPG图片，或者7万字的中文文本。代码…