Search code examples
c++aescrypto++

Re-encrypting the encrypted data file generates a decrypted output


I wrote the encryption function using Crypto++ library, function behaves correctly when a file encryption is done for the first time. If the same encrypted file is passed again for encryption, generates the output which includes encrypted and decrypted data.

bool EncryptDataFile(const char* inputFile, const char* outputFile)
{
  try
  {
    std::vector<byte> key = HexDecoding(PASSCODE);
    std::vector<byte> iv = HexDecoding(INITIALIZATION_VECTOR);

    GCM<AES>::Encryption encryptor;
    encryptor.SetKeyWithIV(key.data(), key.size(), iv.data(), iv.size());

    FileSource fs(inputFile, true,
        new AuthenticatedEncryptionFilter(encryptor,
            new FileSink(outputFile), false, TAG_SIZE));
  }

  catch(...)
  {
    return false;
  }

  return true;
}

Input.txt:

Privacy and Security

Output1.txt - first time encryption output:

{)ªei ?ñìCzN[hç&Ää€|Ùrñ½…
Ä

Input "Output1.txt", Output "Output2.txt" - second time encryption:

Privacy and Security]®Ÿwþñ úeS„£Fpä40WL ,ÈR¯M 

It has revealed the original data. An not sure what is missing here.


Solution

  • If the same encrypted file is passed again for encryption, generates the output which includes encrypted and decrypted data.

    If I am parsing things correctly, you are saying m ≅ Enc(Enc(m)) instead of c = Enc(Enc(m)) in your encryption scheme. This is one of the reasons why you should avoid designing your own scheme.

    This can happen in several scenarios, like with a stream cipher or block cipher in counter mode when re-using a key and iv.

    You should using a different security context for each message or encryption operation. With some hand waiving, that means change the key or iv for each message or encryption operation.


    std::vector<byte> key = HexDecoding(PASSCODE);
    std::vector<byte> iv = HexDecoding(INITIALIZATION_VECTOR);
    

    This is likely your problem. You need to use a different security context for each message or encryption operation.


    Here is how you fix it. You use a key derivation function to derive different security parameters for each encryption. In the code below, the 32-byte key is divided into two 16-byte keys. The same applies to the iv. The first encryption uses key+0 and iv+0; and the second encryption uses key+16 and iv+16.

    cryptopp$ cat test.cxx
    #include "cryptlib.h"
    #include "filters.h"
    #include "files.h"
    #include "aes.h"
    #include "gcm.h"
    #include "hex.h"
    #include "hkdf.h"
    #include "sha.h"
    
    #include <string>
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        using namespace CryptoPP;
    
        std::string password = "super secret password";
        SecByteBlock key(32), iv(32);
    
        HKDF<SHA256> hkdf;
        hkdf.DeriveKey(key, key.size(),
                       (const byte*)password.data(), password.size(),
                       NULL, 0,  // salt
                       (const byte*)"key derivation", 14);
    
        hkdf.DeriveKey(iv, iv.size(),
                       (const byte*)password.data(), password.size(),
                       NULL, 0,  // salt
                       (const byte*)"iv derivation", 13);
    
    
        std::string m = "Yoda said, Do or do not. There is no try.";
        std::string c1, c2;
    
        GCM<AES>::Encryption encryptor;
    
        encryptor.SetKeyWithIV(key, 16, iv, 16);
        StringSource(m, true, new AuthenticatedEncryptionFilter(
                     encryptor, new StringSink(c1)));
    
        encryptor.SetKeyWithIV(key+16, 16, iv+16, 16);
        StringSource(c1, true, new AuthenticatedEncryptionFilter(
                     encryptor, new StringSink(c2)));    
    
        std::cout << "Hex(m):" << std::endl;
        StringSource(m, true, new HexEncoder(new FileSink(std::cout)));
        std::cout << std::endl;
    
        std::cout << "Hex(Enc(m)):" << std::endl;
        StringSource(c1, true, new HexEncoder(new FileSink(std::cout)));
        std::cout << std::endl;
    
        std::cout << "Hex(Enc(Enc(m))):" << std::endl;
        StringSource(c2, true, new HexEncoder(new FileSink(std::cout)));
        std::cout << std::endl;
    
        return 0;
    }
    

    Here is a run of the program:

    cryptopp$ ./test.exe
    Hex(m):
    596F646120736169642C20446F206F7220646F206E6F742E205468657265206973206E6F20747279
    2E
    Hex(Enc(m)):
    D4A9063DE7400E90627DE90D16346DC5A99740C55F6FEE092A99071F55F1BDB25A72B7422126CCC4
    09B5B5C0076E39EBF7256D5DC3151A738D
    Hex(Enc(Enc(m))):
    83A459F2D4A1627624AF162590465AC705C8AC0F4D915E4A4A9D300156C5F9E042CAA47903353F0A
    A1FAE408D5747DD223AC4F9AEF3C320EEF7E79E08AB2C6FBEAE7A3A5B4978C45C7
    

    I think your scheme has some additional problems. For example, if you encrypt the message "Attack at dawn!" multiple times, then you get the same ciphertext on each run. It is leaking information, and it lacks ciphertext indistinguishability.

    I think you should avoid your scheme, and use an Elliptic Curve Integrated Encryption Scheme (ECIES). It avoids most of the latent problems in your scheme, and achieves IND-CCA2.

    The downside to ECIES is, you have to manage a public/private keypair. It is not a big downside, though. You are already managing a password and iv, so changing from a password to a private key is not much more work.