Encoder at a Glance

  • Tools: Made using C++ (Code::Blocks); tinyfiledialogs.c
  • Dev Platform: Developed on Ubuntu 16.04 LTS
  • Target Platform(s): Linux/OSX
  • Time: ~45 hours, December 2016
  • Team: Solo

C++ Plugin System

A main feature of this project is its plugin system - something I have never done before. It allows users to create their own custom content, such as custom cyphers, and greatly increases the modularity of the base application. I don't think the implementation is ideal, and this is actually what limits the program to non-Windows systems, as it uses <dlfcn.h>, which is Linux's (and OSX's) counterpart to Windows' LoadLibrary.

Show me some code


#ifndef PLUGIN_H_INCLUDED
#define PLUGIN_H_INCLUDED

#include <string>
#include <bitset>
#include <vector>
#include "KeyRing.h"

class Plugin {
  public:

    Plugin() {};
    virtual ~Plugin() {};

    /*
    \brief Generic command function. Currently not really used for anything right now.
    \param Two strings; command and options
    \return A string
    */
    virtual std::string Command(std::string command, std::string options) {return "";}

    /*
    \brief Returns the plugin's name to be used in code
    NOTE: The plugin's name should/will EXACTLY match a KeyRing's "encryptionType"
    \param N/A
    \return The plugin's name
    */
    virtual std::string GetPluginName()
    {
        return "REPLACE-ME!";
    }

    /*
    \brief Will do the actual encoding of the data
    \param A vector of bits (the data to be handled)
    \return N/A
    */
    virtual void HandleDataENC(std::vector<std::string> _kr, std::vector< std::bitset<8> >& _data)
    {
        //Base won't do anything to the data
        return;
    }

    /*
    \brief Will do the actual decoding of the data
    \param A vector of bits (the data to be handled)
    \return N/A
    */
    virtual void HandleDataDEC(std::vector<std::string> _kr, std::vector< std::bitset<8> >& _data)
    {
        //Base won't do anything to the data
        return;
    }

    /*
    \brief Will do the actual encoding of the data
    \param A singe byte to be encoded
    \return N/A
    */
    virtual void HandleByteENC(char& _byte)
    {
        //Base won't do anything to the data
        return;
    }

    /*
    \brief Will do the actual decoding of the data
    \param A singe byte to be decoded
    \return N/A
    */
    virtual void HandleByteDEC(char& _byte)
    {
        //Base won't do anything to the data
        return;
    }

    /*
    \brief Prepare to encode/decode the data
    \param A serialized key ring
    \return N/A
    */
    virtual void InitializeByteStream(std::vector<std::string> _kr)
    {
        return;
    }

    /*
    \brief Returns the recommended uses for the plugin; should be one of:
    -> KeyRep           (meaning it should be used to encode/decode the user's keyrep)
    -> Storage           (meaning it should be used to encode/decode generic files for storage)
    -> CommsAll       (meaning it should be used for both encryption and decryption of messages)
    -> CommsEnc     (meaning it should ONLY be used for the ENCRYPTION of messages)
    -> CommsDec     (meaning it should ONLY be used for the DECRYPTION of messages)
    \param N/A
    \return The list of recommended uses.
    */
    virtual std::vector<std::string> RecommendedUses()
    {
        std::vector<std::string> vec;
        return vec;
    }
};


#define DEFINE_PLUGIN(classType, pluginName, pluginVersion, expectedVersion)    \                                                             std::shared_ptr<Plugin> load()    \
{   \
    return std::make_shared<classType>();   \
}   \
const char* name()  \
{   \
        return pluginName;\
}   \
const char* version()   \
{   \
    return pluginVersion;   \
}   \
const char* expVersion()    \
{   \
    return expectedVersion;    \                                                                 }   \

#endif // PLUGIN_H_INCLUDED

Tiny File Dialogs Integration

Another main feature of this program is its ease of use. Unlike most command-line programs, by integrating Tiny File Dialogs, Encoder is very approachable as users can take advantage of familiar ways of interacting with programs.

Show me some code

while (openFileName == nullptr)
{
    std::cout << "Please select the file you wish to encrypt: ";

    openFileName = tinyfd_openFileDialog(
                       "Select File to Encrypt",
                       krm.GetDefaultLoadFilePath().c_str(),
                       0,
                       filters,
                       NULL,
                       0
                   );

    if (openFileName == nullptr)
    {
        std::cout << "Would you like to abort? [Y]es/[N]o:";
        std::cin >> userInput;
        if (userInput == 'y' || userInput == 'Y')
            return;
    }
}

Unlimited Maximum File Size

Unlike previous versions of Encoder, Encoder R3 has a built-in fallback if a file is too large. Instead of trying to load the entire file into memory and then manipulating the data, Encoder R3 supports byte-by-byte manipulation of the data (if the plugins do as well).

Show me some code

//Setup
char byte;
file.seekg(0, file.end);
unsigned int length = file.tellg();
file.seekg(0, file.beg);

//If the file is too big, we will encrypt it via a stream instead
if (length > MAX_FILE_SIZE)
{
    //We initialize the stream encoder once, by passing the keyring
    krm.GetPluginsList().find(pluginName)->second->  InitializeByteStream(krm.GetKeyRingsList().find( krm.FetchHashByName(krName))-> second.SerializeToList());
    while (true)
    {
        file.read(&byte,1);
        krm.GetPluginsList().find(pluginName)->second-> HandleByteENC(byte);
        saveFile.write(reinterpret_cast<char*>(byte), 1);

        if (!file)
            break;
    }
    file.close();
    saveFile.close();
}
else
{
    std::vector<std::bitset<8>> buffer;
    //Read file as bytes
    for (unsigned int cnt = 0; cnt < length; cnt++)
    {
        file.read(&byte, 1);
        buffer.push_back(std::bitset<8>(byte));
        file.seekg(cnt+1);
    }

    //Close the file; we don't want to modify it
    file.close();

    //Encrypt the data
    krm.GetPluginsList().find(pluginName)->second-> HandleDataENC(krm.GetKeyRingsList().find( krm.FetchHashByName(krName))->
    second.SerializeToList(), buffer);

    //Save the encrypted data
    //Now, we start writing
    for (unsigned int cnt = 0; cnt < buffer.size(); cnt++)
    {
        //Is this necessary?
        saveFile.seekp(cnt);
        saveFile.write(reinterpret_cast<char*>(&buffer[cnt]),1);
    }
    saveFile.close();
}

About

Encoder came about as a result of several different project ideas/aspirations. One of these is security - I am of the opinion that the vast majority of people today do not give data security the importance it merits. A part of this problem is the public perception of it - encryption often seems like something that only movie characters and major corporations have to worry about, not your 'average Joe'. I am not going to claim that Encoder R3 is the solution to this problem (though I do believe it is capable enough to be useful to some number of people), but that it is a significant stepping stone towards the solution.