Search code examples
c++qtencryptioncrypto++aes-gcm

AES/GCM with GunZip, cannot decompress correctly


EDIT I have rephrased the question yet again and provided a MINIMAL working example which can reproduce the bug.

I am trying to use GCM for file encryption. My underlining framework is Qt. Idea is this:

  1. load sourceFile e.g. a.jpg
  2. encrypt the file in GCM mode as PDATA
  3. into ADATA store single byte (for now, in real application this will be more with info such as IV, original files name etc.)
  4. append ADATA to encrypted file

  5. load encrypted file and extract ADATA/PDATA/MAC

  6. feed this data in correct order to AuthenticatedDecryptionFilter and expect decrypted file to be stored into FileSink

I need a loop that goes through the unencrypted file and pumps the file into AuthenticatedEncryptionFilter, as I need to check isAlive for threading purposes in real application. Therefore I cannot use simple pipelining and have to go over data manually in loops.

Now to the problem

Hash Verification fails on the code below (exception is thrown on derypt)

#include <qdebug.h>
#include <CryptoPP/cryptlib.h>
#include <CryptoPP/gcm.h>
#include <CryptoPP/aes.h>
#include <CryptoPP/filters.h>
#include <CryptoPP/files.h>
#include <CryptoPP/gzip.h>
#include <qfile.h>
#include <qfileinfo.h>


using namespace CryptoPP;
using namespace std;
int main(int argc, char *argv[])
{

    byte * key = new byte[16];
    byte * iv = new byte[16];

    memset(key, 0, 16);
    memset(iv, 0, 16);

    const int TAG_SIZE = 16;
    const int BUFFER_SIZE = 4096;

    byte keyLength = 16;
    byte blockSize = 16;

    const char * sourceFileName = "C:\\Users\\Tomas\\Documents\\Visual Studio 2013\\Projects\\testgcm\\Win32\\Debug\\source.jpg";
    const char * sourceFileName2 = "C:\\Users\\Tomas\\Documents\\Visual Studio 2013\\Projects\\testgcm\\Win32\\Debug\\source2.jpg";
    const char * destFileName = "C:\\Users\\Tomas\\Documents\\Visual Studio 2013\\Projects\\testgcm\\Win32\\Debug\\source.jpg.enc";


    try
    {

        GCM< AES >::Encryption e;
        e.SetKeyWithIV(key, keyLength, iv, blockSize);

        AuthenticatedEncryptionFilter ef(e,
            new FileSink(destFileName), false, TAG_SIZE
            ); 

        // AuthenticatedEncryptionFilter::ChannelPut
        //  defines two channels: "" (empty) and "AAD"
        //   channel "" is encrypted and authenticated
        //   channel "AAD" is authenticated


        // this is the block for ADATA
        QByteArray adata;
        adata.append((char)1);

        ef.ChannelPut("AAD", (const byte *)adata.data(), adata.size());
        ef.ChannelMessageEnd("AAD");

        FileStore fs(sourceFileName);

        int mr = 0;

        while (mr = fs.MaxRetrievable()){
            fs.TransferTo(ef, BUFFER_SIZE,"");
        }

        ef.ChannelMessageEnd("");

        // append ADATA to encrypted file

        QFile file(QString::fromStdString(destFileName));
        file.open(QIODevice::Append);

        file.write(adata);

        file.close();

        // HELP: is the encrypted file in this format now? ENC_TEXT||MAC(TAG_SIZE)||HEADER(1)


    }
    catch (CryptoPP::BufferedTransformation::NoChannelSupport& e)
    {
        // The tag must go in to the default channel:
        //  "unknown: this object doesn't support multiple channels"
        cerr << "Caught NoChannelSupport..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }
    catch (CryptoPP::AuthenticatedSymmetricCipher::BadState& e)
    {
        // Pushing PDATA before ADATA results in:
        //  "GMC/AES: Update was called before State_IVSet"
        cerr << "Caught BadState..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }
    catch (CryptoPP::InvalidArgument& e)
    {
        cerr << "Caught InvalidArgument..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }

    // DECRYPTION


    try
    {

        // now extract ADATA and MAC from enc file
        QFile file(QString::fromStdString(destFileName));
        file.open(QIODevice::ReadOnly);
        file.seek(file.size() - 1);

        // ADATA
        QByteArray adata = file.read(1);

        // exract MAC

        file.seek(file.size() - 1 - TAG_SIZE);
        QByteArray mac = file.read(TAG_SIZE);

        GCM< AES >::Decryption d;
        d.SetKeyWithIV(key, keyLength, iv);


        // Object will not throw an exception
        //  during decryption\verification _if_
        //  verification fails.

        AuthenticatedDecryptionFilter df(d, new FileSink(sourceFileName2),
            AuthenticatedDecryptionFilter::MAC_AT_BEGIN |
            AuthenticatedDecryptionFilter::THROW_EXCEPTION, TAG_SIZE);


        df.ChannelPut("", (const byte*)mac.data(), mac.size());

        // The order of the following calls are important
        df.ChannelPut("AAD", (const byte*)adata.data(), adata.size());

        // open enc file
        FileStore fs(destFileName);


        // when we read the file, we dont care for the ADATA and TAG, so we omit it
        int omitSize = (adata.size() + mac.size());

        // max retrievable (for FileStore this is how many bytes are not read yet)
        int mr = 0;

        // get part without tag and footer
        while (((mr = fs.MaxRetrievable()) > omitSize)){
            mr = ((mr - omitSize) < BUFFER_SIZE) ? (mr - omitSize) : BUFFER_SIZE;
            fs.TransferTo(df, mr, "");
        }

        // we pumped teh whole filestore into df. it was supposed to be pumping it to GUnzip and then to FileSink

        // and signal this is all
        df.ChannelMessageEnd("AAD");
        df.ChannelMessageEnd("");


    }
    catch (CryptoPP::InvalidArgument& e)
    {
        cerr << "Caught InvalidArgument..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }
    catch (CryptoPP::AuthenticatedSymmetricCipher::BadState& e)
    {
        // Pushing PDATA before ADATA results in:
        //  "GMC/AES: Update was called before State_IVSet"
        cerr << "Caught BadState..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }
    catch (CryptoPP::HashVerificationFilter::HashVerificationFailed& e)
    {
        cerr << "Caught HashVerificationFailed..." << endl;
        cerr << e.what() << endl;
        cerr << endl;
    }




}

I suspect I am feeding the info into decryptor in a wrong way, but I am following official CryptoPP example.

Please help, Thanks


Solution

  • This was trivial in the end. ALWAYS MAKE SURE YOU PASS ivSize into

    GCM< AES >::Decryption d;
    d.SetKeyWithIV(key, keyLength, iv, blockSize);
    

    and

     GCM< AES >::Encryption e;
     e.SetKeyWithIV(key, keyLength, iv, blockSize);
    

    even though they are optional. Failure to do so will result in incorrect decryption