PBKDF2 密钥派生算法:从原理到实践的密钥派生技术(附源码)

前言:为什么我们需要 "密码炼金术"?

想象你有一块普通的铁块(就像用户设置的简单密码 "123456"),直接用它来制作保险箱的锁芯显然太脆弱 —— 小偷用锤子几下就能砸开。但如果把这块铁交给炼金术士:

他会先加入一种独特的催化剂(盐值),这种催化剂每次使用都不同;然后放进熔炉反复锤炼 10 万次(迭代运算),原本脆弱的铁块最终会变成坚硬的合金(高熵密钥)。即使小偷知道你用的是铁块,也无法在短时间内复制出这种合金,因为他既不知道催化剂的配方,也耗不起 10 万次锤炼的时间成本。

PBKDF2 就扮演着 "密码炼金术士" 的角色:它不改变你记忆中的原始密码,却能通过科学的 "锻造工艺",把简单密码转化为足以保护重要信息的加密密钥。

密码安全的三大矛盾

  1. 记忆难度与安全性的矛盾:人类擅长记忆简单模式(如 "password"),但这类密码熵值极低

  2. 存储便利性与抗破解性的矛盾:直接存储密码不安全,单纯哈希又容易被暴力破解

  3. 计算效率与防护强度的矛盾:系统需要快速验证密码,而黑客正利用这种高效性发起攻击

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. 盐值处理:将盐值与块索引(从 1 开始)拼接,形成每个块的输入
  2. 迭代哈希:对每个块执行 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 代码关键点解析

  1. 参数校验:首先检查输入参数的有效性,避免空指针和无效迭代次数

  2. 块计算

    • 根据输出密钥长度和 PRF 输出长度计算所需块数(blocks)
    • 每个块索引从 1 开始,采用大端格式拼接在盐值后
  3. 迭代过程

    • 首次迭代使用盐值 + 块索引作为输入
    • 后续迭代使用前一次的输出作为输入(形成链式依赖)
    • 通过异或运算累积结果,增强随机性
  4. 内存管理:使用临时缓冲区存储中间结果,避免重复分配

四、实际应用指南

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 安全最佳实践

  1. 盐值管理

    • 使用加密安全的随机数生成器(如 /dev/urandom)
    • 盐值应与派生密钥一同存储,不要硬编码
  2. 迭代次数调整

    • 定期测试系统性能,动态调整迭代次数
    • 推荐每次系统升级时重新评估并可能增加迭代次数
  3. 密钥存储

    • 派生密钥应加密存储或使用硬件安全模块(HSM)
    • 避免在日志或调试信息中泄露密钥相关数据

五、安全性分析

5.1 抵御的攻击类型

  • 彩虹表攻击:通过盐值的唯一性,使预计算的哈希表失效
  • 暴力破解:高迭代次数增加了单次尝试的计算成本
  • 字典攻击:同样因迭代次数高而显著降低攻击效率

5.2 潜在风险

  • 迭代次数设置过低会降低安全性
  • 使用弱 PRF(如 HMAC-MD5)可能被攻破
  • 盐值长度不足或可预测会削弱防护效果
  • 密码本身过于简单(如 "123456")仍存在风险

六、总结与展望

PBKDF2 作为经过时间验证的密钥派生算法,在平衡安全性和可用性方面表现出色。其设计思想 —— 通过计算复杂度对抗算力提升 —— 成为现代密码学的重要原则。

随着量子计算的发展,未来可能需要更先进的密钥派生机制,但在可预见的将来,PBKDF2 配合足够的迭代次数和强哈希函数,仍是保护用户密码的可靠选择。

开发者在使用时应牢记:没有绝对安全的算法,只有符合当前安全需求的实现。正确配置参数、遵循最佳实践,才能充分发挥 PBKDF2 的安全防护作用。

参考资料

  1. RFC 2898 - PKCS #5: Password-Based Cryptography Specification Version 2.0
  2. openHiTLS 开源项目:GitCode - 全球开发者的开源社区,开源代码托管平台
  3. NIST Special Publication 800-132 - Recommendation for Password-Based Key Derivation
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值