将 PBKDF2 与 OpenSSL 库一起使用

Posted

技术标签:

【中文标题】将 PBKDF2 与 OpenSSL 库一起使用【英文标题】:Utilizing PBKDF2 with OpenSSL library 【发布时间】:2014-05-12 19:01:50 【问题描述】:

我想将 PBKDF2 算法与 SHA1 HMAC 结合使用(基于 this 答案)。

如何通过加密库使用它?

我从查看 man openssl 开始,但 openssl passwd 命令 (man page) 仅支持少数几种算法。查看crypto 文档,evp 模块有一个EVP_BytesToKey 方法。

仔细选择参数将提供与 PKCS#5 PBKDF1 兼容的实现。但是,新应用程序通常不应该使用它(例如,更喜欢 PCKS#5 中的 PBKDF2)。

这让我回到了最初的问题,我如何通过加密使用 PBKDF2?我是否需要深入研究代码并调用非 API 公开的方法(例如PKCS5_PBKDF2_HMAC)? (如果是这样,是什么让它不被曝光?)

【问题讨论】:

您选择的语言几乎肯定有一个已经实现 PBKDF2 的库。这(恕我直言)比直接使用openssl 命令行或尝试直接与(非常糟糕的)libssl API 对话更可取。 How to use PKCS5_PBKDF2_HMAC_SHA1() 的可能重复项。另见Is there any C API in openssl to derive a key from given string 和What does PKCS5_PBKDF2_HMAC_SHA1 return value mean? “我是否需要深入研究代码并调用非 API 公开的方法(例如 PKCS5_PBKDF2_HMAC)” - 实际上,这是一个公共 API 函数。最近添加了它的文档。 PKCS5_* 是国会大厦,是公共 API 的一部分,可以使用。 pkcs5_* 较低,是内部 API 的一部分,不应使用(或有被更改或删除的风险)。 EVP_BytesToKey 的手册页有些模棱两可,因为它没有明确指出 PKCS5_PBKDF2_HMAC。它确实在“SEE ALSO”部分提到了它,但有明显的脱节(否则,你不会问这个问题)。已经提交了一个补丁来解决它。 Plus One 用于阅读文档并提出问题。感谢您提出。 【参考方案1】:

我确实有一个通过my github repository 的 OpenSSL 库的 PBKDF2 的工作但很差的 C 示例,包括在 Linux 和 Windows 下编译的脚本(通过 MinGW)。位于“Releases”下的源代码是已知的; master 分支中的源代码是 WIP。除了 SSLeay 许可证 OpenSSL 之外,此变体还根据相同的 4 条款 BSD 获得许可。

我仍在努力添加一些功能,然后我会回到我在 Code Review StackExchange 网站上获得的出色输入并升级到 C99 语法等等。

核心代码非常原始,尽管通过了非常广泛的基于字符串的测试向量,但仍可能包含缺陷。它尚未(尚未)针对纯二进制输入进行测试。

#include <openssl/evp.h>
#include <openssl/sha.h>
// crypto.h used for the version
#include <openssl/crypto.h>

void PBKDF2_HMAC_SHA_1nat_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)

    unsigned int i;
    unsigned char digest[outputBytes];
    PKCS5_PBKDF2_HMAC_SHA1(pass, strlen(pass), salt, strlen(salt), iterations, outputBytes, digest);
    for (i = 0; i < sizeof(digest); i++)
        sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);

如果您有 64 位系统,我强烈建议您升级到 PBKDF2-HMAC-SHA-512 或 PBKDF2-HMAC-SHA-384:

#include <openssl/evp.h>
#include <openssl/sha.h>
// crypto.h used for the version
#include <openssl/crypto.h>


void PBKDF2_HMAC_SHA_384_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)

    unsigned int i;
    unsigned char digest[outputBytes];
    PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations, EVP_sha384(), outputBytes, digest);
    for (i = 0; i < sizeof(digest); i++)
        sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);


void PBKDF2_HMAC_SHA_512_string(const char* pass, const unsigned char* salt, int32_t iterations, uint32_t outputBytes, char* hexResult)
 
     unsigned int i;
     unsigned char digest[outputBytes];
     PKCS5_PBKDF2_HMAC(pass, strlen(pass), salt, strlen(salt), iterations, EVP_sha512(), outputBytes, digest);
     for (i = 0; i < sizeof(digest); i++)
         sprintf(hexResult + (i * 2), "%02x", 255 & digest[i]);
     

一个使用示例是:

// 2*outputBytes+1 is 2 hex bytes per binary byte, 
// and one character at the end for the string-terminating \0
char hexResult[2*outputBytes+1];
memset(hexResult,0,sizeof(hexResult));
PBKDF2_HMAC_SHA_1nat_string(pass, salt, iterations, outputBytes, hexResult);
printf("%s\n", hexResult);

// 2*outputBytes+1 is 2 hex bytes per binary byte, 
// and one character at the end for the string-terminating \0
char hexResult[2*outputBytes+1];
memset(hexResult,0,sizeof(hexResult));
PBKDF2_HMAC_SHA_512_string(pass, salt, iterations, outputBytes, hexResult);
printf("%s\n", hexResult);

对每个用户使用 8 到 16 个二进制字节的随机盐,即 16 到 32 个十六进制数字 - 我的代码还没有生成这个的示例

