commit 3b0338e663113cf55a9fea73685cc7776233428d Author: Fergal Moran Date: Thu Mar 23 17:08:57 2017 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..12818cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,254 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +.vscode/ \ No newline at end of file diff --git a/AddFingerprintCommand.cs b/AddFingerprintCommand.cs new file mode 100644 index 0000000..abae9ce --- /dev/null +++ b/AddFingerprintCommand.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.CommandLineUtils; +using System; + +namespace StegoPrint { + public class AddFingerprintCommand : CommandLineApplication { + CommandOption _message; + CommandOption _keyfile; + CommandOption _input; + CommandOption _output; + + public AddFingerprintCommand() { + Name = "add"; + Description = "Adds a fingerprint to an audio file"; + + _message = Option("-$|-m |--message ", "The message to embed in the file", CommandOptionType.SingleValue); + _keyfile = Option("-$|-k |--keyfile ", "The keyfile for the fingerprint", CommandOptionType.SingleValue); + _input = Option("-$|-i |--input ", "The input audio file to process", CommandOptionType.SingleValue); + _output = Option("-$|-o |--output ", "The output file", CommandOptionType.SingleValue); + + HelpOption("-h | -? | --help"); + OnExecute((Func)RunCommand); + } + + private int RunCommand() { + new Fingerprinter().AddMessage(_message.Value(), _keyfile.Value(), _input.Value(), _output.Value()); + return -1; + } + } +} diff --git a/App.cs b/App.cs new file mode 100644 index 0000000..9180bfd --- /dev/null +++ b/App.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.CommandLineUtils; +using System; + +namespace StegoPrint { + class App : CommandLineApplication { + public App() { + Commands.Add(new AddFingerprintCommand()); + Commands.Add(new ExtractFingerprintCommand()); + } + } +} diff --git a/ExtractFingerprintCommand.cs b/ExtractFingerprintCommand.cs new file mode 100644 index 0000000..cea58d6 --- /dev/null +++ b/ExtractFingerprintCommand.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.CommandLineUtils; +using System; + +namespace StegoPrint { + public class ExtractFingerprintCommand : CommandLineApplication { + CommandOption _keyfile; + CommandOption _input; + public ExtractFingerprintCommand() { + Name = "extract"; + Description = "Extracts a fingerprint from a file"; + + _keyfile = Option("-$|-k |--keyfile ", "The keyfile for the fingerprint", CommandOptionType.SingleValue); + _input = Option("-$|-i |--input ", "The input audio file to process", CommandOptionType.SingleValue); + + HelpOption("-h | -? | --help"); + OnExecute((Func)RunCommand); + } + + private int RunCommand() { + new Fingerprinter().ExtractMessage(_keyfile.Value(), _input.Value()); + return -1; + } + } +} diff --git a/Fingerprinter.cs b/Fingerprinter.cs new file mode 100644 index 0000000..c3303ad --- /dev/null +++ b/Fingerprinter.cs @@ -0,0 +1,101 @@ +using System; +using System.IO; +using System.Text; + +namespace StegoPrint { + public class Fingerprinter { + private Stream GetMessageStream(string message) { + BinaryWriter messageWriter = new BinaryWriter(new MemoryStream()); + messageWriter.Write(message.Length); + messageWriter.Write(Encoding.ASCII.GetBytes(message)); + messageWriter.Seek(0, SeekOrigin.Begin); + return messageWriter.BaseStream; + } + + public void AddMessage(string message, string keyFile, string inputFile, string outputFile) { + Stream sourceStream = null; + FileStream destinationStream = null; + WaveStream audioStream = null; + + //create a stream that contains the message, preceeded by its length + Stream messageStream = GetMessageStream(message); + //open the key file + Stream keyStream = new FileStream(keyFile, FileMode.Open); + + try { + + //how man samples do we need? + long countSamplesRequired = WaveUtility.CheckKeyForMessage(keyStream, messageStream.Length); + + if (countSamplesRequired > Int32.MaxValue) { + throw new Exception("Message too long, or bad key! This message/key combination requires " + countSamplesRequired + " samples, only " + Int32.MaxValue + " samples are allowed."); + } + + sourceStream = new FileStream(inputFile, FileMode.Open); + + //create an empty file for the carrier wave + destinationStream = new FileStream(outputFile, FileMode.Create); + + //copy the carrier file's header + audioStream = new WaveStream(sourceStream, destinationStream); + if (audioStream.Length <= 0) { + throw new Exception("Invalid WAV file"); + } + + //are there enough samples in the carrier wave? + if (countSamplesRequired > audioStream.CountSamples) { + String errorReport = "The carrier file is too small for this message and key!\r\n" + + "Samples available: " + audioStream.CountSamples + "\r\n" + + "Samples needed: " + countSamplesRequired; + throw new Exception(errorReport); + } + + //hide the message + WaveUtility utility = new WaveUtility(audioStream, destinationStream); + utility.Hide(messageStream, keyStream); + } catch (Exception ex) { + Console.WriteLine($"Error: {ex.Message}"); + } finally { + if (keyStream != null) { keyStream.Dispose(); } + if (messageStream != null) { messageStream.Dispose(); } + if (audioStream != null) { audioStream.Dispose(); } + if (sourceStream != null) { sourceStream.Dispose(); } + if (destinationStream != null) { destinationStream.Dispose(); } + } + } + public void ExtractMessage(string keyFile, string inputFile) { + FileStream sourceStream = null; + WaveStream audioStream = null; + //create an empty stream to receive the extracted message + MemoryStream messageStream = new MemoryStream(); + //open the key file + Stream keyStream = new FileStream(keyFile, FileMode.Open); + + try { + //open the carrier file + sourceStream = new FileStream(inputFile, FileMode.Open); + audioStream = new WaveStream(sourceStream); + WaveUtility utility = new WaveUtility(audioStream); + + //exctract the message from the carrier wave + utility.Extract(messageStream, keyStream); + + messageStream.Seek(0, SeekOrigin.Begin); + string extractedMessage = new StreamReader(messageStream).ReadToEnd(); + + Console.WriteLine("Message is....."); + Console.WriteLine(extractedMessage); + + Console.ReadKey(); + } catch (Exception ex) { + Console.WriteLine($"Error extracting message {ex.Message}"); + } finally { + if (keyStream != null) { keyStream.Dispose(); } + if (messageStream != null) { messageStream.Dispose(); } + if (audioStream != null) { audioStream.Dispose(); } + if (sourceStream != null) { sourceStream.Dispose(); } + } + } + + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..490bdec --- /dev/null +++ b/Program.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.CommandLineUtils; +using System; +using System.Collections.Generic; + +namespace StegoPrint { + class Program { + static int Main(string[] args) { + try { + return new App().Execute(args); + } catch (Exception ex) { + Console.Write(ex); + return 1; + } + } + } +} \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..99fe029 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "stegoprint": { + "commandName": "Project", + "commandLineArgs": "extract -k \"C:\\dev\\working\\stegano\\mylib\\keyfile.txt\" -i \"C:\\dev\\working\\stegano\\mylib\\out.wav\"" + } + } +} \ No newline at end of file diff --git a/WaveFormat.cs b/WaveFormat.cs new file mode 100644 index 0000000..794c1e2 --- /dev/null +++ b/WaveFormat.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace StegoPrint { + public enum WaveFormats { + Pcm = 1, + Float = 3 + } + [StructLayout (LayoutKind.Sequential)] + public class WaveFormat { + public short wFormatTag; + public short nChannels; + public int nSamplesPerSec; + public int nAvgBytesPerSec; + public short nBlockAlign; + public short wBitsPerSample; + public short cbSize; + + public WaveFormat (int rate, int bits, int channels) { + wFormatTag = (short) WaveFormats.Pcm; + nChannels = (short) channels; + nSamplesPerSec = rate; + wBitsPerSample = (short) bits; + cbSize = 0; + + nBlockAlign = (short) (channels * (bits / 8)); + nAvgBytesPerSec = nSamplesPerSec * nBlockAlign; + } + } +} \ No newline at end of file diff --git a/WaveStream.cs b/WaveStream.cs new file mode 100644 index 0000000..877a49a --- /dev/null +++ b/WaveStream.cs @@ -0,0 +1,263 @@ +using System; +using System.IO; + +namespace StegoPrint { + public class WaveStream : Stream, IDisposable { + private Stream m_Stream; + private long m_DataPos; + private int m_Length; + + private WaveFormat m_Format; + + public WaveFormat Format { + get { return m_Format; } + } + + private string ReadChunk (BinaryReader reader) { + byte[] ch = new byte[4]; + reader.Read (ch, 0, ch.Length); + return System.Text.Encoding.ASCII.GetString (ch); + } + + private void ReadHeader () { + BinaryReader Reader = new BinaryReader (m_Stream); + 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"); + + m_Format = new WaveFormat (22050, 16, 2); // initialize to any format + m_Format.wFormatTag = Reader.ReadInt16 (); + m_Format.nChannels = Reader.ReadInt16 (); + m_Format.nSamplesPerSec = Reader.ReadInt32 (); + m_Format.nAvgBytesPerSec = Reader.ReadInt32 (); + m_Format.nBlockAlign = Reader.ReadInt16 (); + m_Format.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 + while (m_Stream.Position < m_Stream.Length && ReadChunk (Reader) != "data") + ; + + if (m_Stream.Position >= m_Stream.Length) + throw new Exception ("Invalid file format"); + + m_Length = Reader.ReadInt32 (); + m_DataPos = m_Stream.Position; + + Position = 0; + } + + /// ReadChunk(reader) - Changed to CopyChunk(reader, writer) + /// source stream + /// four characters + 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); + } + + /// ReadHeader() - Changed to CopyHeader(destination) + private void CopyHeader (Stream destinationStream) { + BinaryReader reader = new BinaryReader (m_Stream); + BinaryWriter writer = new BinaryWriter (destinationStream); + + if (CopyChunk (reader, writer) != "RIFF") + throw new Exception ("Invalid file format"); + + writer.Write (reader.ReadInt32 ()); // File length minus first 8 bytes of RIFF description + + if (CopyChunk (reader, writer) != "WAVE") + throw new Exception ("Invalid file format"); + + if (CopyChunk (reader, writer) != "fmt ") + throw new Exception ("Invalid file format"); + + int len = reader.ReadInt32 (); + if (len < 16) { // bad format chunk length + throw new Exception ("Invalid file format"); + } else { + writer.Write (len); + } + + m_Format = new WaveFormat (22050, 16, 2); // initialize to any format + m_Format.wFormatTag = reader.ReadInt16 (); + m_Format.nChannels = reader.ReadInt16 (); + m_Format.nSamplesPerSec = reader.ReadInt32 (); + m_Format.nAvgBytesPerSec = reader.ReadInt32 (); + m_Format.nBlockAlign = reader.ReadInt16 (); + m_Format.wBitsPerSample = reader.ReadInt16 (); + + //copy format information + writer.Write (m_Format.wFormatTag); + writer.Write (m_Format.nChannels); + writer.Write (m_Format.nSamplesPerSec); + writer.Write (m_Format.nAvgBytesPerSec); + writer.Write (m_Format.nBlockAlign); + writer.Write (m_Format.wBitsPerSample); + + // advance in the stream to skip the wave format block + len -= 16; // minimum format size + writer.Write (reader.ReadBytes (len)); + len = 0; + /*while (len > 0) + { + reader.ReadByte(); + len--; + }*/ + + // assume the data chunk is aligned + while (m_Stream.Position < m_Stream.Length && CopyChunk (reader, writer) != "data") + ; + + if (m_Stream.Position >= m_Stream.Length) + throw new Exception ("Invalid file format"); + + m_Length = reader.ReadInt32 (); + writer.Write (m_Length); + + m_DataPos = m_Stream.Position; + Position = 0; + } + + /// Write a new header + public static Stream CreateStream (Stream waveData, WaveFormat format) { + MemoryStream stream = new MemoryStream (); + BinaryWriter writer = new BinaryWriter (stream); + + 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 + + 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 ())); + + 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; + } + + public WaveStream (Stream sourceStream, Stream destinationStream) { + m_Stream = sourceStream; + CopyHeader (destinationStream); + } + + public WaveStream (Stream sourceStream) { + m_Stream = sourceStream; + ReadHeader (); + } + + ~WaveStream () { + Dispose (); + } + + public new void Dispose () { + base.Dispose (); + GC.SuppressFinalize (this); + } + + public override bool CanRead { + get { return true; } + } + public override bool CanSeek { + get { return true; } + } + public override bool CanWrite { + get { return false; } + } + public override long Length { + get { return m_Length; } + } + + /// Length of the data (in samples) + public long CountSamples { + get { return (long) ((m_Length - m_DataPos) / (m_Format.wBitsPerSample / 8)); } + } + + public override long Position { + get { return m_Stream.Position - m_DataPos; } + set { Seek (value, SeekOrigin.Begin); } + } + public override void Flush () { } + public override void SetLength (long len) { + throw new InvalidOperationException (); + } + public override long Seek (long pos, SeekOrigin o) { + switch (o) { + case SeekOrigin.Begin: + m_Stream.Position = pos + m_DataPos; + break; + case SeekOrigin.Current: + m_Stream.Seek (pos, SeekOrigin.Current); + break; + case SeekOrigin.End: + m_Stream.Position = m_DataPos + m_Length - pos; + break; + } + return this.Position; + } + + public override int Read (byte[] buf, int ofs, int count) { + int toread = (int) Math.Min (count, m_Length - Position); + return m_Stream.Read (buf, ofs, toread); + } + + /// Read - Changed to Copy + /// Buffer to receive the data + /// Offset + /// Count of bytes to read + /// Where to copy the buffer + /// Count of bytes actually read + public int Copy (byte[] buf, int ofs, int count, Stream destination) { + int toread = (int) Math.Min (count, m_Length - Position); + int read = m_Stream.Read (buf, ofs, toread); + destination.Write (buf, ofs, read); + + if (m_Stream.Position != destination.Position) { + Console.WriteLine (); + } + + return read; + } + + public override void Write (byte[] buf, int ofs, int count) { + throw new InvalidOperationException (); + } + } +} \ No newline at end of file diff --git a/WaveUtility.cs b/WaveUtility.cs new file mode 100644 index 0000000..0b0e31d --- /dev/null +++ b/WaveUtility.cs @@ -0,0 +1,203 @@ +using System.IO; + +namespace StegoPrint { + public class WaveUtility { + /// + /// The read-only stream. + /// Clean wave for hiding, + /// Carrier wave for extracting + /// + private WaveStream sourceStream; + + /// Stream to receive the edited carrier wave + private Stream destinationStream; + + /// bits per sample / 8 + private int bytesPerSample; + + /// Initializes a new WaveUtility for hiding a message + /// Clean wave + /// + /// Header of the clean wave + /// This stream will receive the complete carrier wave + /// + public WaveUtility (WaveStream sourceStream, Stream destinationStream) : this (sourceStream) { + this.destinationStream = destinationStream; + } + + /// Initializes a new WaveUtility for extracting a message + /// Carrier wave + public WaveUtility (WaveStream sourceStream) { + this.sourceStream = sourceStream; + this.bytesPerSample = sourceStream.Format.wBitsPerSample / 8; + } + + /// + /// Hide [messageStream] in [sourceStream], + /// write the result to [destinationStream] + /// + /// The message to hide + /// + /// A key stream that specifies how many samples shall be + /// left clean between two changed samples + /// + public void Hide (Stream messageStream, Stream keyStream) { + + byte[] waveBuffer = new byte[bytesPerSample]; + byte message, bit, waveByte; + int messageBuffer; //receives the next byte of the message or -1 + int keyByte; //distance of the next carrier sample + + while ((messageBuffer = messageStream.ReadByte ()) >= 0) { + //read one byte of the message stream + message = (byte) messageBuffer; + + //for each bit in message + for (int bitIndex = 0; bitIndex < 8; bitIndex++) { + + //read a byte from the key + keyByte = GetKeyValue (keyStream); + + //skip a couple of samples + for (int n = 0; n < keyByte - 1; n++) { + //copy one sample from the clean stream to the carrier stream + sourceStream.Copy (waveBuffer, 0, waveBuffer.Length, destinationStream); + } + + //read one sample from the wave stream + sourceStream.Read (waveBuffer, 0, waveBuffer.Length); + waveByte = waveBuffer[bytesPerSample - 1]; + + //get the next bit from the current message byte... + bit = (byte) (((message & (byte) (1< 0) ? 1 : 0); + + //...place it in the last bit of the sample + if ((bit == 1) && ((waveByte % 2) == 0)) { + waveByte += 1; + } else if ((bit == 0) && ((waveByte % 2) == 1)) { + waveByte -= 1; + } + + waveBuffer[bytesPerSample - 1] = waveByte; + + //write the result to destinationStream + destinationStream.Write (waveBuffer, 0, bytesPerSample); + } + } + + //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); + } + + /// Extract a message from [sourceStream] into [messageStream] + /// Empty stream to receive the extracted message + /// + /// A key stream that specifies how many samples shall be + /// skipped between two carrier samples + /// + public void Extract (Stream messageStream, Stream keyStream) { + + byte[] waveBuffer = new byte[bytesPerSample]; + byte message, bit, waveByte; + int messageLength = 0; //expected length of the message + int keyByte; //distance of the next carrier sample + + while ((messageLength == 0 || messageStream.Length < messageLength)) { + //clear the message-byte + message = 0; + + //for each bit in message + for (int bitIndex = 0; bitIndex < 8; bitIndex++) { + + //read a byte from the key + keyByte = GetKeyValue (keyStream); + + //skip a couple of samples + for (int n = 0; n < keyByte - 1; n++) { + //read one sample from the wave stream + sourceStream.Read (waveBuffer, 0, waveBuffer.Length); + } + sourceStream.Read (waveBuffer, 0, waveBuffer.Length); + waveByte = waveBuffer[bytesPerSample - 1]; + + //get the last bit of the sample... + bit = (byte) (((waveByte % 2) == 0) ? 0 : 1); + + //...write it into the message-byte + message += (byte) (bit << bitIndex); + } + + //add the re-constructed byte to the message + messageStream.WriteByte (message); + + if (messageLength == 0 && messageStream.Length == 4) { + //first 4 bytes contain the message's length + messageStream.Seek (0, SeekOrigin.Begin); + messageLength = new BinaryReader (messageStream).ReadInt32 (); + messageStream.Seek (0, SeekOrigin.Begin); + messageStream.SetLength (0); + } + } + + } + + /// Counts the samples that will be skipped using the specified key stream + /// Key stream + /// Length of the message + /// Minimum length (in samples) of an audio file + public static long CheckKeyForMessage (Stream keyStream, long messageLength) { + long messageLengthBits = messageLength * 8; + long countRequiredSamples = 0; + + if (messageLengthBits > keyStream.Length) { + long keyLength = keyStream.Length; + + // read existing key + byte[] keyBytes = new byte[keyLength]; + keyStream.Read (keyBytes, 0, keyBytes.Length); + + // Every byte stands for the distance between two useable samples. + // The sum of those distances is the required count of samples. + countRequiredSamples = SumKeyArray (keyBytes); + + // The key must be repeated, until every bit of the message has a key byte. + double countKeyCopies = messageLengthBits / keyLength; + countRequiredSamples = (long) (countRequiredSamples * countKeyCopies); + } else { + byte[] keyBytes = new byte[messageLengthBits]; + keyStream.Read (keyBytes, 0, keyBytes.Length); + countRequiredSamples = SumKeyArray (keyBytes); + } + + keyStream.Seek (0, SeekOrigin.Begin); + return countRequiredSamples; + } + + private static long SumKeyArray (byte[] values) { + long sum = 0; + foreach (int value in values) { // '0' causes a distance of one sample, + // every other key causes a distance of its exact value. + sum += (value == 0) ? 1 : value; + } + return sum; + } + + /// + /// Read the next byte of the key stream. + /// Reset the stream if it is too short. + /// + /// The key stream + /// The next key byte + private static byte GetKeyValue (Stream keyStream) { + int keyValue; + if ((keyValue = keyStream.ReadByte ()) < 0) { + keyStream.Seek (0, SeekOrigin.Begin); + keyValue = keyStream.ReadByte (); + if (keyValue == 0) { keyValue = 1; } + } + return (byte) keyValue; + } + } +} \ No newline at end of file diff --git a/candidate.wav b/candidate.wav new file mode 100644 index 0000000..ca623ac Binary files /dev/null and b/candidate.wav differ diff --git a/commands.txt b/commands.txt new file mode 100644 index 0000000..c8a110b --- /dev/null +++ b/commands.txt @@ -0,0 +1,2 @@ +add -m "ArgleBargle" -k "C:\dev\working\stegano\mylib\keyfile.txt" -i "C:\dev\working\stegano\mylib\candidate.wav" -o "C:\dev\working\stegano\mylib\out.wav" +extract -k "C:\dev\working\stegano\mylib\keyfile.txt" -i "C:\dev\working\stegano\mylib\out.wav" \ No newline at end of file diff --git a/keyfile.txt b/keyfile.txt new file mode 100644 index 0000000..241edff --- /dev/null +++ b/keyfile.txt @@ -0,0 +1 @@ +VvQgjAmL5ea9xSkpooHOF0bLpnXwJnJFqHcpw0lLYlnW5Nx0I6ZvhQYkIJ32x9AAijftEXHWEH6azX6inOC9WWHtD89LS6WRXX7jr1NssEB9xN7I5tt4O3AWja8oK6oggSNhFOLsDbpvJzcEM3gtxRLFklcw9e1uipgjyXKNOPPNMI1ElsFEED0ui497AFW6FYnPXalsDjU5K0P3xXwqg37K1iRAUjHhujjeXsMVXakGfHHVMS8NNwHvvM90qN5rIOFEiZ6oeDjbQDMxygaZqGaCPe5VprabqVmhR4Fy55HxaJ2ok87NeYUvW1vc3yPkYCCV8gq3LCE6UHOjVhp5KDe815eRfely7wcnUgXSGxAOKl3FMHhMwYPkqLOVFYDVveaHWk4nqFKRLc2XFBhFh1knhThAIz76nrf91NJchxTaJAg6pC7Kwz9iEULi90Ins6Jk7PhogfnFI73qRU9P1RFgXQG71G5IMpR4YIPRyx1FURTq4Z0bp4HpFReM2ML4KVvZ9lBG1vmIFE5QlFG68s4EtfgMC6Pqm2pjnSPw5jIlbEYFuNzi5b07z0jtA5wtKIiNHRemFk5tV1GcruwPHIrB1obqoi00gAc34BGX7UwHhujKqeNkkGNkW26k4ln4hVFHDjCvibMYjhWoZ6CvFJbhPtLuWwo48bDHi36U6t71AD2KUP51r9FKSVetZIb2webi8BFVDLDACEWBBQh8mpkJbb00LPbRYCyr5GEQ2fciZ9lvFjbrbUUCh2rfG9bmcbDpso7liEbpYGbwchBJBLZy0PFexsvaQAgRt7jWGaTA1h63gvnOMtkwgH96JrV6L6WoEo0awt3rmaeeeONeOlc7MyS3KatSEmTYQParY9veokTnxA6oDIFubV5foLUmwXkv71e3Py0GgnfKCUIYlXsabsh1goS7DnvqO9vRJjmxMGF9vW5aphSvc9tuzfxEvO27H7jlgXkwi8BUUwKZ2kVulVC4nuG67FR6eUezACg1taVNeuCHx0EAyjJDHuxzKwraz06pTCTtfZhScefb55ElILLV1lZaAc5xoC3lltS22q6ER81hzect8mHjnNJ9Otpkno7jtxLKHZRmbXFX28XhMS24PBfNa0CP61EPttH4e6KDaI4OlIUSECkfCjhDlNcJCL19UnIGc86R1JAFscu3L3aivTBOGqiPnqru2myiT1o7hIUt3I7YYzn9swImBguJzAHTOMuH9OPcQ2Tehu6EUGBvAFsrU17OAmkAm0kEZbLV5GBziUs7z2cHnqLnyAHROWiKSHI7MXiHaQhxDhGnxtlD11lQgWziCKceMzywF1RPBluTgX8TywyL5ym2RnE1BnCOiy6YALGDv0lErhcQUDELZFGK7RRD1I9VFQjuUuwATbwYtR6mgMoBkP9PFmDYFo0BcunPBEU6S1qfuzVvNIlt364xxvuv1weKvjFOSEk5UYF3PWfJfp6S5IWK81OOUVqR0MwT7aZMqIAoVXMnM943WNOgH6yaTZrSeLEOE4zfsIUoN8HOBJR5CzcY6tQQgcLtzCIaBsDn7XEFly68zfDrQybEESrU63VmZfCtllTP8IwCQsiyxZjCmo3bXG8FUDkb9wTkjXCvhItytxRGzFLFlTbSiAVJnB25gEm6Dttu6HZnfwBRb5l0XEZIb3Hma2GzokeoRoeVnaFFgjoE7ENnuKwxjQHQcgkSTkNfUROBRfMDeXWzCw37vjw6OtGooO8tDTamuNHOc7I1XL5cT4cFOm7erFcu3xzClUgwYK6EieI6SLatcjil9XZHqXD23zTC7uvFmDjW2iOH0k6URNULNCvepvpqpsMFu6qlphwMxXLXUotC3SLjlxI81oOGPYIszX1q38mKIoBKEsUtv9EfGs8PZXZJINTZITwbcbVyPO8jyrwxfROHG9OWFMhfver6UrSUFPzTrxzl2I7h9N3VMcte0bXgXs5XR2G81gpcKwHnOItKcoFrpgu8p3RmyjpvNJfKWoUKIXQzbMWpzNBhw7gaL4CcjjwW0NFWFjj3uOyffP9ISeUG3DUxCCQFliRDD6iwAyapLrhOmpRm3UUVYRnL6aRoATPCtcWVNAqswZGBQkgIKIU0XnXWB6eJewFbfRBpaFM1u5y8lo9Qr8c48a8Q1sJA4u2HumI03L7FWRGkewVaauC2bFT2qwKx0bHvyoHZaVf4AHTS3NZpGk5bwO73ey0V0LJvPBNC4r5RwW83CnwxnYmlZCquU9knXN5ii07tyySrP2ED0Tie2AQnWejMAOgQTS3ls1WcDmpgHRyeCyoi8mKocxzjYb8GSpMhkqm0QWKaDwIjH8VQfGQuT9UR6qPFihRC9UEXrH1Va1J4F3bTvoxtQvcGYjt5z91bvrZ3zsGJ1RPoLxKHsR4YX6fFXfYYWJpaTuqmYykttOA4fTeuE7fAZLbqu02Hpba4EmAWuoOJoLEw6rQApxKrw1RLIStInOjGIYbPYzUzvcSJr39tlTMLl4kWsMjtXBnfmDTeCIK8JntAbX3EfLF8gCfM8zbb1uBIeEfDUTiRnV1cuFnlo2Daag1fXwb7FxEhUupDKne3ac8klqhS67mav6RiKNyt8kM7yytwNyzAKGBFligLHf1a5mBAOHRYpFM2VpnIYMAD4LXoRsx4xujcEe30XCkk5EUBPYZ89Ep3HRiotFJlBIRhSq7xkbEmgBRsMrXFu1VJf72ZLmZfhGTRQHX39aOUgzGeBWs38qqhDjjeNWwn5CqAQ991f8gGrhzWtXZyzivxm6CJ4ZzsoIwJDtcMAIkOZ3XpVLWuO3QPBZZ2RU4BHBwTi6W0lrrqYCAzo7gUBI69MeOLY4sq1iZOIvDGX4rBRvGBsINa9EMJFa0RnOEZyNV5VywlHtU0OVMCPrOqKTt1GusO0gv50rrOs3nFcfYKMhBEWj6Cjv2cSZT6koHT3044yuzR0U6Q8mBPcej1olw9Omesna1op033IJlsFR1KKL49y0JAzymhUMfeTxsZmwas3p1L5eXWmFTAb3fGc6lRC06i1pZ3IgEJpNzy7O0Rx2CV1wgTL5zbW1xtSaNFVG98HvfjjTGzCxe1f7zfywPw4t8UxwvglXLOpGxKi67liX2bjkZbjCgx0nM5FvMyblJqUt3fQDggQsR3upSTaGkkXPE6UF5Z8gXJrgZzgSLE50kiQckXVgMwEI64a58oTzk61t9sLfVbS5hSTzMAsBrjg1pUnLbeFOob4iWIwGqq5RICssAhaz4BIoTbjWiOXmpxomIvyYBNLxXgFHi1Ur0NStnHrzaLNhINsUuWQjTEbLp2ugYPHKHtCE5GNSK9p0lenlmlZLKzP8LYbzUE0xEmiRscA2aGHTuz5QJpYG6JV28prYhrW9LbroYY5NIfzDETYO0NkvtMVLcbJns67pnCGUvZJvwKelXBCcQn2VthU6ZHx8pSm3UmVW8tuBeNexoDgqIkBoHjnhuKbXzEhvu2cxNqp3bLsFJr46gLqhFc3ws32HP7uxCTXJ4ySuMnMuEqDzfBlPiYeyIfUZMNpMgVSwm4fe4Uv2mkBGIzeicU88gPwB189tzUQZipGjbR1AyUqqCt81cDWzxpwlHKDfFrngF7cNGNzoQfLf83X9ttPNTIjcD7K4kDDbLHKSKoo4txF7Ywa9uf8zepfbIS4055sUuPWPpOt4Iy8DEQK9ZLMZzOtawwJoPnW6ipf1m2pp6P4SLq9XHhSRiOT7x2wJwQnNpxmueqWw02tlgMF1gavYHSqfMky6ksRZ5M20Qy3A6bEU5SHHP0shzQh9OAAFfAgg8G0p9UAPPLERQe0KgBnooOVtUfAe2ZvGOzCgpMBl4ZvRBMWDMjMM7v8uDuJXBwAkVNATBilyrQhlRHLYGj69HtNl99hO01WFqRhrBiLeBUtcGFBh8eW9KskUwCIsjq3sNFECSZsKvwJ6hUOqcqMQHs52F06cxOaxJoG5pb0y53qzOBP2W5hYFeVqkZf8I4t4ETXpGIufOmIOGApx5xVxPFpKoSAkN7q4sDPXZu65a37ELECkbuiDCgSEFOU5etgjURMSyNilNggqbliAY8vy1XPzNDus954Ngzzfcfwm5F2nEY \ No newline at end of file diff --git a/out.wav b/out.wav new file mode 100644 index 0000000..a2038bb Binary files /dev/null and b/out.wav differ diff --git a/stegoprint.csproj b/stegoprint.csproj new file mode 100644 index 0000000..a296963 --- /dev/null +++ b/stegoprint.csproj @@ -0,0 +1,10 @@ + + + Exe + netcoreapp1.1 + + + + + + \ No newline at end of file