You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenSSL 1.0至3.0伪随机函数(PRF)移植后输出不一致问题

OpenSSL 3.0 PRF移植问题:第二次迭代输出与1.1版本不一致

将符合RFC5246第5节定义的*伪随机函数(PRF)*从OpenSSL 1.1版本移植到3.0版本时出现异常:使用相同输入数据调用新旧版本的PRF实现,第二次迭代的输出结果不一致。

代码实现

OpenSSL 1.1 API实现

#include <algorithm>
#include <ios>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "openssl/core_names.h"
#include "openssl/hmac.h"

// Pseudo Random Function, see: RFC5246 Section 5

// use older v1.1 API
bool PRF_Hash_11(const EVP_MD* hashmethod, const std::vector<uint8_t>& secret, const std::vector<uint8_t>& seed, size_t outputlen, std::vector<uint8_t>& output)
{
    if (hashmethod == 0)
        return false;

    output.clear();

    if (outputlen == 0)
    {
        return true;
    }

    //calculate how big the output needs to be
    int size = EVP_MD_size(hashmethod);
    size_t iterations = (outputlen / size);

    if ((outputlen % size) != 0)
        ++iterations;

    output.resize(size * iterations);

    std::vector<uint8_t> An(EVP_MAX_MD_SIZE);
    unsigned int accumulatorLen = 0;
    unsigned int offset = 0;
    unsigned int outputsize = 0;

    HMAC_CTX* ctx = HMAC_CTX_new();
    HMAC_Init_ex(ctx, secret.data(), static_cast<int>(secret.size()), hashmethod, 0);

    //this context generates A(n)
    HMAC_CTX* ctx_hash = HMAC_CTX_new();
    HMAC_Init_ex(ctx_hash, secret.data(), static_cast<int>(secret.size()), hashmethod, 0);

    //calculate A(1)
    HMAC_Update(ctx_hash, seed.data(), seed.size());
    HMAC_Final(ctx_hash, An.data(), &accumulatorLen);

    for (size_t i = 1; i <= iterations; ++i)
    {
        //reinitialize the hash for the next iteration
        HMAC_Init_ex(ctx, 0, 0, 0, 0);

        HMAC_Update(ctx, An.data(), accumulatorLen);
        HMAC_Update(ctx, seed.data(), seed.size());
        HMAC_Final(ctx, output.data() + offset, &outputsize);

        offset += outputsize;

        if (i < iterations) {
            //calculate the next A(i)
            HMAC_Init_ex(ctx_hash, 0, 0, 0, 0);
            HMAC_Update(ctx_hash, An.data(), accumulatorLen);
            HMAC_Final(ctx_hash, An.data(), &accumulatorLen);
        }
    }

    output.resize(outputlen);

    HMAC_CTX_free(ctx);
    HMAC_CTX_free(ctx_hash);

    return true;
}

OpenSSL 3.0 API实现

// use 3.0 API
bool PRF_Hash_30(const char* digname, int hash_size, const std::vector<uint8_t>& secret, const std::vector<uint8_t>& seed, size_t outputlen, std::vector<uint8_t>& output)
{
    output.clear();
    if (outputlen == 0)
    {
        return true;
    }

    //calculate how big the output needs to be
    size_t iterations = (outputlen / hash_size);

    if ((outputlen % hash_size) != 0)
        ++iterations;

    output.resize(hash_size * iterations);

    auto* ossl_ctxt = OSSL_LIB_CTX_new();
    EVP_MAC* hmac = EVP_MAC_fetch(ossl_ctxt, "HMAC", NULL);
    if (hmac == NULL) { return false; }

    OSSL_PARAM params[2];
    params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, (char*)digname, 0);
    params[1] = OSSL_PARAM_construct_end();

    std::vector<uint8_t> An(EVP_MAX_MD_SIZE);
    size_t accumulatorLen = 0;
    size_t offset = 0;
    size_t outputsize = 0;

    // working context
    auto* wctx = EVP_MAC_CTX_new(hmac);
    if (wctx == NULL) { return false; }
    int res = EVP_MAC_init(wctx, secret.data(), secret.size(), params);

    // this context generates A(n)
    auto* hctx = EVP_MAC_CTX_new(hmac);
    if (hctx == NULL) { return false; }
    res = EVP_MAC_init(hctx, secret.data(), secret.size(), params);

    //calculate A(1)
    res = EVP_MAC_update(hctx, seed.data(), seed.size());
    res = EVP_MAC_final(hctx, An.data(), &accumulatorLen, EVP_MAX_MD_SIZE);

    for (size_t i = 1; i <= iterations; ++i)
    {
        res = EVP_MAC_init(wctx, 0, 0, params); //reinitialize the hash for the next iteration
        res = EVP_MAC_update(wctx, An.data(), accumulatorLen);
        res = EVP_MAC_update(wctx, seed.data(), seed.size());
        res = EVP_MAC_final(wctx, output.data() + offset, &outputsize, EVP_MAX_MD_SIZE);

        offset += outputsize;

        if (i < iterations) {
            //calculate the next A(i)
            res = EVP_MAC_init(hctx, 0, 0, params); //reinitialize the hash for the next iteration
            res = EVP_MAC_update(hctx, An.data(), accumulatorLen);
            res = EVP_MAC_final(hctx, An.data(), &accumulatorLen, EVP_MAX_MD_SIZE);
        }
    }

    output.resize(outputlen);

    EVP_MAC_CTX_free(hctx);
    EVP_MAC_CTX_free(wctx);
    EVP_MAC_free(hmac);
    OSSL_LIB_CTX_free(ossl_ctxt);

    return true;
}

测试用例

int main()
{
    const std::vector<uint8_t> secret {
        0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff,
        0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};

    const std::vector<uint8_t> seed {
        0x6b, 0x65, 0x79, 0x20, 0x65, 0x78, 0x70, 0x61, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0xb1, 0x26, 0x07, 0x8d, 0xec, 0x68, 0x2e, 0x69, 0xe7, 0x6d, 0xc1, 0xaa, 0x88, 0xcb, 0xfe, 0xa5, 0x94, 0xd9, 0x82,
        0xa6, 0x0f, 0x2d, 0x32, 0xa0, 0xde, 0x7e, 0x55, 0x8d, 0xf8, 0x1b, 0x83, 0x80, 0xfa, 0x42, 0xa3, 0x0b, 0x6c, 0xad, 0xbf, 0x24, 0x78, 0xb2, 0xbb, 0xfa, 0xa3, 0x5a, 0x98, 0x84, 0xe4, 0x54, 0x10,
        0x2b, 0x46, 0xc0, 0x91, 0x34, 0x96, 0x2e, 0x64, 0x34, 0x73, 0xba, 0x3d, 0xa1};

    // use SHA-384 and 72 bytes out for this test
    const EVP_MD* hashmethod = EVP_sha384();    
    int hash_size = EVP_MD_size(hashmethod);
    const char * digname = OSSL_DIGEST_NAME_SHA2_384;
    constexpr size_t bufsize = 0x48;    // # output bytes needed

    std::vector<uint8_t> output_v1, output_v3;
    output_v1.reserve(bufsize);
    output_v3.reserve(bufsize);

    // generate output using old and new API
    bool ret = PRF_Hash_11(hashmethod, secret, seed, output_v1.capacity(), output_v1);
    ret = PRF_Hash_30(digname, hash_size, secret, seed, output_v3.capacity(), output_v3);

    // print output
    auto prnthex = [](std::vector<uint8_t> v) {
        std::stringstream stream;
        for (auto e : v) {
            stream << ' ' << std::hex << (e / 16) << (e & 0x0F);
        }
        std::cout << stream.str() << std::endl;
    };

    prnthex(output_v1);
    prnthex(output_v3);

    auto pr = std::mismatch(output_v1.begin(), output_v1.end(), output_v3.begin(), output_v3.end());
    std::cout << std::endl << " mismatch at output[" << std::distance(output_v1.begin(), pr.first) << ']' << std::endl;
}

测试输出

21 50 34 fe f0 c6 de 4b e8 e6 df 65 21 36 11 a0 24 30 18 a5 42 74 d8 e5 5f 82 41 ed 4a ef 55 27 e0 b6 c4 fc 95 a8 10 be 35 b9 94 06 3a c9 59 56 4c 4c 2c 2a 01 d2 38 c3 a7 80 40 20 b3 5a 98 7f f3 9d 9c 72 96 b0 61 39
21 50 34 fe f0 c6 de 4b e8 e6 df 65 21 36 11 a0 24 30 18 a5 42 74 d8 e5 5f 82 41 ed 4a ef 55 27 e0 b6 c4 fc 95 a8 10 be 35 b9 94 06 3a c9 59 56 80 50 68 12 4e 46 25 e0 47 91 98 10 08 68 0c ba fa 35 73 9f b1 63 13 af

mismatch at output[48]

作为OpenSSL新手,我无法定位问题所在,且感觉OpenSSL文档难以理解,希望能得到帮助。

内容的提问来源于stack exchange,提问作者darnell_a

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 08:41:57