0
votes

I'm playing around with AES and write the iv and derived key to the file so that I use it later for decryption...

following function encrypts the file and saves the iv and derived key into the file, it also saves encrypted text into separate file:

void MainWindow::on_button_encrypt() try
{
    using namespace std;
    using namespace Gtk;
    using namespace CryptoPP;

    fstream file;

    file.open(m_file_name + ".aes", std::ios::out | std::ios::trunc);
    if (file.is_open())
    {
        // get the plain password:
        SecByteBlock password;
        password_dialog get_passwd(password);
        get_passwd.run();


        // generate random salt
        const int SALT_SIZE = 32;
        SecByteBlock salt(SALT_SIZE);
        AutoSeededRandomPool prng;
        prng.GenerateBlock(salt.BytePtr(), SALT_SIZE);


        // derive a key from password and salt:
        SecByteBlock key(AES::DEFAULT_KEYLENGTH);
        PKCS5_PBKDF2_HMAC<SHA512> gen;
        gen.DeriveKey(key.BytePtr(), key.size(), 1, password.BytePtr(), password.size(), salt.BytePtr(), salt.size(), 200);


        // genereate random iv:
        SecByteBlock iv(AES::BLOCKSIZE);
        OS_GenerateRandomBlock(false, iv.BytePtr(), AES::BLOCKSIZE);


        // encrypt plain text:
        AES::Encryption enc(key.BytePtr(), AES::DEFAULT_KEYLENGTH);
        CBC_Mode_ExternalCipher::Encryption mode(enc, iv);

        string cipher_text;
        StringSink* sink = new StringSink(cipher_text);
        StreamTransformationFilter encryptor(mode, sink);

        string plain_text = m_file_buffer->get_text();
        encryptor.Put(reinterpret_cast<const byte*>(plain_text.c_str()), plain_text.length() + 1);
        encryptor.MessageEnd();

        // write the result:
        file << cipher_text.c_str();
        file.close();
        file.open(m_file_name + ".key", std::ios::out | std::ios::trunc);
        file << key << '\n' << iv;
        file.close();
   }
}
// catch ...

The above function writes the iv and derived key to the file.

0000000002F9F140

0000000002F9F4E0

Following function is supposed to read the file and decrypt it by using the *.key file:

void MainWindow::on_button_decrypt()
{
    using namespace std;
    using namespace CryptoPP;
    Gtk::MessageDialog info("Info");

    fstream file;
    file.open("decrypted.txt", ios::out | ios::trunc);
    if (file.is_open())
    {
        // read the key:
        fstream stream;
        string input;

        SecByteBlock key(AES::DEFAULT_KEYLENGTH);
        SecByteBlock iv(AES::BLOCKSIZE);


        stream.open("test.txt.key", std::ios::in);

        if (stream.is_open())
        {
            getline(stream, input);
            key.Assign(reinterpret_cast<const byte*>(input.c_str()), input.size());
            input.clear();
            getline(stream, input);
            iv.Assign(reinterpret_cast<const byte*>(input.c_str()), input.size());
        }
        else
        {
            info.set_secondary_text("can't read key file");
            info.run();
            return;
        }


        // decrypt:
        AES::Decryption dec(key, AES::DEFAULT_KEYLENGTH);
        CBC_Mode_ExternalCipher::Decryption mode(dec, iv);

        string plain_text;
        StringSink* sink = new StringSink(plain_text);
        StreamTransformationFilter decryptor(mode, sink);
        
        try
        {
            // get encrypted text from buffer (obtained from other function):
            string cipher_text = m_file_buffer->get_text();
            decryptor.Put(reinterpret_cast<const byte*>(cipher_text.c_str()), cipher_text.size());
            decryptor.MessageEnd();
            file << plain_text;
            file.close();
        }
        catch (CryptoPP::Exception& ex)
        {
            info.set_secondary_text(ex.what());
            info.run();
        }
    }
}

CryptoPP trows an exception while decrypting saying this:

