个性化阅读
专注于IT技术分析

使用C#在图像上进行隐写术(隐藏信息)入门

本文概述

隐写术是一种在文件中隐藏文本以产生隐写文本的做法。隐秘术的目的是允许某人秘密地进行交谈, 以使攻击者无法告诉他们的对话是否存在隐藏的含义, 因为没有编程知识的人不会认为图像中隐藏了消息。隐秘术的工作原理是用你自己的数据替换常规计算机文件中未使用的数据。在这种情况下, 数据将为纯文本, 未使用的数据为图像像素中的最低有效位(LSB)。

在本文中, 我们将向你展示如何在C#中隐藏图像文件(JPG, PNG等)中的加密信息。

1.创建所需的帮助程序类

你将需要创建2个类, 并将它们添加到C#项目中。第一个是SteganographyHelper。此类负责隐藏位图上的信息并进行检索。它也有一个帮助程序方法来创建图像版本, 而无需索引像素:

using System;
using System.Drawing;

class SteganographyHelper
{
    enum State
    {
        HIDING, FILL_WITH_ZEROS
    };

    /// <summary>
    /// Creates a bitmap from an image without indexed pixels
    /// </summary>
    /// <param name="src"></param>
    /// <returns></returns>
    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:
                            {
                                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.隐藏和检索图像上的信息

使用第一步的Providen Helper类, 你将可以在图像上隐藏秘密信息, 并在几秒钟内检索它们:

A.隐藏和加密数据

要隐藏文件中的信息, 你需要声明密码。此密码使你以后可以从文件中检索信息。然后将要隐藏在图像上的信息存储在字符串变量中, 该信息也将使用相同的密码进行加密。接下来, 创建两个变量, 这些变量将存储文件的路径(将使用该信息创建的原始文件和新文件), 并继续创建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);

在这种情况下, 我们存储在图像上的信息是一个简单的字符串, 即” …没有人知道我的密码是…”。

B.检索和解密数据

要从上一步创建的图像中检索数据, 你仅需要帮助程序的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);

请注意, 在此实现中, 我们不知道错误或用户界面, 因此你可以实现它。此外, 你可能希望将要包装的代码包装在另一个线程中, 以防止UI冻结。

编码愉快!

赞(0)
未经允许不得转载:srcmini » 使用C#在图像上进行隐写术(隐藏信息)入门

评论 抢沙发

评论前必须登录!