6
\$\begingroup\$

The program can be used to hide an image within another image and later extract the hidden image. This is done by concealing the secret image within the lowest bits of the apparent image. Example of a cat hidden within a tree.

I split it into 2 programs to make commandline args parsing easier, Crypt and Decrypt.

Info.h

//Info.h
#pragma once
#include <stdint.h>

const int keySpace = 6;
typedef uint16_t PTYPE;

unsigned int bitMax(int bits) {
    return (unsigned int)pow(2, bits) - 1;
}

Crypt.h

//Crypt.h
#include "CImg.h"
#include "Info.h"

#include <stdexcept>
#include <bitset>
#include <map>
#include <sstream>


using namespace cimg_library;
using std::string;

std::map<string, string> parseCMD(int argc, char *argv[]);

template<typename T>
void encrypt(CImg<T>& apparent, CImg<T>& secret, std::map<string, string> args) {
    if (!apparent.containsXYZC(secret.width() - 1, secret.height() - 1, secret.depth() - 1, secret.spectrum() - 1)) { //Check that secret fits inside apparent.
        throw std::invalid_argument("Secret is out of bounds of apparent.");
    }

    int bitDepth = std::stoi(args["bitdepth"]) / apparent.spectrum();
    int secretBitDepth = std::stoi(args["secretbitdepth"]) / secret.spectrum();

    apparent.normalize(0, bitMax(bitDepth));
    secret.normalize(0, bitMax(secretBitDepth));

    T secretMask = ~bitMax(secretBitDepth);

    cimg_forXYZC(apparent, x, y, z, v) {
        apparent.atXYZC(x, y, z, v) = (apparent.atXYZC(x, y, z, v) & secretMask) | secret.atXYZC(x, y, z, v); //Set apparent's *secretBitDepth* least significant bits to secret's value. 
    }
}

//Sets the R value of the first *keySpace* pixels of apparent to the binary value of the rotational key.
template<typename T>
void sign(CImg<T>& img, int key) {
    if (key > bitMax(keySpace)) {
        std::stringstream ss;
        ss << "Key is too large to fit into " << keySpace << " bits.";

        throw std::invalid_argument(ss.str());
    }

    std::bitset<keySpace> bitKey(key);
    for (int i = 0; i < keySpace; i++) {
        img.atXYZC(i, 0, 0, 0) = bitKey[i];
    }
}

Crypt.cpp

//Crypt.cpp
#include "Crypt.h"

#include <tclap/CmdLine.h>

int main(int argc, char *argv[]) {
    auto args = parseCMD(argc, argv);

    CImg<PTYPE> secret(args["secret0"].c_str());
    CImg<PTYPE> apparent(args["apparent"].c_str());

    if (args["resize"] == "true") {
        enum class Interpolation { NoneRawMem = -1, NoneBoundaryCondition, NearestNeighbour, MovingAverage, Linear, Grid, Cubic, Lanczos };
        secret.resize(apparent, (int)Interpolation::NearestNeighbour);
    }

    encrypt<PTYPE>(apparent, secret, args);

    if (args["signature"] == "true") {
        sign(apparent, std::stoi(args["secretbitdepth"]) / secret.spectrum());
    }

    apparent.save(args["output"].c_str());

    return 0;
}

//All argument parsing is done here and returned in an argMap<"arg", "value">.
//For multiarguments, the number of the args is passed in argMap["<name>num"] and they can be accessed via argMap[<name>0] ... argMap[<name>n]
std::map<string, string> parseCMD(int argc, char *argv[]) {
    try {
        TCLAP::CmdLine cmd("An Image Steganography tool.", ' ', "0.1");
        std::map<string, string> argMap;

        TCLAP::ValueArg<string> apparent("a", "apparent", "Apparent image to hide secret within.", true, "", "string", cmd);
        TCLAP::ValueArg<string> output("o", "output", "Output image.", false, "Hidden.png", "string", cmd);
        TCLAP::ValueArg<string> bitDepth("b", "bitdepth", "Resulting Color Bit Depth of output image.", false, "8", "integer", cmd);
        TCLAP::ValueArg<string> secretBitDepth("z", "secretbitdepth", "How many bits each secret is going to take up.", false, "8", "integer", cmd);
        TCLAP::MultiArg<string> secret("s", "secret", "Secret image(s) to hide into apparent.", true, "string", cmd);
        TCLAP::SwitchArg resize("r", "resize", "Resize all secrets to apparent?", cmd, false);
        TCLAP::SwitchArg signature("g", "signature", "Embed the rotational key inside the image?", cmd, false);

        cmd.parse(argc, argv);

        for (TCLAP::ArgListIterator it = cmd.getArgList().begin(); it != cmd.getArgList().end(); it++) {
            TCLAP::ValueArg<string>* valArg = dynamic_cast<TCLAP::ValueArg<string>*>(*it);
            TCLAP::SwitchArg* switchArg = dynamic_cast<TCLAP::SwitchArg*>(*it);
            TCLAP::MultiArg<string>* multiArg = dynamic_cast<TCLAP::MultiArg<string>*>(*it);

            if (valArg) {
                argMap[(*it)->getName()] = valArg->getValue();
            }
            else if (switchArg) {
                argMap[(*it)->getName()] = switchArg->getValue() == false ? "false" : "true";
            }
            else if (multiArg) {
                argMap[multiArg->getName() + "num"] = std::to_string(multiArg->getValue().size());

                for (int i = 0; i < multiArg->getValue().size(); i++) {
                    argMap[multiArg->getName() + std::to_string(i)] = multiArg->getValue()[i];
                }
            }
        }

        return argMap;
    }
    catch (TCLAP::ArgException &e) {
        std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
    }
}

