【问题标题】:Encrypt file using file buffer loop使用文件缓冲区循环加密文件
【发布时间】:2016-10-13 03:23:30
【问题描述】:

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

我将它切换为读取 8kb、加密、写入重复系统,但现在每次循环时,它都会在输出中增加额外的 33 字节,我不知道为什么。这意味着如果文件大小

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

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

【问题讨论】:

  • 请不要单独加密 8KB 块。您正在为每个块重用 IV,因此这是一个 many-time pad,因为 GCM 基于 CTR,它是一种流模式,它为每个块创建相同的密钥流。可以在不知道密钥的情况下推导出明文。有了这个方案,如果有更多的块,它会变得更容易。您需要设置一次方案,然后您可以传入多个块。
  • 感谢您让我知道这一点,那么您是否建议在加密代码之前拆分大文件,这样它可能就像 4 个小文件一样,然后加密。这样每块都会得到不同的iv,我仍然可以加密一个大文件?谢谢。
  • 我不知道它在 Crypto++ 中是如何工作的,但我怀疑你会遍历这些块并将它们传递给 ef.ChannelPut("", ...) 并将其他所有内容从 ef.ChannelMessageEnd(""); 移到循环后面. AAD 设置也应该在循环之前完成。
  • 好的,谢谢,我会研究 ChannelPut。此外,如果我只是在文件加密之前创建自己的文件拆分代码,这是否会以任何方式损害文件。例如,4GB 文件被拆分为 1GB (4x) 文件。每一块都用单独的 IV 加密。
  • 只要您确保使用唯一的 IV(随机数)并且不要忘记添加所有 4 个身份验证标签,就不会妥协。问题是拆分会带来更高的管理开销,因为您需要以某种方式写入一个块结束和下一个块开始的文件格式。如果您在单个块中加密,则根本不必这样做。此外,GCM 使用一对密钥 + IV 可确保高达 68GB 的​​安全。

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


【解决方案1】:

去年我使用 AES 256 GCM 使用 C++ 和 crypto++ 库制作了一个加密程序。今年我想把它升级到 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)对此进行了测试。我们可以解决您在更新 wiki 页面时遇到的任何问题,以造福他人。

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


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

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

【讨论】:

    猜你喜欢
    • 2021-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    相关资源
    最近更新 更多