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;
}
}
}