2016-06-12 70 views
0

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

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

我有什么到目前为止能够弄清楚它不是加密代码,因为它对小于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(); 

如果有人可以帮助我弄清楚什么是错的代码,我将不胜感激。

+0

请不要分别加密8KB块。你正在重复使用每个块的IV,因此这是一个[多次填充](http://crypto.stackexchange.com/questions/2249/how-does-one-attack-a-two-time-pad因为GCM基于点播模式,它为每个块创建相同的密钥流。在不知道密钥的情况下推导出明文是可能的。有了这个方案,如果有更多的块,它会变得更容易。您需要设置一次该方案,然后您可以传入多个区块。 –

+0

感谢您让我知道这一点,所以您会建议在加密代码之前将大文件分割开来,因此它可能只有4个较小的文件,然后对其进行加密。这样每个部分会得到不同的iv,我仍然可以加密一个大文件?谢谢。 –

+0

我不确切知道它在Crypto ++中的工作方式,但我怀疑你会遍历块并将它们传递给'ef.ChannelPut(“”,...)'并将所有其他内容从'ef.ChannelMessageEnd (“”);'在循环后面。 AAD设置也应该在循环之前完成。 –

回答

0

去年我做了一个加密程序,使用AES 256 GCM使用C++和crypto ++ lib。今年我想升级到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 ++ wiki上的Init-Update-Final找到。简而言之,不要在每次循环迭代中创建一个新的AuthenticatedEncryptionFilter。相反,使用单个过滤器并调用MaxRetrievable()来确定是否有任何密文准备就绪。如果有,则在可用时检索它。否则,过滤器将无限期地缓冲它。

Init-Update-Final页面有一个例子。以下是update函数的外观。我相信,你期望它主要工作,比如说,从爪哇(这就是为什么我们把它称为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成员StreamTransformationFilterAuthenticatedEncryptionFilter加密和AuthenticatedDecryptionFilter解密。 Artjom还在他的评论中详述了一些潜在的问题。


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

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