2016-06-12 30 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還在他的評論中詳述了一些潛在的問題。


我很抱歉沒有提供大量的代碼。在我看來,你的設計需要一些小的工作,所以你還沒有準備好代碼(還)。

我猜你準備好你的下一組問題代碼(如果你問他們在這裏)。