Decrypt.h

//Decrypt.h
#include "CImg.h"
#include "Info.h"

#include <map>
#include <bitset>
#include <vector>

using namespace cimg_library;
using std::string;

std::map<string, string> parseCMD(int argc, char *argv[]);

template<typename T>
int getKey(CImg<T>& img) {
    std::bitset<keySpace> bitKey;
    for (int i = 0; i < keySpace; i++) {
        bitKey[i] = img.atXYZC(i, 0, 0, 0);
    }

    return static_cast<int>(bitKey.to_ulong());
}

template<typename T>
void rotateDecrypt(CImg<T>& img, int rotateKey) {
    std::vector<int> bitDepths = {8, 24, 48};

    T secretMask = bitMax(rotateKey);

    cimg_forXYZC(img, x, y, z, v) {
        img.atXYZC(x, y, z, v) &= secretMask;
    }

    img.normalize(0, bitMax(*std::upper_bound(bitDepths.begin(), bitDepths.end(), rotateKey)));
}

Decrypt.cpp

//Decrypt.cpp
#include "Decrypt.h"
#include <tclap/CmdLine.h>

int main(int argc, char *argv[]) {
    auto args = parseCMD(argc, argv);
    CImg<PTYPE> apparent(args["apparent"].c_str());

    int key = 0;
    if (args["signature"] == "true") {
        key = getKey(apparent);
    }
    else {
        key = std::stoi(args["key"]);
    }

    bool soleImage;
    istringstream(args["soleimage"]) >> std::boolalpha >> soleImage;

    rotateDecrypt(apparent, key);
    apparent.save(args["output"].c_str());

    return 0;
}

//All argument parsing is done here and returned in an argMap<"arg", "value">.
std::map<string, string> parseCMD(int argc, char *argv[]) {
    try {
        TCLAP::CmdLine cmd("An Image Steganography tool.", ' ', "0.1");
        std::map<string, string> argMap;

        TCLAP::ValueArg<string> output("o", "output", "Output image.", false, "unhidden.png", "string", cmd);
        TCLAP::ValueArg<string> key("k", "key", "Rotational key.", false, "8", "integer", cmd);
        TCLAP::ValueArg<string> apparent("a", "apparent", "Input apparent encrypted image.", false, "8", "integer", cmd);
        TCLAP::SwitchArg signature("s", "signature", "Embed the rotational key inside the image?", cmd, false);

        cmd.parse(argc, argv);

        for (TCLAP::ArgListIterator it = cmd.getArgList().begin(); it != cmd.getArgList().end(); it++) {
            TCLAP::ValueArg<string>* valArg = dynamic_cast<TCLAP::ValueArg<string>*>(*it);
            TCLAP::SwitchArg* switchArg = dynamic_cast<TCLAP::SwitchArg*>(*it);

            if (valArg) {
                argMap[(*it)->getName()] = valArg->getValue();
            }
            else if (switchArg) {
                argMap[(*it)->getName()] = switchArg->getValue() == false ? "false" : "true";
            }
        }

        return argMap;
    }
    catch (TCLAP::ArgException &e) {
        std::cerr << "error: " << e.error() << " for arg " << e.argId() << std::endl;
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Include all the headers you need

Info.h doesn't include required header for pow function (math.h)

Use const references when you don't want to modify parameters to avoid unnecessary copying

encrypt function in Crypt.h

template<typename T> 
void encrypt(CImg<T>& apparent, CImg<T>& secret, std::map<string, string> args)

should be

template<typename T> 
void encrypt(CImg<T>& apparent, CImg<T>& secret, std::map<string, string> const& args)

Do the all the conversions you need before supplying parameters to functions

int bitDepth = std::stoi(args["bitdepth"]) / apparent.spectrum();

This is not good since args["bitdepth"] can simply not convert into integer, you should've processed the arguments separately. The same holds for other arguments. This means you should rewrite parseCMD function or use some arguments parsing library.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.