无论您选择什么,请务必根据测试向量进行验证(其中一些在我的存储库中的 pbkdf2_test.bat/sh 中)。

此外,在您的系统上进行一些基准测试 - 当然是在 PBKDF2-HMAC-SHA-384 和 PBKDF2-HMAC-SHA-512 变体上,在 64 位系统下编译会产生明显更好的结果。将其与my equally poor C++ Crypto++ 和/或my poor C PolarSSL 示例或Jither's C# implementation example 进行比较,具体取决于您的目标系统是什么。

您关心速度的原因是您必须根据生产系统可用的性能与高峰时间登录/创建密码的用户数量相比来选择迭代次数,以免产生太多投诉的缓慢。

攻击者将使用oclHashcat 之类的东西,在具有 8x AMD R9 290Xstock 核心时钟的单台 PC 上,它能够每 30 天尝试 3.4E12 (2^41) 次猜测 PBKDF2-HMAC-SHA-1( SSID为salt,密码,32字节输出长度,4096次迭代,又名WPA/WPA2),或多或少等价于PBKDF2-HMAC-SHA-1(salt,pw,20字节输出长度,8192次迭代)。

如果使用 65536 次迭代,攻击者将只能每 30 天尝试 8.5E11 (~2^39) 次猜测。 如果您使用 1024 次迭代,攻击者将能够改为每 30 天尝试 2.7E13 (~2^44) 次猜测。

当攻击者开始选择他们的攻击时,差异变得很重要。

在这两种情况下,攻击者都会暴力破解非常小的密钥;没有理由不花几分钟甚至几个小时在低垂的果实上。 这是长度为 1-n 的所有十六进制字符,然后是长度为 n+1 到 n+m 的所有可打印字符,然后继续下一行,直到它们位于 n+y 处,并带有硬编码!最后:)。 在这两种情况下,攻击者都会使用小字典和小规则集;说出 184389 个单词的 phpbb 字典和 Best64 规则集 如果您有 1000 个用户,使用 1000 种不同的随机盐,并使用 PBKDF2-HMAC-SHA-1 进行 65536 次迭代,这将占用我们的单台 PC、8 个 GPU 攻击者 (184389*64*1000)/(8.5E11/30) ) 天 = 0.41 天。非常值得! 现在,同样的 phpbb 字典和 4089 条规则的中型 T0X1C 规则集怎么样? 如果您有 1000 个用户,使用 1000 种不同的随机盐,并使用 PBKDF2-HMAC-SHA-1 进行 65536 次迭代,这将占用我们的单台 PC,8 个 GPU 攻击者 (184389*4089*1000)/(8.5E11/30 ) 天 = 26.61 天。 仍然值得,但是这台机器要花将近 4 周的时间进行一次攻击! 如果您有 1000 个用户,使用 1000 种不同的随机盐,并使用 PBKDF2-HMAC-SHA-1 进行 65536 次迭代,这将占用我们的单台 PC、8 个 GPU 攻击者 (184389*4089*1000)/(2.7E13/30 ) 天 = 0.83 天。 完全值得! 现在,同样的 phpbb 字典和 35404 条规则的优秀 d3ead0ne 规则集怎么样? 如果您有 1000 个用户,使用 1000 种不同的随机盐,并且使用 PBKDF2-HMAC-SHA-1 进行了 65536 次迭代,那么我们的单台 PC、8 个 GPU 攻击者 (184389*35404*1000)/(8.5E11/30) ) 天 = 230.39 天。 是的,是时候购买更多机器或在空闲时间进行这项工作了。 如果您有 1000 个用户,使用 1000 种不同的随机盐,并使用 PBKDF2-HMAC-SHA-1 进行 65536 次迭代,这将占用我们的单台 PC、8 个 GPU 攻击者 (184389*4089*1000)/(2.7E13/30 ) 天 = 7.18 天。 值得!只有一周和一顿饭。

现在,对于 PBKDF2,还有其他一些事情需要了解:

对于密码散列,切勿选择大于本机散列大小的二进制输出大小。就个人而言,无论如何我都不推荐二进制输出大小低于 20 字节,因此这对 SHA-1 有点限制。 SHA-1 大小 = 20 字节 SHA-224 为 20 SHA-256 为 20 SHA-384 为 20 SHA-512 为 20 当然,如果哈希不是纯二进制格式,请调整它的存储方式。 原因:PBKDF2 首先运行为一个本机输出大小请求的迭代次数(右侧的数字,上图)。如果您想要更多,它会重新运行整个迭代计数。如果你想要的更少,它会被截断。 攻击者只会匹配第一个本机大小 - 如果第一个字节匹配,是的,那就是密码,所以最好增加迭代次数。

【讨论】:

生成盐只需为每个密码调用 RAND_bytes(salt, saltSize) 并记住将其与摘要一起存储

以上是关于将 PBKDF2 与 OpenSSL 库一起使用的主要内容,如果未能解决你的问题,请参考以下文章

将 OpenSSL RSA 密钥与 .Net 一起使用

使用PBKDF2密钥派生来正确创建用户可读的salt with dry-crypto

将“-servername”参数与 openssl s_client 一起使用

如何将 CUDA 与 vaex(一个 Python 库)一起使用

Java 中带有 bouncycastle 的 PBKDF2

将静态库添加到 podspec