使用文件缓冲区循环加密文件



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

我将其切换到读取 8kb、加密、写入重复系统,但现在每次循环时,它都会在输出中添加额外的 33 字节,我不确定为什么。这意味着,如果文件大小<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();

如果有人能帮助我找出代码的问题,我将不胜感激。

去年,我使用 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++在管道术语中所说的那样)。这实际上在以前已经介绍过,但它并不明显:

  • 无法在线程运行方法中运行 CryptoPP 文件接收器的情况下杀死 Qt 线程
  • 加密++异常调用消息结束
  • 使用管道加密文件

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

第二个问题的答案可以在Crypto++维基上的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);
}

还没有使用经过身份验证的加密模式(如EAX,CCM或GCM)对此进行测试。我们可以解决您在更新 wiki 页面时遇到的任何问题

,以造福他人。我已经知道您需要将成员StreamTransformationFilter换成JavaCiper用于加密的AuthenticatedEncryptionFilter和用于解密的AuthenticatedDecryptionFilter。Artjom在他的评论中还详细介绍了一些潜在的问题。


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

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

最新更新