使用文件缓冲区循环

时间:2016-06-12 17:54:30

标签: c++ qt file encryption crypto++

去年我使用C ++和crypto ++ lib使用AES 256 GCM进行了加密程序。今年我想将它升级到QT并改变我在文件中阅读的方式。旧方法是将整个文件读入char *,然后加密并写出来。我注意到大文件不起作用,所以我需要将其切换到缓冲区。

我将它切换到读取8kb,加密,写入重复系统,但现在每次循环时,它会为输出增加33bytes,我不知道为什么。这意味着如果文件大小<1。 8KB工作,如果文件大小介于8KB和16KB之间,则输出会增加33bytes,如果filesize介于16KB和24KB之间,则输出会额外增加66bytes等。

到目前为止我能够弄清楚的是它不是加密代码,因为它适用于小于8KB的文件,而且它不是文件循环代码,因为我用简单的复制文件代码替换了加密代码,它正确地复制了文件。

我认为问题是我没有重置变量,而且每次循环都会以某种方式搞乱加密代码的数据输入。

这是我的代码

void encryptfile(double progressbarfilecount, bool& threadstatus) {    

// variables for file data
int buffersize = 8192;
string fullfilename;
string filepath;
string filename;
char memblock[8192];
streampos size;
double filesize;
double encryptedfilesize;
string datastring;
CryptoPP::SecByteBlock initializationvector(32);
string initializationvectorstring;
string cipher;
string encoded;
QMessageBox msgBox;

// encrypt the file
// get the filepath and filename
fullfilename = listbox1->item(progressbarfilecount)->text().toUtf8().constData();
size_t found = fullfilename.find_last_of("/\\");
filepath = fullfilename.substr(0,found);
filename = fullfilename.substr(found + 1);

// get the file size
//QFile myFile(QString::fromStdString(fullfilename));
//filesize = myFile.size();
//myFile.close();
filesize = getfilesize(fullfilename);
 qDebug() << "filesize:" << QString::number(filesize);

// setup the file data
ifstream originalfile(fullfilename, ios::in | ios::binary | ios::ate);
ofstream encryptedfile(fullfilename + ".txt", ios::app);

// get random initializationvector
randomnumber.GenerateBlock(initializationvector, initializationvector.size());

// convert it to a string for the text filee
initializationvectorstring = string((char *)initializationvector.begin(),32);

// check if we should get the checksum of the original file
if (testencryptiontogglebuttonguisetting == "On") {
    originalfilechecksum << checksum(fullfilename);
}



// here is the loop where the problem maybe



// encrypt the file 8KB at a time
for (encryptedfilesize = 0; encryptedfilesize < filesize; encryptedfilesize+= buffersize) {
    // check if the data left to write is less than the buffer size
    if (filesize - encryptedfilesize < buffersize) {
        buffersize = filesize - encryptedfilesize;
        qDebug() << "new buffersize:" << QString::number(buffersize);
    }

    // read the file into a memory block
    originalfile.seekg(encryptedfilesize);
    originalfile.read(memblock, buffersize);

    // convert the memoryblock to readable hexadecimal
    datastring = stringtohexadecimal(string(memblock, buffersize), true);

    // encrypt
    try
    {
    GCM< AES >::Encryption e;
    e.SetKeyWithIV(key, sizeof(key), initializationvector,initializationvector.size());
    // Not required for GCM mode (but required for CCM mode)
    // e.SpecifyDataLengths( adata.size(), pdata.size(), 0 );

    AuthenticatedEncryptionFilter ef(e,new StringSink(cipher), false, TAG_SIZE); // AuthenticatedEncryptionFilter

    // AuthenticatedEncryptionFilter::ChannelPut
    //  defines two channels: "" (empty) and "AAD"
    //   channel "" is encrypted and authenticated
    //   channel "AAD" is authenticated
    ef.ChannelPut("AAD", (const byte*)adata.data(), adata.size());
    ef.ChannelMessageEnd("AAD");

    // Authenticated data *must* be pushed before
    //  Confidential/Authenticated data. Otherwise
    //  we must catch the BadState exception
    ef.ChannelPut("", (const byte*)datastring.data(), datastring.size());
    ef.ChannelMessageEnd("");

    // Pretty print
    StringSource(cipher, true,new HexEncoder(new StringSink(encoded), true, 16, " "));
    }
    catch (CryptoPP::BufferedTransformation::NoChannelSupport&)
    {
    // The tag must go in to the default channel:
    //  "unknown: this object doesn't support multiple channels"
        if (operatingsystem() == "Linux") {
            system("error_message_encrypt_file_error.sh");
        }
        if (operatingsystem() == "Windows") {
            ShellExecute(0, L"open", L"error_message_encrypt_file_error.vbs", 0, 0, SW_NORMAL);
        }
    //msgBox.setText("No Channel Support");
    //msgBox.exec();
    return;
    }
    catch (CryptoPP::AuthenticatedSymmetricCipher::BadState&)
    {
    // Pushing PDATA before ADATA results in:
    //  "GMC/AES: Update was called before State_IVSet"
        if (operatingsystem() == "Linux") {
            system("error_message_encrypt_file_error.sh");
        }
        if (operatingsystem() == "Windows") {
            ShellExecute(0, L"open", L"error_message_encrypt_file_error.vbs", 0, 0, SW_NORMAL);
        }
    //msgBox.setText("Data was read before adata");
    //msgBox.exec();
    return;
    }
    catch (CryptoPP::InvalidArgument&)
    {
        if (operatingsystem() == "Linux") {
            system("error_message_encrypt_file_invalid.sh");
        }
        if (operatingsystem() == "Windows") {
            ShellExecute(0, L"open", L"error_message_encrypt_file_invalid.vbs", 0, 0, SW_NORMAL);
        }
    //msgBox.setText("Invalid Argument");
    //msgBox.exec();
    return;
    }

    // convert the cipher to hexadecimal string
    cipher = stringtohexadecimal(cipher, true);

    // write the encrypted file to a text file with the original file extension
    // check to see if we need to write the initialization vector
    if (encryptedfilesize == 0) {
        initializationvectorstring = stringtohexadecimal(initializationvectorstring, true);
        encryptedfile << initializationvectorstring;
        qDebug() << "wrote the initilization vector";
    }
    encryptedfile << encoded;        
    qDebug() << "encrypted filesize:" << QString::number(encryptedfilesize);

    // clear the variables
    encoded = "";
    cipher = "";
    initializationvectorstring = "";
    keys = "";

}

// close the file data
originalfile.close();
encryptedfile.close();

如果有人能帮我弄清楚代码有什么问题,我将不胜感激。

1 个答案:

答案 0 :(得分:0)

  

去年我使用C ++和crypto ++ lib使用AES 256 GCM进行了加密程序。今年我想将它升级到QT并改变我在文件中阅读的方式。旧方法是将整个文件读入char *,然后加密并写出来。我注意到大文件不起作用,所以我需要将其切换到缓冲区......

在最高级别,您似乎有两个设计要求。首先,您需要在避免密文扩展的同时对数据进行分块。其次,您需要集成经过身份验证的加密方案。

每个循环额外的16个字节是由于身份验证标记被添加到每个加密的块。信不信由你,这有时是一个理想的财产。例如,图像下载4.7 GB Gentoo图像并找出整个图像已损坏并最终被拒绝。它的原因是:

for (encryptedfilesize = 0; encryptedfilesize < filesize; encryptedfilesize+= buffersize)
{
    ...
    AuthenticatedEncryptionFilter ef(e,new StringSink(cipher), false, TAG_SIZE); // AuthenticatedEncryptionFilter    
    ...
}

为了实现你的目标,我认为你需要做两件事。首先,要回答如何阻止或分块数据的问题,您需要Pump您的数据(如Crypto ++在Pipeline parlance中调用它)。这实际上已经包含在内,但不容易明白:

以上处理Crypto ++中数据的阻塞或分块。第二个问题,如何避免每个块上的身份验证标记,这里没有被问到(如果内存服务器我正确的话)。

第二个问题的答案可以在Crypto ++ wiki的Init-Update-Final找到。缺点是,不要在每次循环迭代中创建新的AuthenticatedEncryptionFilter。而是使用单个过滤器并调用MaxRetrievable()来确定是否准备好任何密文。如果有,则在可用时检索它。否则,过滤器将无限期地缓冲它。

Init-Update-Final页面有一个例子。以下是update函数的外观。我相信它大部分都可以像你期望的那样工作,比如Java(这就是为什么我们称之为JavaCipher):

size_t JavaCipher::update(const byte* in, size_t isize, byte* out, size_t osize)
{
    if(in && isize)
        m_filter.get()->Put(in, isize);

    if(!out || !osize || !m_filter.get()->AnyRetrievable())
        return 0;

    size_t t = STDMIN(m_filter.get()->MaxRetrievable(), (word64)osize);
    return m_filter.get()->Get(out, t);
}

当您致电final时,就是生成身份验证标记的时间。虽然不容易看出,但标签是在MessageEnd()

的调用中生成的
size_t JavaCipher::final(byte* out, size_t osize)
{
    m_filter.get()->MessageEnd();

    if(!out || !osize || !m_filter.get()->AnyRetrievable())
        return 0;

    size_t t = STDMIN(m_filter.get()->MaxRetrievable(), (word64)osize);
    return m_filter.get()->Get(out, t);
}

我使用authenticated encryption mode(如EAX,CCM或GCM) 对此进行了测试。我们可以在更新维基页面时解决您遇到的任何问题,以造福他人。

我已经知道您需要将JavaCiper成员StreamTransformationFilter换成AuthenticatedEncryptionFilter进行加密,并AuthenticatedDecryptionFilter进行解密。 Artjom还在他的评论中详述了一些潜在的问题。

我很抱歉没有提供大量代码。在我看来,你的设计需要一些小的工作,所以你尚未准备好代码(还)。

我猜你为下一组问题中的代码做好准备(如果你在这里问他们的话)。