Steganography demo

The other day, for kicks, I wrote a small program to embed a hidden image within another image, and another to extract the hidden image. This is called steganography:

…the art and science of writing hidden messages in such a way that no-one apart from the sender and intended recipient even realizes there is a hidden message, a form of security through obscurity. By contrast, cryptography obscures the meaning of a message, but it does not conceal the fact that there is a message. [Wikipedia]
Step through the demo on flickr, and read the descriptions to understand what's going on. In this technique, I use 6 bits per channel for the outer, visible image, and 2 bits per channel for the hidden image. However, the composed result is pretty much indistinguishable from the original.

The programs use the GraphicsMagick variant of the ImageMagick library. (I had GM ready to go already because I used it for other projects.) If you want to play with these images yourself, the JPEG conversions on Flickr will not work; download this PNG instead.

Update: The code is now on github.

// Steganographic decoder, requires GraphicsMagick
// Copyright 2009 Christopher League
// This is free software; you may distribute & modify it under GNU GPLv3
#include <Magick++.h>
#include <iostream>
#include <cassert>

using std::cout;
using std::string;
using namespace Magick;

Quantum revealDisguisedColor(Quantum q)
{
  // just keep lowest 2 bits, then amplify by 2^6
  return (q & 3) << 6;
}

int main(int argc, char** argv)
{
  InitializeMagick(*argv);
  if(argc != 3) {
      cout << "Usage: " << argv[0] << " input.png output.png\n";
      exit(1);
  }

  const string samplePath = argv[1];
  const string outputPath = argv[2];

  cout << "Loading " << samplePath << '\n';
  Image im (samplePath);
  Geometry g = im.size();

  cout << "Dimensions are " << (string)g << '\n';
  cout << "Depth is " << im.depth() << '\n';
  assert(im.type() == TrueColorType);

  for(unsigned x = 0; x < g.width(); x++) {
      for(unsigned y = 0; y < g.height(); y++) {
      Color c = im.pixelColor(x, y);
      c.redQuantum(revealDisguisedColor(c.redQuantum()));
      c.greenQuantum(revealDisguisedColor(c.greenQuantum()));
      c.blueQuantum(revealDisguisedColor(c.blueQuantum()));
      im.pixelColor(x, y, c);
    }
  }

  cout << "Writing " << outputPath << '\n';
  im.write(outputPath);
  return 0;
}
// Steganographic encoder, requires GraphicsMagick
// Copyright 2009 Christopher League
// This is free software; you may distribute & modify it under GNU GPLv3
#include <Magick++.h>
#include <iostream>
#include <cassert>

using std::cout;
using std::string;
using namespace Magick;

Quantum mergeQuanta(Quantum pub, Quantum priv)
{
  // reduce private color to just 2 bits
  priv >>= 6;
  // zero out lowest 2 bits of public color
  pub &= 0xFC;
  // merge
  return pub | priv;
}

Color mergeColors(Color pub, Color priv)
{
  pub.redQuantum(mergeQuanta(pub.redQuantum(), priv.redQuantum()));
  pub.greenQuantum(mergeQuanta(pub.greenQuantum(), priv.greenQuantum()));
  pub.blueQuantum(mergeQuanta(pub.blueQuantum(), priv.blueQuantum()));
  return pub;
}

int main(int argc, char** argv)
{
  InitializeMagick(*argv);
  if(argc != 4) {
      cout << "Usage: " << argv[0] << " public.png private.png output.png\n";
      exit(1);
  }

  const string publicPath = argv[1];
  const string privatePath = argv[2];
  const string outputPath = argv[3];

  cout << "Loading " << publicPath << '\n';
  Image pub (publicPath);
  Geometry g = pub.size();
  assert(pub.depth() <= 8);
  assert(pub.type() == TrueColorType);

  cout << "Loading " << privatePath << '\n';
  Image priv (privatePath);
  assert(priv.size() == g);
  assert(priv.depth() <= 8);
  assert(priv.type() == TrueColorType);

  for(unsigned x = 0; x < g.width(); x++) {
    for(unsigned y = 0; y < g.height(); y++) {
      pub.pixelColor(x, y, mergeColors(pub.pixelColor(x, y),
                                       priv.pixelColor(x, y)));
    }
  }

  cout << "Writing " << outputPath << '\n';
  pub.write(outputPath);
  return 0;
}

©20022015 Christopher League