Начало работы со стеганографией (скрыть информацию) на изображениях с C #

Стеганография — это практика сокрытия текста в файлах для создания стеготекста. Цель стеганографии состоит в том, чтобы позволить кому-то тайно общаться таким образом, чтобы злоумышленник не мог сказать, есть ли скрытый смысл их разговора, поскольку никто без знания программирования не будет думать, что в изображениях скрыты сообщения. Стеганография работает, заменяя биты неиспользуемых данных в обычных компьютерных файлах битами ваших собственных данных. В этом случае данные будут представлять собой простой текст, а неиспользуемые данные — это младшие значащие биты (LSB) в пикселях изображения.

В этой статье мы покажем вам, как скрыть зашифрованную информацию в файле изображения (JPG, PNG и т. Д.) В C #.

1. Создайте необходимые вспомогательные классы

Вам нужно будет создать 2 класса и добавить их в ваш проект C #. Первый SteganographyHelper, Этот класс отвечает за сокрытие информации о Bitmap и получить его. У него есть вспомогательный метод для создания версии изображения без индексированных пикселей:

using System;
using System.Drawing;
class SteganographyHelper
{
enum State
{
HIDING,
FILL_WITH_ZEROS
};
///
/// Creates a bitmap from an image without indexed pixels
///
///
///
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 = text.Length)
{
s = State.FILL_WITH_ZEROS;
}
else
{
charValue = text[charIndex++];
}
}
switch (colorUnitIndex % 3)
{
case 0:
{
if (s == State.HIDING)
{
R += charValue % 2;
charValue /= 2;
}
}
break;
case 1:
{
if (s == State.HIDING)
{
G += charValue % 2;
charValue /= 2;
}
}
break;
case 2:
{
if (s == State.HIDING)
{
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;
}
}

Второй является StringCipher учебный класс. С помощью этого класса мы зашифруем информацию, которую вы хотите скрыть в файлах, это, очевидно, повысит защиту ваших секретных данных. Все его методы являются статическими, поэтому вам не нужно будет создавать новый экземпляр каждый раз, когда вы что-то шифруете или дешифруете:

using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
class StringCipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 256;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
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();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((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);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}

2. Скрывать и извлекать информацию на изображении

С помощью вспомогательных классов первого шага вы сможете скрыть свою секретную информацию на изображениях и получить ее в течение нескольких секунд:

А. Скрывать и шифровать данные

Чтобы скрыть информацию о файле, вам нужно будет указать пароль. Этот пароль позволяет вам получить информацию из вашего файла позже. Затем сохраните информацию, которую вы хотите скрыть на изображении, в строковой переменной, она также будет зашифрована с тем же паролем. Далее создайте две переменные, которые будут хранить путь к файлам (исходный и новый файл, который будет создан с информацией), и приступите к созданию нового экземпляра SteganographyHelper,

Зашифруйте данные, используя свой пароль и помощник, это сгенерирует другую строку (ту, которая будет сохранена на изображении). Теперь важно создать версию изображения без индексированных пикселей, используя CreateNonIndexedImage метод помощника. Если вы этого не сделаете, использование алгоритма вызовет (с некоторыми изображениями, а не со всеми) следующее исключение:

SetPixel не поддерживается для изображений с индексированными пиксельными форматами.

С использованием MergeText Метод помощника, скрыть зашифрованный текст в неиндексированной версии изображения и сохранить его там, где вы хотите:

// Declare the password that will allow you to retrieve the encrypted data later
string _PASSWORD = "password";
// The String data to conceal on the image
string _DATA_TO_HIDE = "Hello, no one should know that my password is 12345";
// Declare the path where the original image is located
string pathOriginalImage = @"C:\Users\sdkca\Desktop\image_example.png";
// Declare the new name of the file that will be generated with the hidden information
string pathResultImage = @"C:\Users\sdkca\Desktop\image_example_with_hidden_information.png";
// Create an instance of the SteganographyHelper
SteganographyHelper helper = new SteganographyHelper();
// Encrypt your data to increase security
// Remember: only the encrypted data should be stored on the image
string encryptedData = StringCipher.Encrypt(_DATA_TO_HIDE, _PASSWORD);
// Create an instance of the original image without indexed pixels
Bitmap originalImage = SteganographyHelper.CreateNonIndexedImage(Image.FromFile(pathOriginalImage));
// Conceal the encrypted data on the image !
Bitmap imageWithHiddenData = SteganographyHelper.MergeText(encryptedData, originalImage);
// Save the image with the hidden information somewhere :)
// In this case the generated file will be image_example_with_hidden_information.png
imageWithHiddenData.Save(pathResultImage);

В этом случае информация, хранящаяся на изображении, представляет собой простую строку, а именно «... никто не должен знать, что мой пароль ...».

Б. Получить и расшифровать данные

Чтобы извлечь данные из изображения, созданного на предыдущем шаге, вам потребуется только ExtractText метод помощника. Это вернет зашифрованную информацию, которую вы можете легко расшифровать, используя ранее установленный пароль:

// The password used to hide the information on the previous step
string _PASSWORD = "password";
// The path to the image that contains the hidden information
string pathImageWithHiddenInformation = @"C:\Users\sdkca\Desktop\image_example_with_hidden_information.png";
// Create an instance of the SteganographyHelper
SteganographyHelper helper = new SteganographyHelper();
// Retrieve the encrypted data from the image
string encryptedData = SteganographyHelper.ExtractText(
new Bitmap(
Image.FromFile(pathImageWithHiddenInformation)
)
);
// Decrypt the retrieven data on the image
string decryptedData = StringCipher.Decrypt(encryptedData, _PASSWORD);
// Display the secret text in the console or in a messagebox
// In our case is "Hello, no one should know that my password is 12345"
Console.WriteLine(decryptedData);
//MessageBox.Show(decryptedData);

Обратите внимание, что в этой реализации мы не знаем об ошибках или пользовательском интерфейсе, поэтому вы сами должны его реализовать. Кроме того, вы можете обернуть код для выполнения в другом потоке, чтобы предотвратить зависание вашего пользовательского интерфейса.

Ссылка на основную публикацию
Adblock
detector