FileTransformationFilter: ciphertext length is not multiple of a block size.

How ever it does not throw anything if decryption process is done in first function ( on_btn_encrypt() ) no file is read there, so it looks I'm having problem with reading encrypted file but have no idea how?

This is content of encrypted file: &`OôÍoYjÜMe×Q°Þ

plaintext is: some text

Do you see what am I missing here? thanks so much!

EDIT:

here are modification which solved the problem as sugested by Qmick and Artjom:

ENCRYPTION

// encrypt the file:
AES::Encryption enc(key.BytePtr(), AES::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Encryption mode(enc, iv);
    
string file = m_file_name + ".aes";
FileSink* sink = new FileSink(file.c_str());
StreamTransformationFilter encryptor(mode, sink);

string plain_text = m_file_buffer->get_text();
encryptor.Put(reinterpret_cast<const byte*>(plain_text.c_str()), plain_text.length());
encryptor.MessageEnd();


// write the key:
file = m_file_name + ".key";
FileSink* out = new FileSink(file.c_str());
Base64Encoder* base64_enc = new Base64Encoder;
base64_enc->Attach(out);
base64_enc->Put(key.BytePtr(), key.size());
base64_enc->MessageEnd();



// write the iv:
file = m_file_name + ".iv";
FileSink* out2 = new FileSink(file.c_str());
Base64Encoder* base64_enc2 = new Base64Encoder;
base64_enc2->Attach(out2);
base64_enc2->Put(iv.BytePtr(), iv.size());
base64_enc2->MessageEnd();

DECRYPTION:

//
// read key
//
string store_key;
StringSink* string_key_in = new StringSink(store_key);
Base64Decoder* base64_key_dec = new Base64Decoder(string_key_in);

string file = "test.txt.key";
FileSource* file_key_in = new FileSource(file.c_str(), true, base64_key_dec);
    
SecByteBlock key(AES::DEFAULT_KEYLENGTH);
key.Assign(reinterpret_cast<const byte*>(store_key.c_str()), store_key.size());




//
// read iv
//
string store_iv;
StringSink* string_iv_in = new StringSink(store_iv);
Base64Decoder* base64_iv_dec = new Base64Decoder(string_iv_in);

file = "test.txt.iv";
FileSource* file_iv_in = new FileSource(file.c_str(), true, base64_iv_dec);

SecByteBlock iv(AES::BLOCKSIZE);
iv.Assign(reinterpret_cast<const byte*>(store_iv.c_str()), store_iv.size());



// read ciphertext:
string store_ciphertext;
StringSink* ciphertext_in = new StringSink(store_ciphertext);

file = m_file_name;
FileSource* file_ciphertext_in = new FileSource(file.c_str(), true, ciphertext_in);

SecByteBlock cipher_text;
cipher_text.Assign(reinterpret_cast<const byte*>(store_ciphertext.c_str()), store_ciphertext.size());



//
// decrypt:
//
AES::Decryption dec(key, AES::DEFAULT_KEYLENGTH);
CBC_Mode_ExternalCipher::Decryption mode(dec, iv);

file = "decrypted.txt";
FileSink* sink = new FileSink(file.c_str());
StreamTransformationFilter decryptor(mode, sink);

decryptor.Put(cipher_text.BytePtr(), cipher_text.size());
decryptor.MessageEnd();
1
The problem is ciphertext length is not multiple of a block size. (duh). Read about block- and padding-modesdeviantfan
@deviantfan, thanks, I did, and as I said in my post above there is no such problem if decryption is done without reading a file.codekiddy
You have to read it in binary, because the ciphertext can contain all sorts of unprintable and whitespace characters like \n and \0.Artjom B.
Thanks Artjom! read/write in binary mode and also hex encoding/decoding the output solved the problem.codekiddy

1 Answers

2
votes

There is no guarentee cipher text will not contain escape sequence like \n, which make getline() get wrong string(truncated).

So it is recommended to transform the cipher text to hex or something else to avoid escape sequence.