Hiding Data in Images Using Steganography in C#
By FoxLearn 2/15/2025 3:09:04 AM 47
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#.
- How to use InputSimulator in C#
- Registering Global Hotkeys in WinForms
- How to implement Sciter in C#
- How to access a SFTP server using SSH.NET in C#
- Current Thread Must Be Set to Single Thread Apartment (STA) Mode
- How to Run a C# WinForms App with Administrator Rights
- How to Identify the Antivirus Software Installed on a PC Using C#
- How to Append a file in C#