【问题标题】:Rare crash with Crypto++ pbkdf2::DeriveKey()Crypto++ pbkdf2::DeriveKey() 的罕见崩溃
【发布时间】:2019-04-11 11:27:06
【问题描述】:

我正在使用 CryptoPP 库来散列一些密码。大约 10 次中有 1 次在下面的 DeriveKey 行上崩溃,并出现段错误。

即使使用固定参数,崩溃似乎仍然是随机的。我想知道我的字符串末尾是否需要一个 '\0' 。或者输出缓冲区可能需要零初始化,或者什么?

无论如何,这是代码。

#include <cryptopp/aes.h>
#include <cryptopp/algparam.h>
#include <cryptopp/filters.h>
#include <cryptopp/modes.h>
#include <cryptopp/sha.h>
#include <cryptopp/pwdbased.h>

int main()
{
CryptoPP::PKCS5_PBKDF2_HMAC<CryptoPP::SHA256> pbkdf2;

CryptoPP::byte salt[16];

CryptoPP::byte key[32];

/* Hard coded for testing purposes */
Common::podFromHex("00f8807a289655b2a8e38cda00182a32", salt);

/* Hard coded for testing purposes */
std::string password = "a";

std::cout << "Salt: " << Common::podToHex(salt) << std::endl;
std::cout << "Salt size: " << sizeof(salt) << std::endl;
std::cout << "Password: " << password.data() << std::endl;
std::cout << "Password size: " << password.size() << std::endl;

/* Rare segfault on this line */
pbkdf2.DeriveKey(
    key, sizeof(key), 0, (CryptoPP::byte *)password.data(),
    password.size(), salt, sizeof(salt), Constants::PBKDF2_ITERATIONS
);
}

一切似乎都已正确初始化 - 打印语句每次都给我完全相同的东西:

Salt: 00f8807a289655b2a8e38cda00182a32
Salt size: 16
Password: a
Password size: 1

此外 - 哈希密码在没有段错误时是可用的。后面我用AES加密,可以完美解密文件,所有数据都符合预期。

派生密钥的源代码可以在这里找到,顺便说一句:https://www.cryptopp.com/docs/ref/pwdbased_8h_source.html#l00235

谢谢。

【问题讨论】:

  • 我们需要更多信息,比如公共库的相关源代码。带有堆栈跟踪的确切崩溃行也会有所帮助。您可能应该在 Valgrind 或其他内存检查工具(如 Address Sanitizer)下运行您的程序。
  • 我正在考虑使用 valgrind 运行 - 这有点痛苦,因为我使用的一些周围代码会抛出大量错误 - 我可能应该在单独的程序中测试它。我将摆脱通用库以获得更好的最小示例 - 我相当确定代码的那部分没有问题,它只是转换为十六进制。
  • 你应该在-O1 保持 Valgrind-clean。如果您在 -O1 观察到大量 Valgrind 的发现,那么您可能有一些问题需要解决。另见The Valgrind Quick Start Guide | Preparing your program。 (-O2 和 -O3 的 {Some|Many} 发现是误报)。
  • 哦,当然,当前代码库存在大量问题 - 尝试慢慢修复它们。

标签: c++ crypto++ pbkdf2


【解决方案1】:

我最终解决了这个问题。这实际上不是因为上面列出的任何代码,而是因为我在信号处理程序中使用 this 调用了一个函数。

因为这需要很长时间才能运行(使用 500,000 次迭代),所以它似乎每次都在这里崩溃,但实际上并不是因为这条线。

我最终修复了它,在信号处理程序中翻转了一个 bool 标志,并有一个单独的线程监视该标志。设置标志后,它会调用析构函数,进而触发正常的关闭流程,让我的所有线程都能正确关闭并保存。

【讨论】:

    【解决方案2】:

    我在冒险猜测,但salt 不是以 NULL 结尾的。该程序可能正在访问超过salt 数组的第 16 个元素:

    std::cout << "Salt: " << Common::podToHex(salt) << std::endl;
    

    以下执行多次没有问题。它是你的程序少了对公共库的调用。

    对std::memcpy 的调用只占用字符串最左边的 16 个字节。它不进行转换。 (我只想删除对 Common 的调用)。

    $ cat test.cxx
    
    #include "cryptlib.h"
    #include "filters.h"
    #include "sha.h"
    #include "hex.h"
    #include "files.h"
    #include "pwdbased.h"
    
    #include <string>
    #include <iostream>
    #include <cstring>
    
    int main()
    {
        using namespace CryptoPP;
    
        PKCS5_PBKDF2_HMAC<SHA256> pbkdf2;
    
        byte salt[16], key[32];
    
        /* Hard coded for testing purposes */
        // Common::podFromHex("00f8807a289655b2a8e38cda00182a32", salt);
        std::memcpy(salt, "00f8807a289655b2a8e38cda00182a32", 16);
    
        /* Hard coded for testing purposes */
        std::string password = "a";
    
        // std::cout << "Salt: " << Common::podToHex(salt) << std::endl;
        std::cout << "Salt: ";
        StringSource(salt, sizeof(salt), true, new HexEncoder(new FileSink(std::cout)));
        std::cout << std::endl;
    
        std::cout << "Salt size: " << sizeof(salt) << std::endl;
        std::cout << "Password: " << password.data() << std::endl;
        std::cout << "Password size: " << password.size() << std::endl;
    
        /* Rare segfault on this line */
        pbkdf2.DeriveKey(
            key, sizeof(key), 0, (byte *)password.data(),
            password.size(), salt, sizeof(salt), 10000 /*Constants::PBKDF2_ITERATIONS*/
        );
    
        std::cout << "Key: ";
        StringSource(key, sizeof(key), true, new HexEncoder(new FileSink(std::cout)));
        std::cout << std::endl;
    
        return 0;
    }
    

    编译并执行:

    $ g++ -DNDEBUG -g2 -O3 test.cxx -o test.exe ./libcryptopp.a
    $ ./test.exe
    Salt: 30306638383037613238393635356232
    Salt size: 16
    Password: a
    Password size: 1
    Key: F88BA6947B802C66F7E7A2BC0099AFD92C81DC293E3CC48C2DA3FA75E27ECE6B
    

    【讨论】:

    • 也许我理解错了,但你的例子中的 salt not null 也不是终止的吗?明天我会进行更多测试 - 我可能可以摆脱 Common:: 调用来制作一个更好的最小示例。
    • @Zpalmtree - 正确,但我只编码 16 个字节(编码为 32 个字符),然后将 32 个字节插入 std::cout。我不依赖 NULL 终止符。
    • 也许我已经通过使用 Common:: 东西来尝试简化示例而感到困惑 - 在实际使用中,我使用一种方法为 salt 生成 16 个随机字节。我似乎无法再重现该错误,而 valgrind 没有提供任何帮助。试图将其归咎于宇宙射线,罕见的段错误非常令人沮丧。 :(
    猜你喜欢
    • 1970-01-01
    • 2016-06-13
    • 1970-01-01
    • 2020-02-24
    • 2016-12-23
    • 1970-01-01
    • 2020-08-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多