using System; using System.Collections.Generic; using System.Drawing; using System.IO; namespace GrimoireTactics.Framework.Security { public static class Steganography { /// /// Encodes the message. /// /// The bitmap. /// The password. /// The message. public static void EncodeMessage(Bitmap bitmap, string password, string message) { Random rand = new Random(HashPassword(password)); HashSet usedPositions = new HashSet(); byte[] bytes = BitConverter.GetBytes(message.Length); for (int i = 0; i < bytes.Length; i++) { EncodeByte(bitmap, rand, bytes[i], usedPositions); } char[] chars = message.ToCharArray(); for (int i = 0; i < chars.Length; i++) { EncodeByte(bitmap, rand, (byte)chars[i], usedPositions); } } /// /// Encodes the byte. /// /// The bm. /// The rand. /// The value. /// The used positions. private static void EncodeByte(Bitmap bm, Random rand, byte value, HashSet usedPositions) { for (int i = 0; i < 8; i++) { int row, col, pix; PickPosition(bm, rand, usedPositions, out row, out col, out pix); Color clr = bm.GetPixel(row, col); byte r = clr.R; byte g = clr.G; byte b = clr.B; int bit = 0; if ((value & 1) == 1) bit = 1; switch (pix) { case 0: r = (byte)((r & 0xFE) | bit); break; case 1: g = (byte)((g & 0xFE) | bit); break; case 2: b = (byte)((b & 0xFE) | bit); break; } clr = Color.FromArgb(clr.A, r, g, b); bm.SetPixel(row, col, clr); value >>= 1; } } /// /// Picks the position. /// /// The bm. /// The rand. /// The used positions. /// The row. /// The col. /// The pix. private static void PickPosition(Bitmap bm, Random rand, HashSet usedPositions, out int row, out int col, out int pix) { for (;;) { row = rand.Next(0, bm.Width); col = rand.Next(0, bm.Height); pix = rand.Next(0, 3); string key = row + "/" + col + "/" + pix; if (!usedPositions.Contains(key)) { usedPositions.Add(key); return; } } } /// /// Hashes the password. /// /// The password. /// private static int HashPassword(string password) { int shift1 = 3; int shift2 = 17; char[] chars = password.ToCharArray(); int value = 0; for (int i = 1; i < password.Length; i++) { int chValue = chars[i]; value ^= (chValue << shift1); value ^= (chValue << shift2); shift1 = (shift1 + 7) % 19; shift2 = (shift2 + 13) % 23; } return value; } /// /// Decodes the message. /// /// The bm. /// The password. /// /// Message length + len + is too big to make sense. Invalid password. public static string DecodeMessage(Bitmap bm, string password) { Random rand = new Random(HashPassword(password)); HashSet usedPositions = new HashSet(); int len = 0; byte[] bytes = BitConverter.GetBytes(len); for (int i = 0; i < bytes.Length; i++) { bytes[i] = DecodeByte(bm, rand, usedPositions); } len = BitConverter.ToInt32(bytes, 0); if (len > 10000) { throw new InvalidDataException( "Message length " + len + " is too big to make sense. Invalid password."); } char[] chars = new char[len]; for (int i = 0; i < chars.Length; i++) { chars[i] = (char)DecodeByte(bm, rand, usedPositions); } return new string(chars); } /// /// Decodes the byte. /// /// The bm. /// The rand. /// The used positions. /// private static byte DecodeByte(Bitmap bm, Random rand, HashSet usedPositions) { byte value = 0; byte valueMask = 1; for (int i = 0; i < 8; i++) { int row, col, pix; PickPosition(bm, rand, usedPositions, out row, out col, out pix); byte colorValue = 0; switch (pix) { case 0: colorValue = bm.GetPixel(row, col).R; break; case 1: colorValue = bm.GetPixel(row, col).G; break; case 2: colorValue = bm.GetPixel(row, col).B; break; } if ((colorValue & 1) == 1) { value = (byte)(value | valueMask); } valueMask <<= 1; } return value; } } }