密钥派生函数(KDF)基础
聊密钥派生之前,我先问个问题:你平时用哈希函数直接处理密码吗?
如果你答「是」,那这篇文章你可得仔细看。我见过太多项目直接把用户密码 SHA256 一下就当密钥用——嗯,这其实是个坑。
KDF 的定义与作用
密钥派生函数(Key Derivation Function,KDF),说白了就是从原始密钥材料中安全地派生出密钥的函数。
什么叫「原始密钥材料」?可能是用户输入的密码、DH 协议交换的共享秘密、或者随机种子。这些材料通常不满足密钥的安全要求——要么熵不够,要么格式不对,要么容易被攻击。
KDF 要干三件事:
- 拉伸:把短密码变成长密钥(比如 8 位密码 → 256 位 AES 密钥)
- 均匀化:让输出比特分布均匀,消除原始材料的统计偏差
- 加固:增加暴力破解成本(通过迭代、加盐等手段)
核心观点:KDF 不是简单的哈希,而是一个带状态、可配置、抗攻击的密钥加工流水线。
我在做国密改造项目时,遇到过客户直接把 SM3 哈希结果当 SM4 密钥用。结果呢?密钥空间严重偏斜,部分密钥比特相关性极高。后来我给他们补了个 KDF 流程,问题才解决。
KDF 在密码协议中的位置
你想想看,一个典型的密码协议里,KDF 通常出现在哪个环节?
我画了张图,帮你理清位置关系:
从图上你能看到:KDF 处于密钥材料与具体密码算法之间的桥梁位置。没有它,原始材料直接喂给加密算法,很容易出问题。
我参与过的一个金融系统,TLS 握手后直接用 ECDH 共享秘密的前 16 字节当 AES 密钥。测试环境没问题,生产环境偶尔解密失败。查了半天,发现是共享秘密的高位比特在某些曲线下分布不均。加上 KDF 后,再没出过事。
KDF 与哈希函数的区别与联系
很多人觉得 KDF 就是「哈希函数多跑几轮」。这话对了一半,但漏掉了关键差异。
我整理了一张对比表,你一看就明白:
| 对比维度 | 哈希函数(如 SM3) | 密钥派生函数(KDF) |
|---|---|---|
| 核心目标 | 数据完整性校验 | 安全派生密钥 |
| 输入 | 任意长度消息 | 密钥材料 + 盐值 + 上下文参数 |
| 输出长度 | 固定(SM3 为 256 位) | 可配置(任意长度) |
| 抗暴力破解 | 设计为快速 | 设计为慢速(迭代/内存硬) |
| 盐值需求 | 不需要 | 必须(防彩虹表) |
| 典型应用 | 数字签名、消息认证 | 密码存储、密钥协商 |
我的经验:选型时记住一句话——哈希函数求快,KDF 求稳。如果你在做密码存储,别用 SM3 直接哈希,用 PBKDF2 或国密标准的 KDF。
那它们有什么联系呢?
- 大多数 KDF 底层基于哈希函数构建(比如 PBKDF2 用 HMAC-SM3)
- KDF 继承了哈希函数的单向性和抗碰撞性
- KDF 在哈希基础上增加了迭代、加盐、输出控制等机制
说白了,KDF 是哈希函数的「增强版」——保留了哈希的安全特性,又补上了密钥派生所需的额外功能。
避坑指南:我曾经见过一个项目,用 SM3 对密码做 10000 次迭代,以为这就是 KDF。结果呢?没有加盐,没有输出长度控制,彩虹表一打一个准。记住:迭代 ≠ KDF,盐值不可少。
最后说个实际例子。国密标准 GM/T 0006 中定义的 KDF,底层用的是 SM3。它的工作流程大致是:
输入:密钥材料 Z,密钥长度 L,其他参数
过程:
1. 初始化计数器 ct = 0x00000001
2. 循环计算:
D_i = SM3(Z || ct || 其他参数)
ct++
3. 拼接 D_1 || D_2 || ... || D_n
4. 截取前 L 位作为输出密钥
你看,底层是 SM3,但多了计数器、拼接、截断——这就是 KDF 的典型结构。
嗯,KDF 的基础就聊到这儿。记住它的定位:密钥材料到密钥之间的安全转换器。后面章节我们会深入国密标准中的具体 KDF 实现,到时候再细聊。