4.14 RNG与密钥派生:密码系统的“熵源“
RNG:随机数生成器
随机数生成器(RNG)是密码系统的核心组件:
RNG作用:
密码系统中的随机数用途:
│
├── 密钥生成
│ ├── AES密钥
│ ├── RSA密钥(P、Q素数)
│ ├── ECC密钥(私钥值d)
│
├── 加密操作
│ ├── CBC IV
│ ├── GCM nonce
│ ├── OAEP填充随机数
│ ├── PSS salt
│
├── 签名操作
│ ├── DSA/ECDSA随机数k
│ ├── RSA-PSS salt
│
└── 其他
│ ├── Token序列号
│ ├── Object UUID
│ ├── PIN salt
RNG的安全要求
密码学安全的RNG(CSPRNG)必须满足:
CSPRNG安全要求:
1. 不可预测性
├── 已知过去的输出,无法预测未来输出
├── 已知部分输出,无法预测其他部分
└── 统计测试无法区分与真随机
2. 不可回溯
├── 即使内部状态泄露
├── 无法从当前状态回溯过去的输出
└── 需要前向安全性
3. 熵源充足
├── 熵池必须有足够的熵
├── 熵来源多样(系统事件、硬件)
└── 定期补充熵
4. 状态保护
├── 内部状态保密
├── 状态更新不可逆
└── 状态泄露后可恢复
RNG抽象类
SoftHSM2定义了RNG抽象接口:
struct RNGImpl
{
enum Type
{
Default,
System, // 系统RNG(/dev/urandom)
Botan, // Botan RNG
OpenSSL // OpenSSL RNG
};
};
class RNG
{
public:
RNG();
virtual ~RNG() { }
// 生成随机字节
virtual bool generateRandom(ByteString& output, size_t length) = 0;
// 生成随机数(指定范围)
virtual unsigned long generateRandomRange(unsigned long min, unsigned long max);
// 添加熵
virtual bool addEntropy(const ByteString& entropy);
// 获取RNG状态
virtual bool isHealthy() = 0;
};
BotanRNG实现
BotanRNG使用Botan AutoSeeded_RNG:
class BotanRNG : public RNG
{
public:
BotanRNG();
virtual ~BotanRNG();
virtual bool generateRandom(ByteString& output, size_t length);
virtual bool addEntropy(const ByteString& entropy);
virtual bool isHealthy();
private:
Botan::AutoSeeded_RNG* rng;
};
BotanRNG::BotanRNG()
{
// Botan自动种子RNG
// 从系统熵源自动收集种子
rng = new Botan::AutoSeeded_RNG();
}
bool BotanRNG::generateRandom(ByteString& output, size_t length)
{
Botan::SecureVector<Botan::byte> random = rng->random_vec(length);
output = ByteString(random.begin(), random.size());
return true;
}
bool BotanRNG::addEntropy(const ByteString& entropy)
{
// Botan RNG会自动从系统收集熵
// 也可以手动添加
rng->add_entropy(entropy.byte_str(), entropy.size());
return true;
}
bool BotanRNG::isHealthy()
{
// Botan AutoSeeded_RNG始终健康
// 因为它会自动从系统熵源重新种子
return true;
}
OSSLRNG实现
OSSLRNG使用OpenSSL RAND API:
class OSSLRNG : public RNG
{
public:
OSSLRNG();
virtual ~OSSLRNG();
virtual bool generateRandom(ByteString& output, size_t length);
virtual bool addEntropy(const ByteString& entropy);
virtual bool isHealthy();
private:
bool initialized;
};
OSSLRNG::OSSLRNG()
{
// OpenSSL RNG初始化
// 会自动从系统熵源种子
initialized = (RAND_status() == 1);
}
bool OSSLRNG::generateRandom(ByteString& output, size_t length)
{
unsigned char* buffer = new unsigned char[length];
// 使用RAND_bytes生成密码安全随机数
int ret = RAND_bytes(buffer, length);
if (ret != 1) {
// RAND_bytes失败,可能熵不足
delete[] buffer;
return false;
}
output = ByteString(buffer, length);
delete[] buffer;
return true;
}
bool OSSLRNG::addEntropy(const ByteString& entropy)
{
// 添加熵到OpenSSL RNG
RAND_add(entropy.byte_str(), entropy.size(), entropy.size());
// 检查熵状态
initialized = (RAND_status() == 1);
return true;
}
bool OSSLRNG::isHealthy()
{
// 检查OpenSSL RNG状态
return (RAND_status() == 1);
}
系统熵源
系统熵源提供随机性的来源:
系统熵源:
Linux/Unix:
├── /dev/urandom
│ ├── 内核熵池
│ ├── 阻塞较少(推荐)
│ └── 从硬件中断、键盘、鼠标收集熵
│
├── /dev/random
│ ├── 内核熵池(阻塞)
│ ├── 熵不足时阻塞
│ └── 适合高安全需求
│
├── getrandom() syscall
│ ├── 新的内核接口
│ ├── 更安全
│ └── 不依赖文件系统
│
└── RDRAND/RDSEED(Intel CPU)
├── 硬件随机数
├── 高速
└── 可作为熵源
Windows:
├── CryptGenRandom
│ ├── CryptoAPI接口
│ └── 从系统收集熵
│
└── BCryptGenRandom
├── 新的CNG接口
├── 更安全
└── 推荐使用
密钥派生函数(KDF)
密钥派生函数从主密钥派生子密钥:
KDF用途:
密钥派生场景:
│
├── 密钥层次结构
│ ├── 主密钥 → 子密钥
│ ├── 不同用途不同密钥
│ ├── 加密密钥、MAC密钥分开
│
├── ECDH结果处理
│ ├── 共享密钥 → 对称密钥
│ ├── 添加上下文信息
│
├── PBKDF(密码派生)
│ ├── 用户密码 → 密钥
│ ├── 加Salt防止字典攻击
│
└── TLS密钥派生
├── 主密钥 → 会话密钥
├── 客户端/服务端密钥分开
HKDF:基于HMAC的KDF
HKDF是最常用的密钥派生函数:
HKDF结构(RFC 5869):
HKDF = HKDF-Extract + HKDF-Expand
HKDF-Extract(提取):
┌─────────────────────────────────────────────┐
│ │
│ 输入: │
│ ├── IKM:输入密钥材料 │
│ ├── Salt:盐值(可选,建议提供) │
│ │
│ 计算: │
│ PRK = HMAC-Hash(Salt, IKM) │
│ │
│ 输出: │
│ PRK:伪随机密钥(Hash长度) │
│ │
└─────────────────────────────────────────────┘
HKDF-Expand(扩展):
┌─────────────────────────────────────────────┐
│ │
│ 输入: │
│ ├── PRK:伪随机密钥 │
│ ├── Info:上下文信息(可选) │
│ ├── L:输出长度 │
│ │
│ 计算: │
│ T(0) = 空字符串 │
│ T(1) = HMAC-Hash(PRK, T(0) || Info || 0x01)│
│ T(2) = HMAC-Hash(PRK, T(1) || Info || 0x02)│
│ ... │
│ T(N) = HMAC-Hash(PRK, T(N-1) || Info || N) │
│ │
│ 输出: │
│ OKM = T(1) || T(2) || ... || T(N) │
│ 截取前L字节 │
│ │
└─────────────────────────────────────────────┘
PKCS#11 HKDF机制
PKCS#11 v3.1定义了CKM_HKDF机制:
/* CK_HKDF_PARAMS结构(PKCS#11 v3.1) */
typedef struct CK_HKDF_PARAMS {
CK_BBOOL bExtract; // 是否执行Extract
CK_BBOOL bExpand; // 是否执行Expand
CK_MECHANISM_TYPE prfHashMechanism; // PRF Hash机制
CK_ULONG ulSaltType; // Salt类型
CK_BYTE_PTR pSalt; // Salt数据
CK_ULONG ulSaltLen; // Salt长度
CK_OBJECT_HANDLE hSaltKey; // Salt密钥Handle
CK_BYTE_PTR pInfo; // Info数据
CK_ULONG ulInfoLen; // Info长度
} CK_HKDF_PARAMS;
/* Salt类型(位掩码风格,PKCS#11 v3.0标准) */
#define CKF_HKDF_SALT_NULL 0x00000001UL // 无Salt
#define CKF_HKDF_SALT_DATA 0x00000002UL // Salt数据
#define CKF_HKDF_SALT_KEY 0x00000004UL // Salt密钥对象
使用示例:
/* HKDF密钥派生示例 */
CK_HKDF_PARAMS params;
CK_OBJECT_HANDLE hBaseKey;
CK_OBJECT_HANDLE hDerivedKey;
// 设置参数
params.bExtract = CK_TRUE;
params.bExpand = CK_TRUE;
params.prfHashMechanism = CKM_SHA256_HMAC; // PRF哈希机制类型
params.ulSaltType = CKF_HKDF_SALT_DATA;
params.pSalt = salt;
params.ulSaltLen = saltLen;
params.hSaltKey = CK_INVALID_HANDLE;
params.pInfo = "encryption key";
params.ulInfoLen = strlen("encryption key");
CK_MECHANISM mechanism = {CKM_HKDF, ¶ms, sizeof(params)};
// 派生密钥
CK_ATTRIBUTE derivedTemplate[] = {
{CKA_CLASS, &secretKeyClass, sizeof(secretKeyClass)},
{CKA_KEY_TYPE, &aesKeyType, sizeof(aesKeyType)},
{CKA_VALUE_LEN, &keyLen, sizeof(keyLen)},
};
rv = C_DeriveKey(hSession, &mechanism, hBaseKey,
derivedTemplate, 3, &hDerivedKey);
HKDF实现
SoftHSM2的HKDF实现:
bool BotanHKDF::deriveKey(SymmetricKey** ppKey,
const ByteString& ikm,
const ByteString& salt,
const ByteString& info,
size_t length,
HashAlgo::Type hash)
{
// 使用Botan HKDF实现
std::string hashName;
switch (hash) {
case HashAlgo::SHA256:
hashName = "SHA-256";
break;
case HashAlgo::SHA384:
hashName = "SHA-384";
break;
case HashAlgo::SHA512:
hashName = "SHA-512";
break;
default:
return false;
}
// Botan HKDF函数
Botan::SecureVector<Botan::byte> okm;
okm.resize(length);
Botan::hkdf(hashName,
okm.begin(), length,
ikm.byte_str(), ikm.size(),
salt.byte_str(), salt.size(),
info.byte_str(), info.size());
// 创建派生密钥
SymmetricKey* key = new AESKey(length * 8);
key->setKeyBits(ByteString(okm.begin(), okm.size()));
*ppKey = key;
return true;
}
bool OSSLHKDF::deriveKey(SymmetricKey** ppKey,
const ByteString& ikm,
const ByteString& salt,
const ByteString& info,
size_t length,
HashAlgo::Type hash)
{
// 使用OpenSSL EVP_KDF实现
EVP_KDF* kdf = EVP_KDF_fetch(NULL, "HKDF", NULL);
EVP_KDF_CTX* ctx = EVP_KDF_CTX_new(kdf);
// 设置参数
OSSL_PARAM params[5];
params[0] = OSSL_PARAM_construct_utf8_string("digest", "SHA256", 0);
params[1] = OSSL_PARAM_construct_octet_string("key", ikm.byte_str(), ikm.size());
params[2] = OSSL_PARAM_construct_octet_string("salt", salt.byte_str(), salt.size());
params[3] = OSSL_PARAM_construct_octet_string("info", info.byte_str(), info.size());
params[4] = OSSL_PARAM_construct_end();
unsigned char* buffer = new unsigned char[length];
EVP_KDF_derive(ctx, buffer, length, params);
// 创建派生密钥
SymmetricKey* key = new AESKey(length * 8);
key->setKeyBits(ByteString(buffer, length));
delete[] buffer;
EVP_KDF_CTX_free(ctx);
EVP_KDF_free(kdf);
*ppKey = key;
return true;
}
PBKDF2:密码派生
PBKDF2从密码派生密钥:
PBKDF2结构(RFC 2898):
输入:
├── P:密码
├── S:Salt
├── c:迭代次数
├── dkLen:派生密钥长度
├── PRF:伪随机函数(HMAC)
计算:
F(P, S, c, i) = U1 ^ U2 ^ ... ^ Uc
其中:
├── U1 = PRF(P, S || INT(i))
├── U2 = PRF(P, U1)
├── ...
├── Uc = PRF(P, Uc-1)
输出:
DK = F(1) || F(2) || ... 截取dkLen字节
迭代次数建议:
├── 最小:10000次
├── 推荐:100000次
├── 高安全:1000000次
/* PKCS#11 PBKDF2 */
CK_MECHANISM mechanism = {CKM_PKCS5_PBKD2, ¶ms, sizeof(params)};
CK_PKCS5_PBKD2_PARAMS params;
params.saltSource = CKZ_SALT_SPECIFIED;
params.pSaltSourceData = salt;
params.ulSaltSourceDataLen = saltLen;
params.iterations = 100000;
params.prf = CKP_PKCS5_PBKD2_HMAC_SHA256;
params.pPrfData = NULL;
params.ulPrfDataLen = 0;
// 派生密钥
rv = C_DeriveKey(hSession, &mechanism, hPasswordKey,
template, count, &hDerivedKey);
ECDH密钥派生
ECDH结果需要经过KDF处理:
ECDH + KDF流程:
Alice Bob
│ │
│ 1. 生成临时密钥 │
│ d_A, Q_A │ d_B, Q_B
│ │
│ 2. 交换公钥 │
│ 发送 Q_A ──────────────────→ │
│ │
│←─────────────────────────────── Q_B
│ │
│ 3. 计算共享密钥 │
│ K_A = d_A * Q_B │ K_B = d_B * Q_A
│ │
│ 4. KDF处理 │
│ 共享密钥K │ 共享密钥K
│ │ │
│ │ HKDF │ HKDF
│ │ Salt, Info │ Salt, Info
│ ▼ │ ▼
│ AES-256密钥 │ AES-256密钥
│ │
│ 最终双方获得相同密钥
一个类比:水源与分流
水源与分流类比:
RNG(水源)
│
├── 系统熵源(自然水源)
│ ├── 雨水(硬件中断)
│ ├── 河流(键盘鼠标)
│ ├── 地下水(系统事件)
│ ├── 汇聚到水库(熵池)
│
├── CSPRNG(净水厂)
│ ├── 收集原水(熵)
│ ├── 处理净化(算法)
│ ├── 输出净水(随机数)
│ ├── 持续供应(自动种子)
│
└── 输出(自来水)
├── 密钥生成(新管道)
├── IV生成(分流)
├── 填充随机数(阀门)
├── 签名随机数k(计量)
KDF(分流系统)
│
├── 主密钥(主水管)
│ ├── 高压水源
│ ├── 安全存储
│
├── HKDF(分流阀)
│ ├── Extract:降压处理
│ ├── Expand:分流输出
│ ├── Info标签:管道标识
│
├── 派生密钥(分支管道)
│ ├── 加密密钥管道
│ ├── MAC密钥管道
│ ├── 不同用途分开
│
└── 安全保证
├── 主管道压力一致
├── 分支管道独立
├── 一个分支断不影响其他
本篇小结
今天我们分析了SoftHSM2的RNG与密钥派生实现。
RNG安全要求:
- 不可预测性
- 不可回溯
- 熵源充足
- 状态保护
RNG抽象类:
- generateRandom:生成随机字节
- addEntropy:添加熵
- isHealthy:健康检查
BotanRNG:
- AutoSeeded_RNG
- 自动从系统收集熵
- C++原生API
OSSLRNG:
- RAND_bytes
- RAND_status健康检查
- RAND_add添加熵
系统熵源:
- Linux:/dev/urandom, getrandom()
- Windows:CryptGenRandom, BCryptGenRandom
- Intel:RDRAND/RDSEED
HKDF结构:
- Extract:HMAC(Salt, IKM) → PRK
- Expand:HMAC(PRK, T||Info||i) → OKM
CK_HKDF_PARAMS:
- bExtract/bExpand:控制流程
- prfHashMechanism:PRF Hash
- Salt类型:NULL/Data/Key
- Info:上下文信息
PBKDF2:
- 密码 + Salt + 迭代
- HMAC迭代计算
- 防止字典攻击
ECDH + KDF:
- 计算共享密钥
- HKDF处理
- 添加上下文信息
下一节,我们将总结SoftHSM2的分析,看看开源实现给我们的启示。
【下集预告】
SoftHSM2分析总结?
开源与闭源的区别?
厂商为什么闭源?
开源实现的启示?
下一节,从开源到闭源。