Stack Overflow Asked by Jeremy James on November 16, 2021
I’m writing a small steganography application in C# and was able to hide text in images. However the method I used was the GetPixel/SetPixel
method which was a lot slower for larger images, which I noticed after trying to hide a mp3 file in the image. After some google searches, I found out about LockBits
. While the speed did improve drastically, I discovered that I was unable to extract the encrypted data (the cipher text) which was hidden in the image.
I’m not sure if the issue is with how I insert the data or when extracting it. When attempting to extract the Base64 cipher text, it would be corrupted (random symbols and characters) and throw an exception about it not being a Base64String. I ended up changing the code by following what was on the documentation for LockBits
, I’ll paste it below.
Merging the cipher text
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);
}
Extracting the cipher text
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;
}
An example of what the extracted cipher text looks like when the error is thrown:
I$I$I$I$I$I$I$I$I$I$I$I$I$I䥉II!J$$
.
It should be a Base-64 String
Just for reference, I’m using a LUT PNG image to hide the data, so I’m able see a slight difference in color when compared to the original. So I know the RGB values are indeed being changed.
You need to consider:
You mention RGB PNG but you are using 4 channels in your code (ARGB), double check that.
Here is a sample method that WILL obtain the same data that using the slow Bitmap GetPixel, but really fast. Based on this sample you can fix your code, as:
Code:
/// <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;
}
Answered by Pedro77 on November 16, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP