本文介绍了在网络安全中,如何使用 PBKDF2 (Password-Based Key Derivation Function 2) 和 OpenSSL 来安全地从密码生成加密密钥。PBKDF2 通过增加迭代次数来减慢哈希过程,从而提高密码破解的难度,文章通过C代码展示了如何在OpenSSL中使用PBKDF2,并使用测试向量验证了其正确性。
那么,在网络安全中,我们如何定义“基本事实”,例如从给定密码生成的加密密钥是什么,或者给定消息的哈希输出是什么? 很有可能我们通常会求助于 OpenSSL 及其 C 代码实现。 有一种小方法很可能保护你的密码,并使黑客的生活变得困难:强大的 PBKDF2(基于密码的密钥派生函数 2)。 让我们使用 OpenSSL 和 C 实现此功能,并验证输出是否正确。
所有那些图表显示破解密码的哈希版本需要多长时间,它们的定义都是错误的。 大多数将采用快速哈希方法的破解速度并使用它。 通过适当的 KDF(密钥派生函数),我们通常会减慢整个过程,甚至可以达到每秒只能哈希几个密码,而不是数十亿个密码的程度。 因此,如果我们正确选择哈希方法,我们可以让某人极难发现原始密码。 从密码生成加密密钥也是如此。
当我们说我们存储密码的哈希版本时,我们通常不是指我们使用标准哈希方法。 这包括 MD5、SHA-1 和 SHA-256。 原因是这些哈希方法速度很快,并且攻击者每秒可以尝试数十亿个密码并尝试这些密码的哈希版本。 这包括使用盐值。
我们通常使用的方法是使用密钥派生函数 (KDF)。 为此,最广泛使用的方法之一是 PBKDF2(基于密码的密钥派生函数 2)。 它在 RFC 2898 中定义,并生成加盐的哈希。 我们还添加了许多迭代,这减慢了哈希过程,同时也禁用了 GPU 的使用。
通常,这用于从定义的密码创建加密密钥,并且无法从哈希值中反转密码。 PBKDF2 用于 TrueCrypt 中,以生成读取加密驱动器的标头信息所需的密钥,该信息存储加密密钥。 在 WPA-2 中,我们使用 4,096 次迭代。
在 OpenSSL 中,我们定义了 PBKDF2 的参数,例如消息 (msg)、盐值 (salt)、哈希方法(SHA1、SHA256 或 SHA256)以及迭代次数 [ 这里]:
OSSL_PARAM params[5];
if (strcmp(hash,"SHA1")==0) params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha1, strlen(SN_sha1));
else if (strcmp(hash,"SHA512")==0) params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha512, strlen(SN_sha512));
else params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha256, strlen(SN_sha256));
params[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD,
msg,strlen(msg));
params[2] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
salt, strlen(salt));
params[3] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter);
params[4] = OSSL_PARAM_construct_end();
然后我们可以通过以下方式生成给定长度的输出 [ 这里]:
EVP_KDF_derive(kctx, out, len, params);
其中 len 是输出中的字节数。 以下是使用 C 进行的编码 [ 这里]:
##include <stdio.h>
##include <string.h>
##include <stdlib.h>
##include <openssl/evp.h>
##include <openssl/pem.h>
##include <openssl/err.h>
##include <openssl/core_names.h>
##include <openssl/params.h>
##include <openssl/kdf.h>
static void hexdump(const char *label, const unsigned char *buf, size_t len) {
printf("%s (%zu bytes): ", label, len);
for (size_t i = 0; i < len; i++) printf("%02X", buf[i]);
printf("\n");
}
int main(int argc, char** argv) {
unsigned int iter=2048;
EVP_KDF *kdf;
EVP_KDF_CTX *kctx;
unsigned char out[256];
unsigned char *msg="Hello World!";
unsigned char *salt="NaCl";
unsigned char *hash="SHA256";
int len=64;
if (argc > 1) msg = argv[1];
if (argc > 2) salt = argv[2];
if (argc > 3) iter = atoi(argv[3]);
if (argc > 4) hash = argv[4];
if (argc > 5) len = atoi(argv[5]);
kdf = EVP_KDF_fetch(NULL, "PBKDF2", NULL);
kctx = EVP_KDF_CTX_new(kdf);
EVP_KDF_free(kdf);
OSSL_PARAM params[5];
if (strcmp(hash,"SHA1")==0) params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha1, strlen(SN_sha1));
else if (strcmp(hash,"SHA512")==0) params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha512, strlen(SN_sha512));
else params[0] = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha256, strlen(SN_sha256));
params[1] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD,
msg,strlen(msg));
params[2] = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
salt, strlen(salt));
params[3] = OSSL_PARAM_construct_uint32(OSSL_KDF_PARAM_ITER, &iter);
params[4] = OSSL_PARAM_construct_end();
EVP_KDF_derive(kctx, out, sizeof(out), params);
printf("Message: [%s]\n",msg);
printf("Salt: [%s]\n",salt);
printf("Iterations: [%d]\n",iter);
printf("Hash: [%s]\n",hash);
printf("Length: [%d]\n\n",len);
hexdump("KDF: ",out,len);
EVP_KDF_CTX_free(kctx);
return EXIT_SUCCESS;
}
一个示例运行显示 [ 这里]:
Message: [password]
Salt: [NaCl]
Iterations: [2048]
Hash: [SHA256]
Length: [64]
KDF: (64 bytes): 79A033D2E270A68D91127F5E6341505A65EDFF77442E073D4D2041B4B1428D321A82609957D0F89FD61EC98CDD529C3BEA336C6E68EBA133D3F18B0925840D5
一个示例运行如下:
要进行测试,这是一个测试向量 [ 这里]:
Input:
P = "passwordPASSWORDpassword" (24 octets)
S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" (36 octets)
c = 4096
dkLen = 25
Output:
PBKDF2-HMAC-SHA256 = 348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c (25 octets)
PBKDF2-HMAC-SHA512 = 8c0511f4c6e597c6ac6315d8f0362e225f3c501495ba23b868 (25 octets)
如果我们尝试 SHA256:
对于 SHA-512:
你可以看到这两个测试向量都已正确实现。
我们不会使用标准的哈希方法将密码哈希为加密密钥,因为存在安全问题,并且哈希速度很快。 一种改进的方法是使用 PBKDF2,这会减慢整个哈希过程。 你会发现每次连接到 Wi-fi 时,都是 PBKDF2 在保护你的密码。
这是 scrypt 的实现:
https://asecuritysite.com/scrypt/openssl_scrypt
- 原文链接: medium.com/asecuritysite...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!