Created
March 27, 2017 10:31
-
-
Save AndreCAndersen/78b38ef60b402c7f1b7566e091941d0a to your computer and use it in GitHub Desktop.
Hiding Your Bits in the Bytes: A basic example of modern steganography using C#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
namespace SteganographyTest | |
{ | |
class Program | |
{ | |
static void Main() | |
{ | |
byte[] hiddenBytes = Util.BitmapToByteArray(Image.FromFile("hidden.png")); | |
Encode(hiddenBytes, "innocuous.png", "encoded.png"); | |
byte[] loadedHiddenBytes = Decode("encoded.png"); | |
Util.ByteArrayToBitmap(loadedHiddenBytes).Save("decoded.png", ImageFormat.Png); | |
CreateMask("innocuous.png", "encoded.png"); | |
} | |
public static void Encode(byte[] hiddenBytes, string inputImageFileName, string outputImageFileName) | |
{ | |
// Loading the data we want to hide to a byte array | |
byte[] hiddenLengthBytes = BitConverter.GetBytes(hiddenBytes.Length); | |
byte[] hiddenCombinedBytes = Util.Combine(hiddenLengthBytes, hiddenBytes); | |
// Loading an innocuous image we want to store the hidden data in to a byte array | |
Image innocuousBmp = Image.FromFile(inputImageFileName); | |
byte[] rgbComponents = Util.RgbComponentsToBytes(innocuousBmp); | |
// Encoding the hidden data into the innocuous image, and storing it to file. | |
byte[] encodedRgbComponents = EncodeBytes(hiddenCombinedBytes, rgbComponents); | |
Bitmap encodedBmp = Util.ByteArrayToBitmap(encodedRgbComponents, innocuousBmp.Width, innocuousBmp.Height); | |
encodedBmp.Save(outputImageFileName, ImageFormat.Png); | |
} | |
private static byte[] EncodeBytes(byte[] hiddenBytes, byte[] innocuousBytes) | |
{ | |
BitArray hiddenBits = new BitArray(hiddenBytes); | |
byte[] encodedBitmapRgbComponents = new byte[innocuousBytes.Length]; | |
for (int i = 0; i < innocuousBytes.Length; i++) | |
{ | |
if (i < hiddenBits.Length) | |
{ | |
byte evenByte = (byte)(innocuousBytes[i] - innocuousBytes[i] % 2); | |
encodedBitmapRgbComponents[i] = (byte)(evenByte + (hiddenBits[i] ? 1 : 0)); | |
} | |
else | |
{ | |
encodedBitmapRgbComponents[i] = innocuousBytes[i]; | |
} | |
} | |
return encodedBitmapRgbComponents; | |
} | |
public static byte[] Decode(string imageFileName) | |
{ | |
// Loading the seemingly innocuous image with hidden data into a byte array | |
Bitmap loadedEncodedBmp = new Bitmap(imageFileName); | |
byte[] loadedEncodedRgbComponents = Util.RgbComponentsToBytes(loadedEncodedBmp); | |
const int bytesInInt = 4; | |
byte[] loadedHiddenLengthBytes = DecodeBytes(loadedEncodedRgbComponents, 0, bytesInInt); | |
int loadedHiddenLength = BitConverter.ToInt32(loadedHiddenLengthBytes, 0); | |
byte[] loadedHiddenBytes = DecodeBytes(loadedEncodedRgbComponents, bytesInInt, loadedHiddenLength); | |
return loadedHiddenBytes; | |
} | |
private static byte[] DecodeBytes(byte[] innocuousLookingData, int byteIndex, int byteCount) | |
{ | |
const int bitsInBytes = 8; | |
int bitCount = byteCount * bitsInBytes; | |
int bitIndex = byteIndex * bitsInBytes; | |
bool[] loadedHiddenBools = new bool[bitCount]; | |
for (int i = 0; i < bitCount; i++) | |
{ | |
loadedHiddenBools[i] = innocuousLookingData[i + bitIndex] % 2 == 1; | |
} | |
BitArray loadedHiddenBits = new BitArray(loadedHiddenBools); | |
byte[] loadedHiddenBytes = new byte[loadedHiddenBits.Length / bitsInBytes]; | |
loadedHiddenBits.CopyTo(loadedHiddenBytes, 0); | |
return loadedHiddenBytes; | |
} | |
public static void CreateMask(string inputImageFileName1, string inputImageFileName2) | |
{ | |
Image image1 = Image.FromFile(inputImageFileName1); | |
Image image2 = Image.FromFile(inputImageFileName2); | |
Bitmap bmp1 = new Bitmap(image1); | |
Bitmap bmp2 = new Bitmap(image2); | |
Bitmap maskDiff = new Bitmap(bmp1); | |
Bitmap maskParity1 = new Bitmap(bmp1); | |
Bitmap maskParity2 = new Bitmap(bmp2); | |
for (int i = 0; i < maskDiff.Height; i++) | |
{ | |
for (int j = 0; j < maskDiff.Width; j++) | |
{ | |
Color px1 = bmp1.GetPixel(j, i); | |
Color px2 = bmp2.GetPixel(j, i); | |
int maskDiffIntensity = 255 - Math.Abs(px2.R - px1.R) * 85 - Math.Abs(px2.G - px1.G) * 85 - Math.Abs(px2.B - px1.B) * 85; | |
maskDiff.SetPixel(j, i, Color.FromArgb(maskDiffIntensity, maskDiffIntensity, maskDiffIntensity)); | |
int maskParityIntensity1 = (px1.R % 2) * 85 + (px1.G % 2) * 85 + (px1.B % 2) * 85; | |
maskParity1.SetPixel(j, i, Color.FromArgb(maskParityIntensity1, maskParityIntensity1, maskParityIntensity1)); | |
int maskParityIntensity2 = (px2.R % 2) * 85 + (px2.G % 2) * 85 + (px2.B % 2) * 85; | |
maskParity2.SetPixel(j, i, Color.FromArgb(maskParityIntensity2, maskParityIntensity2, maskParityIntensity2)); | |
} | |
} | |
maskDiff.Save("maskDiff.png"); | |
maskParity1.Save("maskParity_" + inputImageFileName1); | |
maskParity2.Save("maskParity_" + inputImageFileName2); | |
} | |
} | |
class Util | |
{ | |
public static byte[] BitmapToByteArray(Image img) | |
{ | |
using (MemoryStream ms = new MemoryStream()) | |
{ | |
img.Save(ms, ImageFormat.Png); | |
return ms.ToArray(); | |
} | |
} | |
public static Image ByteArrayToBitmap(byte[] bytes) | |
{ | |
using (MemoryStream ms = new MemoryStream(bytes)) | |
{ | |
return Image.FromStream(ms); | |
} | |
} | |
public static byte[] Combine(byte[] left, byte[] right) | |
{ | |
byte[] combined = new byte[left.Length + right.Length]; | |
Buffer.BlockCopy(left, 0, combined, 0, left.Length); | |
Buffer.BlockCopy(right, 0, combined, left.Length, right.Length); | |
return combined; | |
} | |
public static byte[] RgbComponentsToBytes(Image innocuousImg) | |
{ | |
Bitmap innocuousBmp = new Bitmap(innocuousImg); | |
int counter = 0; | |
byte[] components = new byte[3 * innocuousBmp.Width * innocuousBmp.Height]; | |
for (int y = 0; y < innocuousBmp.Height; y++) | |
{ | |
for (int x = 0; x < innocuousBmp.Width; x++) | |
{ | |
Color c = innocuousBmp.GetPixel(x, y); | |
components[counter++] = c.R; | |
components[counter++] = c.G; | |
components[counter++] = c.B; | |
} | |
} | |
return components; | |
} | |
public static Bitmap ByteArrayToBitmap(byte[] rgbComponents, int width, int hight) | |
{ | |
Queue<byte> rgbComponentQueue = new Queue<byte>(rgbComponents); | |
Bitmap bitmap = new Bitmap(width, hight); | |
for (int y = 0; y < hight; y++) | |
{ | |
for (int x = 0; x < width; x++) | |
{ | |
bitmap.SetPixel(x, y, Color.FromArgb(rgbComponentQueue.Dequeue(), rgbComponentQueue.Dequeue(), rgbComponentQueue.Dequeue())); | |
} | |
} | |
return bitmap; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment