Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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, &params, 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, &params, 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分析总结?

开源与闭源的区别?

厂商为什么闭源?

开源实现的启示?

下一节,从开源到闭源。