前言:为什么我们需要 "密码炼金术"?
想象你有一块普通的铁块(就像用户设置的简单密码 "123456"),直接用它来制作保险箱的锁芯显然太脆弱 —— 小偷用锤子几下就能砸开。但如果把这块铁交给炼金术士:
他会先加入一种独特的催化剂(盐值),这种催化剂每次使用都不同;然后放进熔炉反复锤炼 10 万次(迭代运算),原本脆弱的铁块最终会变成坚硬的合金(高熵密钥)。即使小偷知道你用的是铁块,也无法在短时间内复制出这种合金,因为他既不知道催化剂的配方,也耗不起 10 万次锤炼的时间成本。
PBKDF2 就扮演着 "密码炼金术士" 的角色:它不改变你记忆中的原始密码,却能通过科学的 "锻造工艺",把简单密码转化为足以保护重要信息的加密密钥。
密码安全的三大矛盾
-
记忆难度与安全性的矛盾:人类擅长记忆简单模式(如 "password"),但这类密码熵值极低
-
存储便利性与抗破解性的矛盾:直接存储密码不安全,单纯哈希又容易被暴力破解
-
计算效率与防护强度的矛盾:系统需要快速验证密码,而黑客正利用这种高效性发起攻击
PBKDF2 的巧妙之处在于,它让合法用户只需付出一次 "锤炼成本"(登录时的计算),却让黑客为每一个猜测的密码都付出同等成本 —— 这就像给合法用户发了一把快速通行证,却给黑客设置了十万道关卡。
引言:为什么需要密钥派生函数?
在现代密码学中,用户通常使用容易记忆的密码(如生日、单词组合等),但这些密码往往熵值较低,直接作为加密密钥存在严重安全隐患。密钥派生函数(Key Derivation Function,KDF)的作用就是将这些低熵密码转换为高熵的加密密钥,同时通过特定机制抵御暴力破解和字典攻击。
PBKDF2(Password-Based Key Derivation Function 2)是目前应用最广泛的密钥派生函数之一,由 RSA 实验室提出并被纳入 RFC 2898 标准。它通过迭代哈希运算和盐值(salt)机制,显著提高了密码破解的难度,被广泛应用于操作系统登录、数据库加密、数字货币钱包等安全领域。
一、PBKDF2 的核心原理
1.1 基本定义(基于 RFC 2898)
根据 RFC 2898 定义,PBKDF2 的数学表达式如下:
plaintext
PBKDF2(PRF, Password, Salt, c, dkLen) -> DK
其中各参数含义:
- PRF:伪随机函数(Pseudorandom Function),通常使用 HMAC(如 HMAC-SHA256)
- Password:用户输入的密码(字节序列)
- Salt:随机生成的盐值(至少 8 字节),用于防止彩虹表攻击
- c:迭代次数(正整数),决定计算强度
- dkLen:期望输出的密钥长度
- DK:派生得到的密钥
1.2 工作流程解析
PBKDF2 的核心思想是 "通过计算复杂度换取安全性",其工作流程可分为三个阶段:
- 盐值处理:将盐值与块索引(从 1 开始)拼接,形成每个块的输入
- 迭代哈希:对每个块执行 c 次 PRF 运算,形成中间结果(T_i)
- 第一次迭代:PRF (Password, Salt || i)
- 后续迭代:PRF (Password, 前一次迭代结果)
3.结果拼接:将所有块的中间结果拼接,取前 dkLen 字节作为最终密钥
这种设计使得攻击者必须为每个可能的密码执行 c 次哈希运算,当 c 足够大时(如 10 万次以上),暴力破解的成本会显著增加。
二、关键参数的安全选择
2.1 盐值(Salt)
- 必须随机生成,推荐长度至少 16 字节(128 位)
- 不同用户应使用不同盐值,且需与派生密钥一同存储
- 无需保密,但必须保证唯一性
2.2 迭代次数(c)
- 没有固定标准,需根据系统性能动态调整
- 原则:在可接受的响应时间内尽可能大(如现代系统推荐 10 万 - 100 万次)
- 需定期评估并增加,应对硬件算力提升
2.3 伪随机函数(PRF)
- 推荐使用 HMAC-SHA256 或更安全的哈希算法
- 避免使用已被破解的算法(如 HMAC-MD5、HMAC-SHA1)
- 算法选择需结合系统兼容性和安全需求
三、开源实现解析(基于 openHiTLS)
openHiTLS 是一款开源的密码学工具库,其 PBKDF2 实现遵循 RFC 2898 标准,具有良好的可移植性和安全性。以下是核心代码解析:
3.1 核心函数实现
3.2 代码关键点解析
-
参数校验:首先检查输入参数的有效性,避免空指针和无效迭代次数
-
块计算:
- 根据输出密钥长度和 PRF 输出长度计算所需块数(blocks)
- 每个块索引从 1 开始,采用大端格式拼接在盐值后
-
迭代过程:
- 首次迭代使用盐值 + 块索引作为输入
- 后续迭代使用前一次的输出作为输入(形成链式依赖)
- 通过异或运算累积结果,增强随机性
-
内存管理:使用临时缓冲区存储中间结果,避免重复分配
四、实际应用指南
4.1 正确使用示例(C 语言)
#include "hitls_build.h"
#ifdef HITLS_CRYPTO_PBKDF2
#include <stdint.h>
#include "securec.h"
#include "bsl_err_internal.h"
#include "bsl_sal.h"
#include "crypt_local_types.h"
#include "crypt_errno.h"
#include "crypt_utils.h"
#include "crypt_algid.h"
#include "eal_mac_local.h"
#include "crypt_ealinit.h"
#include "pbkdf2_local.h"
#include "bsl_params.h"
#include "crypt_params_key.h"
#include "crypt_pbkdf2.h"
#define PBKDF2_MAX_BLOCKSIZE 64
#define PBKDF2_MAX_KEYLEN 0xFFFFFFFF
static const uint32_t PBKDF_ID_LIST[] = {
CRYPT_MAC_HMAC_MD5,
CRYPT_MAC_HMAC_SHA1,
CRYPT_MAC_HMAC_SHA224,
CRYPT_MAC_HMAC_SHA256,
CRYPT_MAC_HMAC_SHA384,
CRYPT_MAC_HMAC_SHA512,
CRYPT_MAC_HMAC_SM3,
CRYPT_MAC_HMAC_SHA3_224,
CRYPT_MAC_HMAC_SHA3_256,
CRYPT_MAC_HMAC_SHA3_384,
CRYPT_MAC_HMAC_SHA3_512,
};
struct CryptPbkdf2Ctx {
CRYPT_MAC_AlgId macId;
EAL_MacMethod macMeth;
uint32_t mdSize;
void *macCtx;
uint8_t *password;
uint32_t passLen;
uint8_t *salt;
uint32_t saltLen;
uint32_t iterCnt;
#ifdef HITLS_CRYPTO_PROVIDER
void *libCtx;
#endif
bool hasGetMdSize;
};
bool CRYPT_PBKDF2_IsValidAlgId(CRYPT_MAC_AlgId id)
{
return ParamIdIsValid(id, PBKDF_ID_LIST, sizeof(PBKDF_ID_LIST) / sizeof(PBKDF_ID_LIST[0]));
}
int32_t CRYPT_PBKDF2_U1(const CRYPT_PBKDF2_Ctx *pCtx, uint32_t blockCount, uint8_t *u, uint32_t *blockSize)
{
int32_t ret;
const EAL_MacMethod *macMeth = &pCtx->macMeth;
void *macCtx = pCtx->macCtx;
(void)macMeth->reinit(macCtx);
if ((ret = macMeth->update(macCtx, pCtx->salt, pCtx->saltLen)) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
/* processing the big endian */
uint32_t blockCnt = CRYPT_HTONL(blockCount);
if ((ret = macMeth->update(macCtx, (uint8_t *)&blockCnt, sizeof(blockCnt))) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
if ((ret = macMeth->final(macCtx, u, blockSize)) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_Un(const CRYPT_PBKDF2_Ctx *pCtx, uint8_t *u, uint32_t *blockSize, uint8_t *t, uint32_t tLen)
{
int32_t ret;
const EAL_MacMethod *macMeth = &pCtx->macMeth;
void *macCtx = pCtx->macCtx;
macMeth->reinit(macCtx);
if ((ret = macMeth->update(macCtx, u, *blockSize)) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
if ((ret = macMeth->final(macCtx, u, blockSize)) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
DATA_XOR(t, u, t, tLen);
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_CalcT(const CRYPT_PBKDF2_Ctx *pCtx, uint32_t blockCount, uint8_t *t, uint32_t *tlen)
{
uint8_t u[PBKDF2_MAX_BLOCKSIZE] = {0};
uint8_t tmpT[PBKDF2_MAX_BLOCKSIZE] = {0};
uint32_t blockSize = PBKDF2_MAX_BLOCKSIZE;
int32_t ret;
uint32_t iterCnt = pCtx->iterCnt;
/* U1 = PRF(Password, Salt + INT_32_BE(i))
tmpT = U1 */
ret = CRYPT_PBKDF2_U1(pCtx, blockCount, u, &blockSize);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
(void)memcpy_s(tmpT, PBKDF2_MAX_BLOCKSIZE, u, blockSize);
for (uint32_t un = 1; un < iterCnt; un++) {
/* t = t ^ Un */
ret = CRYPT_PBKDF2_Un(pCtx, u, &blockSize, tmpT, blockSize);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
}
uint32_t len = (*tlen > blockSize) ? blockSize : (*tlen);
(void)memcpy_s(t, *tlen, tmpT, len);
*tlen = len;
BSL_SAL_CleanseData(u, PBKDF2_MAX_BLOCKSIZE);
BSL_SAL_CleanseData(tmpT, PBKDF2_MAX_BLOCKSIZE);
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_GenDk(const CRYPT_PBKDF2_Ctx *pCtx, uint8_t *dk, uint32_t dkLen)
{
uint32_t curLen;
uint8_t *t = dk;
uint32_t tlen;
uint32_t i;
int32_t ret;
ret = pCtx->macMeth.init(pCtx->macCtx, pCtx->password, pCtx->passLen, NULL);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
/* DK = T1 + T2 + ⋯ + Tdklen/hlen */
for (i = 1, curLen = dkLen; curLen > 0; i++) {
tlen = curLen;
ret = CRYPT_PBKDF2_CalcT(pCtx, i, t, &tlen);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
curLen -= tlen;
t += tlen;
}
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_HMAC(void *libCtx, const EAL_MacMethod *macMeth, CRYPT_MAC_AlgId macId, const EAL_MdMethod *mdMeth,
const uint8_t *key, uint32_t keyLen,
const uint8_t *salt, uint32_t saltLen,
uint32_t iterCnt, uint8_t *out, uint32_t len)
{
int32_t ret;
CRYPT_PBKDF2_Ctx pCtx;
if (macMeth == NULL || mdMeth == NULL || out == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if (key == NULL && keyLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
// add keyLen limit based on rfc2898
if (mdMeth->mdSize == 0 || (keyLen / mdMeth->mdSize) >= PBKDF2_MAX_KEYLEN) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
if (salt == NULL && saltLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if ((len == 0) || (iterCnt == 0)) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
void *macCtx = macMeth->newCtx(libCtx, macId);
if (macCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return CRYPT_MEM_ALLOC_FAIL;
}
pCtx.macMeth = *macMeth;
pCtx.macCtx = macCtx;
pCtx.password = (uint8_t *)(uintptr_t)key;
pCtx.passLen = keyLen;
pCtx.salt = (uint8_t *)(uintptr_t)salt;
pCtx.saltLen = saltLen;
pCtx.iterCnt = iterCnt;
ret = CRYPT_PBKDF2_GenDk(&pCtx, out, len);
macMeth->deinit(macCtx);
macMeth->freeCtx(macCtx);
macCtx = NULL;
return ret;
}
CRYPT_PBKDF2_Ctx *CRYPT_PBKDF2_NewCtx(void)
{
CRYPT_PBKDF2_Ctx *ctx = BSL_SAL_Calloc(1, sizeof(CRYPT_PBKDF2_Ctx));
if (ctx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return NULL;
}
return ctx;
}
CRYPT_PBKDF2_Ctx *CRYPT_PBKDF2_NewCtxEx(void *libCtx)
{
(void)libCtx;
CRYPT_PBKDF2_Ctx *ctx = CRYPT_PBKDF2_NewCtx();
if (ctx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return NULL;
}
#ifdef HITLS_CRYPTO_PROVIDER
ctx->libCtx = libCtx;
#endif
return ctx;
}
int32_t CRYPT_PBKDF2_SetMacMethod(CRYPT_PBKDF2_Ctx *ctx, const CRYPT_MAC_AlgId id)
{
#ifdef HITLS_CRYPTO_ASM_CHECK
if (CRYPT_ASMCAP_Mac(id) != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(CRYPT_EAL_ALG_ASM_NOT_SUPPORT);
return CRYPT_EAL_ALG_ASM_NOT_SUPPORT;
}
#endif
if (!CRYPT_PBKDF2_IsValidAlgId(id)) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
// free the old macCtx
if (ctx->macCtx != NULL) {
if (ctx->macMeth.freeCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_ERR_MAC_METH);
return CRYPT_PBKDF2_ERR_MAC_METH;
}
ctx->macMeth.freeCtx(ctx->macCtx);
ctx->macCtx = NULL;
(void)memset_s(&ctx->macMeth, sizeof(EAL_MacMethod), 0, sizeof(EAL_MacMethod));
}
EAL_MacMethod *macMeth = EAL_MacFindMethod(id, &ctx->macMeth);
if (macMeth == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_EAL_ERR_METH_NULL_MEMBER);
return CRYPT_EAL_ERR_METH_NULL_MEMBER;
}
if (macMeth->newCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_ERR_MAC_METH);
return CRYPT_PBKDF2_ERR_MAC_METH;
}
#ifdef HITLS_CRYPTO_PROVIDER
ctx->macCtx = macMeth->newCtx(ctx->libCtx, id);
#else
ctx->macCtx = macMeth->newCtx(NULL, id);
#endif
if (ctx->macCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return CRYPT_MEM_ALLOC_FAIL;
}
ctx->macId = id;
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_SetPassWord(CRYPT_PBKDF2_Ctx *ctx, const uint8_t *password, uint32_t passLen)
{
if (password == NULL && passLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
BSL_SAL_ClearFree(ctx->password, ctx->passLen);
ctx->password = BSL_SAL_Dump(password, passLen);
if (ctx->password == NULL && passLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return CRYPT_MEM_ALLOC_FAIL;
}
ctx->passLen = passLen;
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_SetSalt(CRYPT_PBKDF2_Ctx *ctx, const uint8_t *salt, uint32_t saltLen)
{
if (salt == NULL && saltLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
BSL_SAL_FREE(ctx->salt);
ctx->salt = BSL_SAL_Dump(salt, saltLen);
if (ctx->salt == NULL && saltLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_MEM_ALLOC_FAIL);
return CRYPT_MEM_ALLOC_FAIL;
}
ctx->saltLen = saltLen;
return CRYPT_SUCCESS;
}
int32_t CRYPT_PBKDF2_SetCnt(CRYPT_PBKDF2_Ctx *ctx, const uint32_t iterCnt)
{
if (iterCnt == 0) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
ctx->iterCnt = iterCnt;
return CRYPT_SUCCESS;
}
static int32_t Pbkdf2GetMdSize(CRYPT_PBKDF2_Ctx *ctx, const char *mdAttr)
{
if (ctx->hasGetMdSize) {
return CRYPT_SUCCESS;
}
void *libCtx = NULL;
#ifdef HITLS_CRYPTO_PROVIDER
libCtx = ctx->libCtx;
#endif
EAL_MdMethod mdMeth = {0};
EAL_MacDepMethod depMeth = {.method = {.md = &mdMeth}};
int32_t ret = EAL_MacFindDepMethod(ctx->macId, libCtx, mdAttr, &depMeth, NULL);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
ctx->mdSize = mdMeth.mdSize;
ctx->hasGetMdSize = true;
return CRYPT_SUCCESS;
}
#ifdef HITLS_CRYPTO_PROVIDER
static int32_t CRYPT_PBKDF2_SetMdAttr(CRYPT_PBKDF2_Ctx *ctx, const char *mdAttr, uint32_t valLen)
{
if (mdAttr == NULL || valLen == 0) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
if (ctx->macCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_ERR_MAC_ID_NOT_SET);
return CRYPT_PBKDF2_ERR_MAC_ID_NOT_SET;
}
// Set mdAttr for macCtx
if (ctx->macMeth.setParam == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_ERR_MAC_METH);
return CRYPT_PBKDF2_ERR_MAC_METH;
}
BSL_Param param[] = {
{.key = CRYPT_PARAM_MD_ATTR, .valueType = BSL_PARAM_TYPE_UTF8_STR,
.value = (void *)(uintptr_t)mdAttr, .valueLen = valLen, .useLen = 0},
BSL_PARAM_END
};
int32_t ret = ctx->macMeth.setParam(ctx->macCtx, param);
if (ret != CRYPT_SUCCESS) {
BSL_ERR_PUSH_ERROR(ret);
return ret;
}
return Pbkdf2GetMdSize(ctx, mdAttr);
}
#endif
int32_t CRYPT_PBKDF2_SetParam(CRYPT_PBKDF2_Ctx *ctx, const BSL_Param *param)
{
uint32_t val = 0;
uint32_t len = 0;
const BSL_Param *temp = NULL;
int32_t ret = CRYPT_PBKDF2_PARAM_ERROR;
if (ctx == NULL || param == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if ((temp = BSL_PARAM_FindConstParam(param, CRYPT_PARAM_KDF_MAC_ID)) != NULL) {
len = sizeof(val);
GOTO_ERR_IF(BSL_PARAM_GetValue(temp, CRYPT_PARAM_KDF_MAC_ID,
BSL_PARAM_TYPE_UINT32, &val, &len), ret);
GOTO_ERR_IF(CRYPT_PBKDF2_SetMacMethod(ctx, val), ret);
}
if ((temp = BSL_PARAM_FindConstParam(param, CRYPT_PARAM_KDF_PASSWORD)) != NULL) {
GOTO_ERR_IF(CRYPT_PBKDF2_SetPassWord(ctx, temp->value, temp->valueLen), ret);
}
if ((temp = BSL_PARAM_FindConstParam(param, CRYPT_PARAM_KDF_SALT)) != NULL) {
GOTO_ERR_IF(CRYPT_PBKDF2_SetSalt(ctx, temp->value, temp->valueLen), ret);
}
if ((temp = BSL_PARAM_FindConstParam(param, CRYPT_PARAM_KDF_ITER)) != NULL) {
len = sizeof(val);
GOTO_ERR_IF(BSL_PARAM_GetValue(temp, CRYPT_PARAM_KDF_ITER,
BSL_PARAM_TYPE_UINT32, &val, &len), ret);
GOTO_ERR_IF(CRYPT_PBKDF2_SetCnt(ctx, val), ret);
}
#ifdef HITLS_CRYPTO_PROVIDER
if ((temp = BSL_PARAM_FindConstParam(param, CRYPT_PARAM_MD_ATTR)) != NULL) {
GOTO_ERR_IF(CRYPT_PBKDF2_SetMdAttr(ctx, temp->value, temp->valueLen), ret);
}
#endif
ERR:
return ret;
}
int32_t CRYPT_PBKDF2_Derive(CRYPT_PBKDF2_Ctx *ctx, uint8_t *out, uint32_t len)
{
if (ctx == NULL || out == NULL || ctx->macCtx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if (ctx->macMeth.deinit == NULL || ctx->macMeth.freeCtx == NULL || ctx->macMeth.init == NULL ||
ctx->macMeth.reinit == NULL || ctx->macMeth.update == NULL || ctx->macMeth.final == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_ERR_MAC_METH);
return CRYPT_PBKDF2_ERR_MAC_METH;
}
if (ctx->password == NULL && ctx->passLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
int32_t ret = Pbkdf2GetMdSize(ctx, NULL);
if (ret != CRYPT_SUCCESS) {
return ret;
}
// add keyLen limit based on rfc2898
if (ctx->mdSize == 0 || (ctx->passLen / ctx->mdSize) >= PBKDF2_MAX_KEYLEN) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
if (ctx->salt == NULL && ctx->saltLen > 0) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if ((len == 0) || (ctx->iterCnt == 0)) {
BSL_ERR_PUSH_ERROR(CRYPT_PBKDF2_PARAM_ERROR);
return CRYPT_PBKDF2_PARAM_ERROR;
}
return CRYPT_PBKDF2_GenDk(ctx, out, len);
}
int32_t CRYPT_PBKDF2_Deinit(CRYPT_PBKDF2_Ctx *ctx)
{
if (ctx == NULL) {
BSL_ERR_PUSH_ERROR(CRYPT_NULL_INPUT);
return CRYPT_NULL_INPUT;
}
if (ctx->macMeth.freeCtx != NULL) {
ctx->macMeth.freeCtx(ctx->macCtx);
ctx->macCtx = NULL;
}
BSL_SAL_ClearFree((void *)ctx->password, ctx->passLen);
BSL_SAL_FREE(ctx->salt);
(void)memset_s(ctx, sizeof(CRYPT_PBKDF2_Ctx), 0, sizeof(CRYPT_PBKDF2_Ctx));
return CRYPT_SUCCESS;
}
void CRYPT_PBKDF2_FreeCtx(CRYPT_PBKDF2_Ctx *ctx)
{
if (ctx == NULL) {
return;
}
if (ctx->macMeth.freeCtx != NULL) {
ctx->macMeth.freeCtx(ctx->macCtx);
}
BSL_SAL_ClearFree((void *)ctx->password, ctx->passLen);
BSL_SAL_FREE(ctx->salt);
BSL_SAL_Free(ctx);
}
#endif // HITLS_CRYPTO_PBKDF2
4.2 安全最佳实践
-
盐值管理:
- 使用加密安全的随机数生成器(如 /dev/urandom)
- 盐值应与派生密钥一同存储,不要硬编码
-
迭代次数调整:
- 定期测试系统性能,动态调整迭代次数
- 推荐每次系统升级时重新评估并可能增加迭代次数
-
密钥存储:
- 派生密钥应加密存储或使用硬件安全模块(HSM)
- 避免在日志或调试信息中泄露密钥相关数据
五、安全性分析
5.1 抵御的攻击类型
- 彩虹表攻击:通过盐值的唯一性,使预计算的哈希表失效
- 暴力破解:高迭代次数增加了单次尝试的计算成本
- 字典攻击:同样因迭代次数高而显著降低攻击效率
5.2 潜在风险
- 迭代次数设置过低会降低安全性
- 使用弱 PRF(如 HMAC-MD5)可能被攻破
- 盐值长度不足或可预测会削弱防护效果
- 密码本身过于简单(如 "123456")仍存在风险
六、总结与展望
PBKDF2 作为经过时间验证的密钥派生算法,在平衡安全性和可用性方面表现出色。其设计思想 —— 通过计算复杂度对抗算力提升 —— 成为现代密码学的重要原则。
随着量子计算的发展,未来可能需要更先进的密钥派生机制,但在可预见的将来,PBKDF2 配合足够的迭代次数和强哈希函数,仍是保护用户密码的可靠选择。
开发者在使用时应牢记:没有绝对安全的算法,只有符合当前安全需求的实现。正确配置参数、遵循最佳实践,才能充分发挥 PBKDF2 的安全防护作用。
参考资料:
- RFC 2898 - PKCS #5: Password-Based Cryptography Specification Version 2.0
- openHiTLS 开源项目:GitCode - 全球开发者的开源社区,开源代码托管平台
- NIST Special Publication 800-132 - Recommendation for Password-Based Key Derivation
83

被折叠的 条评论
为什么被折叠?



