/* This class has been written by
* Corinna John (Hannover, Germany)
* picturekey@binary-universe.net
*
* You can use the code in any context you like,
* as long as you do not delete this comment.
*
* Please send me a little feedback about what you're
* using this code for and what changes you'd like to
* see in later versions. (And please excuse my bad english)
*
* WARNING: This is experimental code.
* Some bugs and flaws have been left in there,
* to keep the code readable to people who want
* to understand the algorithm.
* Please do not expect "Release Quality".
* */
using System;
using System.IO;
namespace SteganoDemo
{
/// <summary>Hides/extracts data in/from a wave stream</summary>
public class WaveUtility : FileUtility
{
/// <summary>Format of the source file</summary>
private WaveFormat carrierFormat;
/// <summary>Position of the "data" chunk in the source file</summary>
private long dataPosition;
/// <summary>
/// The read-only stream.
/// Clean wave for hiding,
/// Carrier wave for extracting
/// </summary>
private Stream sourceStream;
/// <summary>Stream to receive the edited carrier wave</summary>
private Stream destinationStream;
/// <summary>bits per sample / 8</summary>
private int bytesPerSample;
public override void Initialize(CarrierFile carrierFile){
base.Initialize(carrierFile);
ReadHeader();
}
public override long CountUnits(){
return carrierFile.CountCarrierUnits;
}
private string ReadChunk(BinaryReader reader) {
byte[] ch = new byte[4];
reader.Read(ch, 0, ch.Length);
return System.Text.Encoding.ASCII.GetString(ch);
}
/// <summary>ReadChunk(reader) - Changed to CopyChunk(reader, writer)</summary>
/// <param name="reader">source stream</param>
/// <returns>four characters</returns>
private string CopyChunk(BinaryReader reader, BinaryWriter writer) {
byte[] ch = new byte[4];
reader.Read(ch, 0, ch.Length);
//copy the chunk
writer.Write(ch);
return System.Text.Encoding.ASCII.GetString(ch);
}
private void ReadHeader() {
Stream sourceStream;
if(carrierFile.SourceStream == null){
sourceStream = new FileStream(carrierFile.SourceFileName, FileMode.Open, FileAccess.Read);
}else{
sourceStream = carrierFile.SourceStream;
carrierFile.SourceStream.Seek(0, SeekOrigin.Begin);
}
BinaryReader reader = new BinaryReader(sourceStream);
if (ReadChunk(reader) != "RIFF")
throw new Exception("Invalid file format");
reader.ReadInt32(); // File length minus first 8 bytes of RIFF description, we don't use it
if (ReadChunk(reader) != "WAVE")
throw new Exception("Invalid file format");
if (ReadChunk(reader) != "fmt ")
throw new Exception("Invalid file format");
int len = reader.ReadInt32();
if (len < 16) // bad format chunk length
throw new Exception("Invalid file format");
carrierFormat = new WaveFormat(22050, 16, 2); // initialize to any format
carrierFormat.wFormatTag = reader.ReadInt16();
carrierFormat.nChannels = reader.ReadInt16();
carrierFormat.nSamplesPerSec = reader.ReadInt32();
carrierFormat.nAvgBytesPerSec = reader.ReadInt32();
carrierFormat.nBlockAlign = reader.ReadInt16();
carrierFormat.wBitsPerSample = reader.ReadInt16();
// advance in the stream to skip the wave format block
len -= 16; // minimum format size
while (len > 0) {
reader.ReadByte();
len--;
}
// assume the data chunk is aligned
String chunk;
do{
chunk = ReadChunk(reader);
}while(sourceStream.Position < sourceStream.Length && chunk != "data");
if (sourceStream.Position >= sourceStream.Length)
throw new Exception("Invalid file format");
//read length of the data chunk
//carrierFile.CountCarrierUnits = reader.ReadInt32() / carrierFormat.wBitsPerSample / 8;
int dataLength = reader.ReadInt32();
carrierFile.CountCarrierUnits = dataLength / (carrierFormat.wBitsPerSample / 8);
this.dataPosition = sourceStream.Position;
this.bytesPerSample = carrierFormat.wBitsPerSample / 8;
}
/// <summary>Copy the header from [sourceStream] to [destinationStream]</summary>
private void CopyHeader() {
BinaryReader reader = new BinaryReader(sourceStream);
BinaryWriter writer = new BinaryWriter(destinationStream);
byte[] buffer = new byte[16];
//copy "RIFF"+length+"WAVEfmt "
reader.Read(buffer, 0, 16);
writer.Write(buffer);
Int32 len = reader.ReadInt32();
writer.Write(len);
//copy format
reader.Read(buffer, 0, 16);
writer.Write(buffer);
// copy wave format block
len -= 16; // minimum format size
writer.Write( reader.ReadBytes(len) );
// assume the data chunk is aligned
while(sourceStream.Position < sourceStream.Length && CopyChunk(reader, writer) != "data")
;
//copy length of data
writer.Write( reader.ReadInt32() );
}
/// <summary>Write a new header and add wave data</summary>
public static Stream CreateStream(WaveFormat format, Stream waveData) {
MemoryStream stream = new MemoryStream();
BinaryWriter writer = new BinaryWriter(stream);
//write RIFF header
writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF".ToCharArray()));
writer.Write((Int32)(waveData.Length + 36)); //File length minus first 8 bytes of RIFF description
writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt ".ToCharArray()));
writer.Write((Int32)16); //length of following chunk: 16
//write format
writer.Write((Int16)format.wFormatTag);
writer.Write((Int16)format.nChannels);
writer.Write((Int32)format.nSamplesPerSec);
writer.Write((Int32)format.nAvgBytesPerSec);
writer.Write((Int16)format.nBlockAlign);
writer.Write((Int16)format.wBitsPerSample);
writer.Write(System.Text.Encoding.ASCII.GetBytes("data".ToCharArray()));
//write wave data
writer.Write((Int32)waveData.Length);
waveData.Seek(0, SeekOrigin.Begin);
byte[] b = new byte[waveData.Length];
waveData.Read(b, 0, (int)waveData.Length);
writer.Write(b);
writer.Seek(0, SeekOrigin.Begin);
return stream;
}
private void CopyBitsToSample(int bitsPerUnit, byte messageByte, ref int messageBitIndex, ref byte waveByte){
for(int carrierBitIndex=0; carrierBitIndex<bitsPerUnit; carrierBitIndex++){
SetBit(messageBitIndex, messageByte, carrierBitIndex, ref waveByte);
messageBitIndex++;
}
}
/// <summary>
/// Hide [messageStream] in [sourceStream],
/// write the result to [destinationStream]
/// </summary>
/// <param name="messageStream">The message to hide</param>
/// <param name="keyStream">
/// A key stream that specifies how many samples shall be
/// left clean between two changed samples
/// </param>
public override void Hide(Stream messageStream, Stream keyStream){
byte[] waveBuffer = new byte[bytesPerSample];
byte[] skipBuffer;
byte message;
int messageBuffer; //receives the next byte of the message or -1
int keyByte; //distance of the next carrier sample
int bitsPerUnit; //how many bits to hide in each sample
//carrierFile.CountBitsToHidePerCarrierUnit has not been hidden yet
bool isCountBitsPerUnitHidden = false;
if(carrierFile.SourceStream == null){
//copy source file
FileStream sourceFileStream = new FileStream(carrierFile.SourceFileName, FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[sourceFileStream.Length];
sourceFileStream.Read(buffer, 0, buffer.Length);
sourceStream = new MemoryStream(buffer);
sourceFileStream.Close();
}else{
sourceStream = carrierFile.SourceStream;
carrierFile.SourceStream.Seek(0, SeekOrigin.Begin);
}
//open destination file
destinationStream = new FileStream(carrierFile.DestinationFileName, FileMode.Create, FileAccess.Write);
CopyHeader();
if(carrierFile.NoisePercent > 0){ //add nonsense
long dataPosition = sourceStream.Position;
Random random = new Random();
byte[] noiseByte = new byte[1];
int maxIndexSamples = (int)((sourceStream.Length / bytesPerSample)-1);
int countNoisySamples = (int)(maxIndexSamples * ((float)carrierFile.NoisePercent/100));
int samplePostition;
for(int n=0; n<countNoisySamples; n++){
//get clean sample
samplePostition = random.Next(0, maxIndexSamples) * bytesPerSample;
sourceStream.Seek(samplePostition, SeekOrigin.Begin);
sourceStream.Read(waveBuffer, 0, bytesPerSample);
//add noise to the sample
random.NextBytes(noiseByte);
for(int messageBitIndex=0; messageBitIndex<8; ){
CopyBitsToSample(carrierFile.CountBitsToHidePerCarrierUnit,
noiseByte[0],
ref messageBitIndex,
ref waveBuffer[bytesPerSample-1]);
sourceStream.Seek(samplePostition, SeekOrigin.Begin);
sourceStream.Write(waveBuffer, 0, bytesPerSample);
}
}
sourceStream.Seek(dataPosition,SeekOrigin.Begin);
}
// ----------------------------------------- Hide the Message
long streamLength = messageStream.Length;
while(messageStream.Position < streamLength){
if(isCountBitsPerUnitHidden){
//read one byte of the message stream
messageBuffer = messageStream.ReadByte();
message = (byte)messageBuffer;
bitsPerUnit = carrierFile.CountBitsToHidePerCarrierUnit;
}else{
message = (byte)carrierFile.CountBitsToHidePerCarrierUnit;
//use default bitcount to hide the correct bitcount
bitsPerUnit = new WaveFileType().MinBitsPerCarrierUnit;
}
//for each bit in message
for(int messageBitIndex=0; messageBitIndex<8; ){
//read a byte from the key
keyByte = GetKey(keyStream);
//skip a couple of samples
skipBuffer = new byte[keyByte * bytesPerSample];
sourceStream.Read(skipBuffer, 0, skipBuffer.Length);
destinationStream.Write(skipBuffer, 0, skipBuffer.Length);
//read one sample from the wave stream
sourceStream.Read(waveBuffer, 0, waveBuffer.Length);
CopyBitsToSample(bitsPerUnit, message, ref messageBitIndex, ref waveBuffer[bytesPerSample-1]);
//write the result to destinationStream
destinationStream.Write(waveBuffer, 0, bytesPerSample);
}
isCountBitsPerUnitHidden = true;
}
//copy the rest of the wave without changes
waveBuffer = new byte[sourceStream.Length - sourceStream.Position];
sourceStream.Read(waveBuffer, 0, waveBuffer.Length);
destinationStream.Write(waveBuffer, 0, waveBuffer.Length);
sourceStream.Close();
destinationStream.Close();
}
/// <summary>Extract a message from [sourceStream] into [messageStream]</summary>
/// <param name="messageStream">Empty stream to receive the extracted message</param>
/// <param name="keyStream">
/// A key stream that specifies how many samples shall be
/// skipped between two carrier samples
/// </param>
public override void Extract(Stream messageStream, Stream keyStream){
byte[] waveBuffer = new byte[bytesPerSample];
byte message, waveByte;
int keyByte; //distance of the next carrier sample
int bitsPerUnit; //how many bits are hidden in each sample
//carrierFile.CountBitsToHidePerCarrierUnit has not been read yet
bool isCountBitsPerUnitExtracted = false;
sourceStream = new FileStream(carrierFile.SourceFileName, FileMode.Open, FileAccess.Read);
sourceStream.Seek(dataPosition, SeekOrigin.Begin);
//for(int n=0; (carrierFile.CountBytesToHide==0 || n<=carrierFile.CountBytesToHide); n++){
long count = 0; //carrierFile.CountBytesToHide;
for(; (count>=0 || carrierFile.CountBytesToHide==0); count--){
message = 0; //clear the message-byte
if(isCountBitsPerUnitExtracted){
bitsPerUnit = carrierFile.CountBitsToHidePerCarrierUnit;
}else{
//use default bitcount to get the correct bitcount
bitsPerUnit = new WaveFileType().MinBitsPerCarrierUnit;
}
//for each bit in message
for(int messageBitIndex=0; messageBitIndex<8; ){ //messageBitIndex++){
//read a byte from the key
keyByte = GetKey(keyStream);
//skip a couple of samples
for(int skippedSamples=0; skippedSamples<keyByte; skippedSamples++){
//read one sample from the wave stream
sourceStream.Read(waveBuffer, 0, waveBuffer.Length);
}
//read one sample from the wave stream
sourceStream.Read(waveBuffer, 0, waveBuffer.Length);
waveByte = waveBuffer[bytesPerSample-1];
for(int carrierBitIndex=0; carrierBitIndex<bitsPerUnit; carrierBitIndex++){
AddBit(messageBitIndex, ref message, carrierBitIndex, waveByte);
messageBitIndex++;
}
}
if(! isCountBitsPerUnitExtracted){
carrierFile.CountBitsToHidePerCarrierUnit = message;
isCountBitsPerUnitExtracted = true;
}else{
//add the re-constructed byte to the message
messageStream.WriteByte(message);
if(carrierFile.CountBytesToHide==0 && messageStream.Length==4){
//first Int32 contains the message's length
messageStream.Seek(0, SeekOrigin.Begin);
carrierFile.CountBytesToHide = new BinaryReader(messageStream).ReadInt32();
count = carrierFile.CountBytesToHide;
messageStream.Seek(0, SeekOrigin.Begin);
messageStream.SetLength(0);
}
}
}
sourceStream.Close();
}
public override FileType GetFileType(){
return new WaveFileType();
}
}
}