Hiding Data in Images Using Steganography in C#
By FoxLearn 2/15/2025 3:09:04 AM 203
The goal of steganography is to facilitate covert communication, where an observer would not realize that any hidden information exists. This is achieved by subtly replacing the least significant bits (LSBs) in image pixels with your own data. By doing so, the image looks unchanged to the naked eye, while containing hidden messages.
In this tutorial, we'll demonstrate how to hide encrypted data in an image file (such as JPG, PNG, etc.) using C#.
To get started, you'll need to create two helper classes and add them to your C# project.
1. SteganographyHelper Class
This class is responsible for hiding and extracting information from Bitmap images. It also includes a method to create images without indexed pixels, which is required for some image formats.
using System; using System.Drawing; class SteganographyHelper { enum State { HIDING, FILL_WITH_ZEROS }; public static Bitmap CreateNonIndexedImage(Image src) { Bitmap newBmp = new Bitmap(src.Width, src.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); using (Graphics gfx = Graphics.FromImage(newBmp)) { gfx.DrawImage(src, 0, 0); } return newBmp; } public static Bitmap MergeText(string text, Bitmap bmp) { State s = State.HIDING; int charIndex = 0; int charValue = 0; long colorUnitIndex = 0; int zeros = 0; int R = 0, G = 0, B = 0; for (int i = 0; i < bmp.Height; i++) { for (int j = 0; j < bmp.Width; j++) { Color pixel = bmp.GetPixel(j, i); pixel = Color.FromArgb(pixel.R - pixel.R % 2, pixel.G - pixel.G % 2, pixel.B - pixel.B % 2); R = pixel.R; G = pixel.G; B = pixel.B; for (int n = 0; n < 3; n++) { if (colorUnitIndex % 8 == 0) { if (zeros == 8) { if ((colorUnitIndex - 1) % 3 < 2) { bmp.SetPixel(j, i, Color.FromArgb(R, G, B)); } return bmp; } if (charIndex >= text.Length) { s = State.FILL_WITH_ZEROS; } else { charValue = text[charIndex++]; } } switch (colorUnitIndex % 3) { case 0: R += charValue % 2; charValue /= 2; break; case 1: G += charValue % 2; charValue /= 2; break; case 2: B += charValue % 2; charValue /= 2; bmp.SetPixel(j, i, Color.FromArgb(R, G, B)); break; } colorUnitIndex++; if (s == State.FILL_WITH_ZEROS) { zeros++; } } } } return bmp; } public static string ExtractText(Bitmap bmp) { int colorUnitIndex = 0; int charValue = 0; string extractedText = String.Empty; for (int i = 0; i < bmp.Height; i++) { for (int j = 0; j < bmp.Width; j++) { Color pixel = bmp.GetPixel(j, i); for (int n = 0; n < 3; n++) { switch (colorUnitIndex % 3) { case 0: charValue = charValue * 2 + pixel.R % 2; break; case 1: charValue = charValue * 2 + pixel.G % 2; break; case 2: charValue = charValue * 2 + pixel.B % 2; break; } colorUnitIndex++; if (colorUnitIndex % 8 == 0) { charValue = reverseBits(charValue); if (charValue == 0) { return extractedText; } char c = (char)charValue; extractedText += c.ToString(); } } } } return extractedText; } public static int reverseBits(int n) { int result = 0; for (int i = 0; i < 8; i++) { result = result * 2 + n % 2; n /= 2; } return result; } }
2. StringCipher Class
This class will be used to encrypt and decrypt the hidden information, adding an extra layer of security.
using System; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; class StringCipher { private const int Keysize = 256; private const int DerivationIterations = 1000; public static string Encrypt(string plainText, string passPhrase) { var saltStringBytes = Generate256BitsOfRandomEntropy(); var ivStringBytes = Generate256BitsOfRandomEntropy(); var plainTextBytes = Encoding.UTF8.GetBytes(plainText); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = new RijndaelManaged()) { symmetricKey.BlockSize = 256; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream()) { using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) { cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); cryptoStream.FlushFinalBlock(); var cipherTextBytes = saltStringBytes.Concat(ivStringBytes).Concat(memoryStream.ToArray()).ToArray(); return Convert.ToBase64String(cipherTextBytes); } } } } } } public static string Decrypt(string cipherText, string passPhrase) { var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).ToArray(); using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) { var keyBytes = password.GetBytes(Keysize / 8); using (var symmetricKey = new RijndaelManaged()) { symmetricKey.BlockSize = 256; symmetricKey.Mode = CipherMode.CBC; symmetricKey.Padding = PaddingMode.PKCS7; using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) { using (var memoryStream = new MemoryStream(cipherTextBytes)) { using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) { var plainTextBytes = new byte[cipherTextBytes.Length]; var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); } } } } } } private static byte[] Generate256BitsOfRandomEntropy() { var randomBytes = new byte[32]; using (var rngCsp = new RNGCryptoServiceProvider()) { rngCsp.GetBytes(randomBytes); } return randomBytes; } }
Now that you have the helper classes set up, follow the steps below to hide and extract information in images.
Conceal and Encrypt Data
First, declare a password for encryption. Then, encrypt the data you want to hide, such as a string. After that, you'll need to create a version of the image without indexed pixels and use the MergeText
method to hide the encrypted data.
string _PASSWORD = "password"; string _DATA_TO_HIDE = "Hello, no one should know that my password is 12345"; string pathOriginalImage = @"C:\path\to\image.png"; string pathResultImage = @"C:\path\to\image_with_hidden_data.png"; // Create an instance of SteganographyHelper SteganographyHelper helper = new SteganographyHelper(); // Encrypt your data string encryptedData = StringCipher.Encrypt(_DATA_TO_HIDE, _PASSWORD); // Create a non-indexed version of the original image Bitmap originalImage = SteganographyHelper.CreateNonIndexedImage(Image.FromFile(pathOriginalImage)); // Hide the encrypted data in the image Bitmap imageWithHiddenData = SteganographyHelper.MergeText(encryptedData, originalImage); // Save the new image with hidden data imageWithHiddenData.Save(pathResultImage);
Retrieve and Decrypt Data
To extract and decrypt the hidden data, simply use the ExtractText
method and then decrypt the result.
string _PASSWORD = "password"; string pathImageWithHiddenInformation = @"C:\path\to\image_with_hidden_data.png"; SteganographyHelper helper = new SteganographyHelper(); // Extract the encrypted data string encryptedData = SteganographyHelper.ExtractText(new Bitmap(Image.FromFile(pathImageWithHiddenInformation))); // Decrypt the extracted data string decryptedData = StringCipher.Decrypt(encryptedData, _PASSWORD); // Display the secret message Console.WriteLine(decryptedData);
By following this guide, you can securely embed and retrieve hidden messages within image files using C#.
- Primitive types in C#
- How to set permissions for a directory in C#
- How to Convert Int to Byte Array in C#
- How to Convert string list to int list in C#
- How to convert timestamp to date in C#
- How to Get all files in a folder in C#
- How to use Channel as an async queue in C#
- Case sensitivity in JSON deserialization