是否可以使用LockBits在位图中隐藏数据?

时间:2020-07-20 15:20:55

标签: c# encryption bitmap steganography

我正在用C#编写一个小型的隐写应用程序,并且能够在图像中隐藏文本。但是我使用的方法是GetPixel/SetPixel方法,对于较大的图像,该方法要慢得多,在尝试将mp3文件隐藏在图像中后,我注意到了这种方法。经过一些Google搜索,我发现了有关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值的确在改变。

1 个答案:

答案 0 :(得分:0)

您需要考虑:

  • 跨度-具有的图像数据的图像宽度可能有所不同。 More info here
  • 颜色/数据通道的图像数
    • 8 bpp(1个频道)
    • 24 bpp(3通道RGB)
    • 32 bpp(4通道ARGB,其中A表示Alpha,透明度)

enter image description here

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

这里是一个示例方法,它将获得与使用慢速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;
}