|
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.IO;
namespace SteganoFTP.Common
{
public class Stego
{
public const int BUFFER_SIZE = 72; //2048;
private Collection<byte> hide_waitingBuffer;
private Collection<byte> extract_waitingBuffer;
private int hide_blockSize;
private int extract_blockSize;
private int hide_CurrentMessageIndex;
private byte[] hide_Message; // overall message
private byte[] hide_MessagePart; // partial message fitting into current transmission
private long hide_ExpectedStreamSize;
private int extract_RemainingMessageSize;
public long ExpectedStreamSize
{
get { return hide_ExpectedStreamSize; }
set
{
this.hide_ExpectedStreamSize = value;
if (hide_ExpectedStreamSize > -1)
{
// break the transmission into one block per message byte
if (hide_ExpectedStreamSize < hide_Message.Length)
{ // Message is longer than transmission.
// send only [buffer.Length] message bytes
// and wait for the next transmission
hide_MessagePart = new byte[hide_ExpectedStreamSize];
}
else
{ // Message is shorter than transmission.
// send the whole text
hide_MessagePart = new byte[hide_Message.Length];
}
this.hide_blockSize = (int)Math.Floor((float)hide_ExpectedStreamSize / hide_MessagePart.Length);
Array.Copy(hide_Message, 0, hide_MessagePart, 0, hide_MessagePart.Length);
}
}
}
public bool Hide_HasWaitingBuffer
{
get
{
return this.hide_waitingBuffer.Count > 0;
}
}
public bool Extract_HasWaitingBuffer
{
get
{
return this.extract_waitingBuffer.Count > 0;
}
}
public byte[] Hide_WaitingBuffer
{
get
{
byte[] buffer = new byte[this.hide_waitingBuffer.Count];
this.hide_waitingBuffer.CopyTo(buffer, 0);
return buffer;
}
}
public byte[] Extract_WaitingBuffer
{
get
{
byte[] buffer = new byte[this.extract_waitingBuffer.Count];
this.extract_waitingBuffer.CopyTo(buffer, 0);
return buffer;
}
}
public Stego(byte[] messageToSend)
{
Initialize();
this.hide_Message = messageToSend;
}
public Stego()
{
Initialize();
this.hide_Message = new byte[0];
}
private void Initialize()
{
this.hide_waitingBuffer = new Collection<byte>();
this.hide_blockSize = -1;
this.hide_CurrentMessageIndex = 0;
this.hide_ExpectedStreamSize = -1;
this.extract_waitingBuffer = new Collection<byte>();
this.extract_blockSize = -1;
this.extract_RemainingMessageSize = -1;
}
public long CalculateSizeAfterHiding(long sizeBeforeHiding)
{
long sizeAfterHiding;
if (sizeBeforeHiding < hide_Message.Length)
{ // Message is longer than transmission.
// send only [buffer.Length] message bytes
// and wait for the next transmission
sizeAfterHiding = sizeBeforeHiding * 2;
}
else
{ // Message is shorter than transmission.
// send the whole file
sizeAfterHiding = sizeBeforeHiding + hide_Message.Length;
}
// every streams contains 8 bytes for block/message size
sizeAfterHiding += 8;
return sizeAfterHiding;
}
/// <summary>
/// Inserts data from a file into a binary buffer.
/// </summary>
/// <param name="buffer">Buffer.</param>
/// <param name="size">Size of the buffer.</param>
/// <returns>Buffer with hidden data.</returns>
public byte[] Hide(byte[] buffer, int size, long remainingStreamSize)
{
if (hide_ExpectedStreamSize < 0)
{ // Up-/Download size is not known yet.
// queue the buffer and wait
foreach (byte b in buffer)
{
hide_waitingBuffer.Add(b);
}
return null;
}
byte[] returnArray = null;
// copy buffers to collection
Collection<byte> transmissionBuffer = new Collection<byte>();
foreach (byte b in hide_waitingBuffer)
{
transmissionBuffer.Add(b);
}
for (int n = 0; n < size; n++)
{
transmissionBuffer.Add(buffer[n]);
}
if (transmissionBuffer.Count < hide_blockSize)
{ // The buffer is too small for a block.
// wait until the socket received more data
hide_waitingBuffer = transmissionBuffer;
}
else
{
hide_waitingBuffer.Clear();
// transmission must be an integer number of blocks
int countBlocksInCurrentBuffer = (int)Math.Floor((float)transmissionBuffer.Count / hide_blockSize);
int maxTransmissionSize = countBlocksInCurrentBuffer * hide_blockSize;
// move the unused rest of the current buffer to the waiting collection
MoveEndToCollection(transmissionBuffer, hide_waitingBuffer, maxTransmissionSize);
int insertAtIndex = 0;
if (hide_CurrentMessageIndex == 0)
{ // sending first buffer
// append [blockSize] and [messageLength] to buffer
PrependInt32(hide_blockSize, transmissionBuffer);
PrependInt32(hide_MessagePart.Length, transmissionBuffer);
insertAtIndex = 8;
}
// insert message into carrier
for(int blockIndex=0; blockIndex<countBlocksInCurrentBuffer; blockIndex++)
{
if (hide_CurrentMessageIndex == hide_MessagePart.Length)
{ // end of message
break;
}
transmissionBuffer.Insert(
insertAtIndex,
hide_Message[hide_CurrentMessageIndex]);
// proceed with next part
hide_CurrentMessageIndex++;
// each insertion adds 1 byte, so the next insert position is '+n'
insertAtIndex += hide_blockSize + 1;
}
// return new transmission buffer
byte[] newBuffer = new byte[transmissionBuffer.Count];
transmissionBuffer.CopyTo(newBuffer, 0);
returnArray = newBuffer;
}
return returnArray;
}
/// <summary>
/// Removes hidden data from a binary buffer and stores it in a file.
/// </summary>
/// <param name="buffer">Buffer.</param>
/// <param name="size">Size of the buffer.</param>
/// <returns>Buffer without the extracted bytes.</returns>
public byte[] Extract(byte[] buffer, int size, Stream outMessage)
{
byte[] returnArray = null;
// copy buffers to collection
Collection<byte> transmissionBuffer = new Collection<byte>();
foreach (byte b in extract_waitingBuffer)
{
transmissionBuffer.Add(b);
}
for (int n = 0; n < size; n++)
{
transmissionBuffer.Add(buffer[n]);
}
if (extract_blockSize < 0 && transmissionBuffer.Count >= 8)
{ // The first 8 octets arrived.
// read/remove block and message size
this.extract_blockSize = RemoveInt32(transmissionBuffer, 7);
this.extract_RemainingMessageSize = RemoveInt32(transmissionBuffer, 3);
}
if (transmissionBuffer.Count < extract_blockSize || extract_blockSize < 0)
{ // The buffer is too small for a block.
// wait until the socket received more data
extract_waitingBuffer = transmissionBuffer;
}
else
{
extract_waitingBuffer.Clear();
// transmission must be an integer number of blocks
int countBlocksInCurrentBuffer = (int)Math.Floor((float)(transmissionBuffer.Count / (extract_blockSize+1)));
int maxTransmissionSize = countBlocksInCurrentBuffer * (extract_blockSize+1);
// move the unused rest of the current buffer to the waiting collection
MoveEndToCollection(transmissionBuffer, extract_waitingBuffer, maxTransmissionSize);
// read and remove message from carrier
int transmissionBufferIndex = 0;
while (extract_RemainingMessageSize > 0 && transmissionBufferIndex < transmissionBuffer.Count)
{
outMessage.WriteByte(transmissionBuffer[transmissionBufferIndex]);
transmissionBuffer.RemoveAt(transmissionBufferIndex);
transmissionBufferIndex += extract_blockSize;
extract_RemainingMessageSize--;
}
// return new transmission buffer
byte[] newBuffer = new byte[transmissionBuffer.Count];
transmissionBuffer.CopyTo(newBuffer, 0);
returnArray = newBuffer;
}
return returnArray;
}
private void MoveEndToCollection(Collection<byte> source, Collection<byte> destination, int sourceMaxSize)
{
while (source.Count > sourceMaxSize)
{
destination.Add(source[sourceMaxSize]);
source.RemoveAt(sourceMaxSize);
}
}
private void PrependInt32(int value, Collection<byte> buffer)
{
int restValue = value;
// split value into octets
byte value1 = (byte)(restValue >> 24);
restValue -= value1 << 24;
byte value2 = (byte)(restValue >> 16);
restValue -= value2 << 16;
byte value3 = (byte)(restValue >> 8);
byte value4 = (byte)(restValue - (value3 << 8));
// append value to buffer
buffer.Insert(0, value1);
buffer.Insert(0, value2);
buffer.Insert(0, value3);
buffer.Insert(0, value4);
}
private int RemoveInt32(Collection<byte> buffer, int offset)
{ // get value from 4 octets
int value = 0;
for (int n = 3; n > -1; n--)
{
value += buffer[offset] << (n*8);
buffer.RemoveAt(offset);
offset--;
}
return value;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.