我确实有一个通过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 首先运行为一个本机输出大小请求的迭代次数(右侧的数字,上图)。如果您想要更多,它会重新运行整个迭代计数。如果你想要的更少,它会被截断。
- 攻击者只会匹配第一个本机大小 - 如果第一个字节匹配,是的,那就是密码,所以最好增加迭代次数。