【问题标题】:Is it possible to hide data in a bitmap using LockBits?是否可以使用 LockBits 隐藏位图中的数据?
【发布时间】:2020-11-09 21:32:44
【问题描述】:

我正在用 C# 编写一个小型隐写术应用程序,并且能够隐藏图像中的文本。但是我使用的方法是GetPixel/SetPixel 方法,它对于较大的图像要慢得多,我在尝试隐藏图像中的 mp3 文件后注意到了这一点。经过一些谷歌搜索,我发现了LockBits。虽然速度确实大幅提高,但我发现我无法提取隐藏在图像中的加密数据(密文)。

我不确定问题出在我如何插入数据或提取数据时。尝试提取 Base64 密文时,它会被损坏(随机符号和字符)并引发异常,说明它不是 Base64String。我最终按照LockBits 文档中的内容更改了代码,我将其粘贴在下面。

合并密文

public static unsafe void MergeEncryptedData(string data, Bitmap bmp, string output) {
    State s = State.HIDING;

    int height = bmp.Height;
    int width = bmp.Width;

    var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);
    byte * scan0 = (byte * ) bitmapData.Scan0;

    int bytesPerPixel = 4;
    int dataIndex = 0;
    byte dataValue = 0;
    long colorUnitIndex = 0;
    int zeros = 0;
    byte R, G, B;

    Parallel.For(0, height, (i, loopState) = > {

        byte * currentLine = scan0 + (i * bitmapData.Stride);

        for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {
            R = currentLine[i + 2];
            G = currentLine[i + 1];
            B = currentLine[i];

            for (int n = 0; n < 3; n++) {

                if (colorUnitIndex % 8 == 0) {
                    if (zeros == 8) {
                        if ((colorUnitIndex - 1) % 3 < 2) {
                            currentLine[i + 2] = R;
                            currentLine[i + 1] = G;
                            currentLine[i] = B;
                            //bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
                        }
                        loopState.Stop();
                    }

                    if (dataIndex >= data.Length) {
                        s = State.FILL_WITH_ZEROS;
                    } else {
                        dataValue = (byte) data[dataIndex++];
                    }
                }

                switch (colorUnitIndex % 3) {
                case 0:
                    {
                        if (s == State.HIDING) {
                            B += (byte)(dataValue % 2);
                            dataValue /= 2;
                        }
                    }
                    break;
                case 1:
                    {
                        if (s == State.HIDING) {
                            G += (byte)(dataValue % 2);
                            dataValue /= 2;
                        }
                    }
                    break;
                case 2:
                    {
                        if (s == State.HIDING) {
                            R += (byte)(dataValue % 2);
                            dataValue /= 2;
                        }
                        currentLine[i + 2] = R;
                        currentLine[i + 1] = G;
                        currentLine[i] = B;
                        //bmp.SetPixel(j, i, Color.FromArgb(R, G, B));
                    }
                    break;
                }

                colorUnitIndex++;

                if (s == State.FILL_WITH_ZEROS) {
                    zeros++;
                }
            }
        }
    });

    bmp.UnlockBits(bitmapData);
    bmp.Save(output, ImageFormat.Png);
}

提取密文

public static unsafe string ExtractData(Bitmap bmp) {
    int height = bmp.Height;
    int width = bmp.Width;

    var bitmapData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bmp.PixelFormat);
    byte * scan0 = (byte * ) bitmapData.Scan0.ToPointer();

    int bytesPerPixel = 4;
    int colorUnitIndex = 0;
    int charValue = 0;
    string extractedText = String.Empty;

    Parallel.For(0, height, (i, loopState) = > {

        byte * currentLine = scan0 + (i * bitmapData.Stride);

        for (int j = 0; j < (bitmapData.Width * bytesPerPixel); j += bytesPerPixel) {

            for (int n = 0; n < 3; n++) { //this particular loop feels incorrect

                switch (colorUnitIndex % 3) {
                case 0:
                    {
                        charValue = charValue * 2 + currentLine[i] % 2;
                    }
                    break;
                case 1:
                    {
                        charValue = charValue * 2 + currentLine[i + 1] % 2;
                    }
                    break;
                case 2:
                    {
                        charValue = charValue * 2 + currentLine[i + 2] % 2;
                    }
                    break;
                }

                colorUnitIndex++;

                if (colorUnitIndex % 8 == 0) {
                    charValue = reverseBits(charValue);

                    if (charValue == 0) {
                        loopState.Stop();
                    }

                    char c = (char) charValue;
                    extractedText += c.ToString();
                }
            }
        }
    });

    bmp.UnlockBits(bitmapData);
    return extractedText;
}

抛出错误时提取的密文的示例: I$I$I$I$I$I$I$I$I$I$I$I$I$I䥉II!J$$。 它应该是一个 Base-64 字符串

仅供参考,我使用 LUT PNG 图像来隐藏数据,因此与原始图像相比,我可以看到颜色略有不同。所以我知道 RGB 值确实在改变。

【问题讨论】:

  • 是的,您可以在没有锁定位的情况下隐藏数据。
  • 为了吸引答案,您可能需要将其简化为 minimal reproducible example。您是否尝试过确定 MergeEncryptedData()ExtractData() 中哪一个的行为与旧的 GetPixel()SetPixel() 版本不同?让我们找出这两种方法中的哪一种有错误有点超出了堆栈溢出问题的范围。
  • 首先,将MergeEncryptedData()的输出与旧的SetPixel()版本进行比较;如果存在差异,则表明MergeEncryptedData() 存在问题。接下来,尝试将旧版SetPixel() 的有效输出发送到ExtractData()。如果有错误,则 ExtractData() 中存在错误。一旦找出问题所在,您就可以使用更准确的信息和可以正常工作的旧代码示例来更新您的问题。

标签: c# encryption bitmap steganography


【解决方案1】:

你需要考虑:

  • 步幅 - 图像数据的图像宽度可能不同。 More info here
  • 颜色/数据通道的图像数量
    • 8 bpp(1 通道)
    • 24 bpp(3 通道 RGB)
    • 32 bpp(4 通道 ARGB,其中 A 表示 alpha,透明度)

您提到了 RGB PNG,但您在代码中使用了 4 个通道 (ARGB),请仔细检查。

这是一个示例方法,它将获得与使用慢速 Bitmap GetPixel 相同的数据,但速度非常快。根据此示例,您可以修复您的代码,如下所示:

  • 如何计算每像素位数
  • 如何正确使用步幅
  • 如何读取多个频道

代码:

/// <summary>
/// Get pixel directly from unmanaged pixel data based on the Scan0 pointer.
/// </summary>
/// <param name="bmpData">BitmapData of the Bitmap to get the pixel</param>
/// <param name="p">Pixel position</param>
/// <returns>Pixel value</returns>
public static byte[] GetPixel(BitmapData bmpData, Point p)
{
    if ((p.X > bmpData.Width - 1) || (p.Y > bmpData.Height - 1))
       throw new ArgumentException("GetPixel Point p is outside image bounds!");
    
    int bitsPerPixel = ((int)bmpData.PixelFormat >> 8) & 0xFF;
    int channels = bitsPerPixel / 8;

    byte[] data = new byte[channels];
    int id = p.Y * bmpData.Stride + p.X * channels;
    unsafe
    {
        byte* pData = (byte*)bmpData.Scan0;
        for (int i = 0; i < data.Length; i++)
        {
            data[i] = pData[id + i];
        }
    }
    return data;
}

【讨论】:

    猜你喜欢
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多