HMAC DRBG incorrect output

I’m trying to implement HMAC DRBG and I’m using CTR_DRBG as a reference.

My calls are as follows:

mbedtls_hmac_drbg_seed( &hmac_ctx, md_info, mbedtls_test_entropy_func, entropy_inputs, nonce_pers, strlen(nonce_pers) );
mbedtls_hmac_drbg_set_prediction_resistance(&hmac_ctx, MBEDTLS_CTR_DRBG_PR_ON );
mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, addInput1, addInputLength);
mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, addInput2, addInputLength);

I’ve printed the offset of index used for mbedtls_test_entropy_func() after every call. Here are the values:

**For SHA-1:**
md size = 20
Before mbedtls_hmac_drbg_seed() : 0
After mbedtls_hmac_drbg_seed() : 24
After mbedtls_hmac_drbg_random_with_add 1() : 40

========================================

**For SHA-256:**
md size = 32
Before mbedtls_hmac_drbg_seed() : 0
After mbedtls_hmac_drbg_seed() : 48
After mbedtls_hmac_drbg_random_with_add 1() : 80

Within 1 request file I’ve tests with SHA-1. SHA-256 and SHA-512. So I should be able to test any variant of SHA.
The #defines in config file are shown below:

#define MBEDTLS_SHA1_C
#define MBEDTLS_ENTROPY_FORCE_SHA256
#define MBEDTLS_SHA512_C

The offsets don’t seem right to me. I’m using the same mbedtls_test_entropy_func() as I used for CTR_DRBG ( I’ve replicated mbedtls_test_entropy_func () from ctr_drbg_validate_internal ()).

Hi @athorath

Thank you for your question!

Are you using the hmac_drbg as reference for your tests? If so, note that the entropy_context used should also change.

The entropy length in hmac_drbg is not identical as in ctr_drbg.

As you can see from the code, calling mbedtls_hmac_drbg_seed() will have the following definitions:

    /*
     * See SP800-57 5.6.1 (p. 65-66) for the security strength provided by
     * each hash function, then according to SP800-90A rev1 10.1 table 2,
     * min_entropy_len (in bits) is security_strength.
     *
     * (This also matches the sizes used in the NIST test vectors.)
     */
    ctx->entropy_len = md_size <= 20 ? 16 : /* 160-bits hash -> 128 bits */
                       md_size <= 28 ? 24 : /* 224-bits hash -> 192 bits */
                       32;  /* better (256+) -> 256 bits */

Which calls hmac_drbg_reseed_core() with use_nonce = 1 , so it calls f_entropy twice:

    if( ( ret = ctx->f_entropy( ctx->p_entropy,
                                seed, ctx->entropy_len ) ) != 0 )

and

     * from the entropy source. See Sect 8.6.7 in SP800-90A. */
    if( use_nonce )
    {
        /* Note: We don't merge the two calls to f_entropy() in order
         *       to avoid requesting too much entropy from f_entropy()
         *       at once. Specifically, if the underlying digest is not
         *       SHA-1, 3 / 2 * entropy_len is at least 36 Bytes, which
         *       is larger than the maximum of 32 Bytes that our own
         *       entropy source implementation can emit in a single
         *       call in configurations disabling SHA-512. */
        if( ( ret = ctx->f_entropy( ctx->p_entropy,
                                    seed + seedlen,
                                    ctx->entropy_len / 2 ) ) != 0 )

so, for SHA1 hmac_drbg, your offset should be 16 + 8 = 24 , and for SHA256, it should be 32 + 16 = 48 , as the results you are seeing. The reasoning for these values are in the standards mentioned in the comments.
Regards,
Mbed TLS Support
Ron

@roneld01: Thanks for the wonderful explanation.
Now I’ve updated my code to look like hmac_drbg you mentioned in the comment above. I’m using hmac_drbg_pr() as my reference.

My mbedtls_test_entropy_func() now matches whatever is in that file. Apart from that, all my calls match hmac_drbg_pr() from the file you mentioned.

    mbedtls_hmac_drbg_init (&hmac_ctx);
    md_info = mbedtls_md_info_from_type( MBEDTLS_MD_SHA1 );
    p_entropy.p = entropy_inputs;
    p_entropy.len = strlen(entropy_inputs);

    rc = mbedtls_hmac_drbg_seed( &hmac_ctx, md_info, mbedtls_test_entropy_func, entropy_inputs, nonce_pers, strlen(nonce_pers) );
    
    mbedtls_hmac_drbg_set_prediction_resistance(&hmac_ctx, MBEDTLS_HMAC_DRBG_PR_ON );
    
    rc = mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, addInput1, addInputLength);
    rc = mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, addInput2, addInputLength);

It exactly matches hmac_drbg_pr(). However, I get an error in entropy function at line memcpy( buf, ctx->p, len );

I’ve printed the values:

(gdb) print len
$2 = 24
(gdb) print ctx->len
$3 = 11840012698478436789

ctx->len seems to be wrong. May be that’s what is causing the error?

entropy_ctx *ctx = (entropy_ctx *) data;
This line is not assigning anything to ctx->len is it?
In mbedtls_test_entropy_func() we just declare ctx of type entropy_ctx and assign data to it but nothing is assigned to ctx->len.

Also wanted to check one more thing.
Can you please confirm what’s the answer you should be getting while using the below inputs (listed what responses I get with OpenSSL and mbedTLS):

[SHA-1]
[PredictionResistance = True]
[EntropyInputLen = 128]
[NonceLen = 64]
[PersonalizationStringLen = 0]
[AdditionalInputLen = 0]
[ReturnedBitsLen = 640]

COUNT = 0
EntropyInput = 6878f9ea4ff71310b5e1eb74d12c50a4
Nonce = 9edc884c73de0637
PersonalizationString =
AdditionalInput =
EntropyInputPR = c924feaafcfacaaf76e558351963eddc
AdditionalInput =
EntropyInputPR = a9704c677723b480c10c878636167c0d
mbedtls_ReturnedBits = 459a3ea43f958d3000edf286e718238049740011fe061dc55ccde3cd575170c508ebc78178f79a926344d074fb4d5e19e89f4fc4c388f851ecd8e2a154f6ef3939dbd5e481095cce614c9aa2399987c3
openssl_ReturnedBits = f6a4a5e37fd88c6a444346d035d5a50b4e00522e28cc0aad9893cb1ca03fcb8474142bf3f40e5c391e81826240420f22415ef7bf9142add94925d53d93078927b00c1fe0b20a9387facd95fcf9ab541a

@roneld01: Hi, can you please confirm if this sounds right?
For SHA-1, after mbedtls_hmac_drbg_seed(), the entropy offset is 16+8 which is 24 (like you said in your previous comment) and that’s what I see.
And after first call to mbedtls_hmac_drbg_random_with_add() the offset is increased by 16, so the total is 40.
Does that sound right? That means for second mbedtls_hmac_drbg_random_with_add() there’s just 8 bytes left?

Consider this example:

EntropyInput = 6878f9ea4ff71310b5e1eb74d12c50a4
Nonce = 9edc884c73de0637
PersonalizationString =
AdditionalInput =
EntropyInputPR = c924feaafcfacaaf76e558351963eddc
AdditionalInput =
EntropyInputPR = a9704c677723b480c10c878636167c0d

So the entropy input would be [6878f9ea4ff71310b5e1eb74d12c50a4c924feaafcfacaaf76e558351963eddca9704c677723b480c10c878636167c0d].

So for the mbedtls_hmac_drbg_seed() 6878f9ea4ff71310b5e1eb74d12c50a4c924feaafcfacaaf is used (24 bytes).
For first mbedtls_hmac_drbg_random_with_add() 76e558351963eddca9704c677723b480 is used (16 bytes).
For second mbedtls_hmac_drbg_random_with_add() just c10c878636167c0d is left (8 bytes).

Hi @athorath
As for your comparison between OpenSSL and Mbed TLS results, you should be comparing with test vectors, as it is not an efficient way to know where and if there is an issue.

And after first call to mbedtls_hmac_drbg_random_with_add() the offset is increased by 16, so the total is 40. Does that sound right?

Yes, the initial seeding uses nonce, which adds additional 8 bytes to the entropy, while the mbedtls_hmac_drbg_random_with_add() calls mbedtls_hmac_drbg_reseed() in case PR is on. Please try callingmbedtls_hmac_drbg_set_prediction_resistance() with MBEDTLS_HMAC_DRBG_PR_OFF and compare the outputs wiht openSSL.

Regards,
Mbed TLS Support
Ron

@roneld01: Thanks for the comment.
I agree with your statement that I should be comparing with test vector. I’m looking at test_suite_hmac_drbg.pr.data for reference and uncovered a problem where I was not considering the nonce. So that suggestion helped me with one of the issues.
Now I’m using the same inputs as mentioned in the above file. Updated my request file from test_vector. I’m using the first test instance. The one where input would be “a0c9ab58f1e2e5a4de3ebd4ff73e9c5b64efd8ca028cf81148a584fe69ab5aee42aa4d42176099d45e1397dc404d86a37bf55954756951e4” ( both in test_vector and test_suite_hmac_drbg.pr.data)

So with this update, I think my offsets are perfectly matching what you told earlier.

DEBUG entropy_inputs : [a0c9ab58f1e2e5a4de3ebd4ff73e9c5b64efd8ca028cf81148a584fe69ab5aee42aa4d42176099d45e1397dc404d86a37bf55954756951e4]

Before mbedtls_hmac_drbg_seed() : 0

After mbedtls_hmac_drbg_seed() : 24

After mbedtls_hmac_drbg_random_with_add 1() : 40

After mbedtls_hmac_drbg_random_with_add 2() : 56

However, the output should’ve been “9a00a2d00ed59bfe31ecb1399b608148d1969d250d3c1e94101098129325cab8fccc2d54731970c0107aa4892519955e4bc6001d7f4e6a2bf8a301ab46055c09a67188f1a740eef3e15c029b44af0344” but I get “e419b73378e43b4a450aa8472e2c3bd70a5f71a160b2f76597489224f2af83e8f600abdb13bf9c3ae60990428fe29aa1352265b4a3fa658a696e60a94415f09303f4083aeb1ad6b11cd645383e0a33a3”.

I’m doing the prediction resistance true scenario hence cannot turn it off. But apart from that my calls match hmac_drbg_pr(). Please let me know if you spot any errors. I can’t seem to find anything wrong at all but still get incorrect result.

rc = mbedtls_hmac_drbg_seed( &hmac_ctx, md_info, mbedtls_test_entropy_func, entropy_inputs, nonce_pers, strlen(nonce_pers) );
mbedtls_hmac_drbg_set_prediction_resistance(&hmac_ctx, MBEDTLS_HMAC_DRBG_PR_ON );
rc = mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, params->addInput1, params->addInputLength);
rc = mbedtls_hmac_drbg_random_with_add(&hmac_ctx, result, resultLen, params->addInput2, params->addInputLength);

I can’t stress enough how helpful your comments have been. THANK YOU!

Hi @athorath
I do see some issues, but they should not affect this specific test, as both additional input lengths are 0, and nonce_pers is also empty string.
However, the issues I see are:

  1. nonce_pers shoujld not be measured with strlen. ALthough it is a personelization string, you should consider it as a hex binary, and use a length parameter, in case there is hte zero bytes as part of the string. Since in this test, the personelization string length is 0, it shouldn’t affect this failure.
    2.For both additional input,s you use the same length :params->addInputLength. Is this always the case? Anyway, in this specific use case, the additional input lengths are 0 as well, so it shouldn’t effect.

Since, as you said, this test vector is also used in the test_suite_hmac_drbg.pr successfully, you should compare your code with the code in the test used by Mbed TLS.

  1. Have you called mbedtls_hmac_drbg_init( &hmac_ctx ); ?
  2. Have you verified that in fact strlen(nonce_pers) is 0?
  3. Have you verified that params->addInputLength is 0?
  4. Have you checked the two return codes (rc) in your calls?

Regards

  1. Have you called mbedtls_hmac_drbg_init( &hmac_ctx ); ? Yes I have
  2. Have you verified that in fact strlen(nonce_pers) is 0? nonce_pers is a concatenation of nonce and personalization string. In my testcase, personalization string is 0 length but nonce is not null (shown below);
  3. Have you verified that params->addInputLength is 0? Yes
  4. Have you checked the two return codes ( rc ) in your calls? (shown below)

nonce_pers : [9edc884c73de0637]

nonce_pers length : 8

params->addInputLength : 0

rc from mbedtls_hmac_drbg_seed : 0

rc from mbedtls_hmac_drbg_random_with_add 1 : 0

rc from mbedtls_hmac_drbg_random_with_add 2 : 0

Input :

EntropyInput = 6878f9ea4ff71310b5e1eb74d12c50a4
Nonce = 9edc884c73de0637
PersonalizationString =
AdditionalInput =
EntropyInputPR = c924feaafcfacaaf76e558351963eddc
AdditionalInput =
EntropyInputPR = a9704c677723b480c10c878636167c0d

My entropy would be 6878f9ea4ff71310b5e1eb74d12c50a49edc884c73de0637c924feaafcfacaaf76e558351963eddca9704c677723b480c10c878636167c0d

Format of aboev entropy string : EntropyInput || Nonce_Pers || EntropyInputPR || EntropyInputPR

Hi @athorath
Thank you for your information.

Note that the initial seeding should receive only the PersonalizationString (which is length 0 in your case), and the input for the test, as Entropy, should receive only the nonce, meaning:
EntropyInput || Nonce || EntropyInputPR || EntropyInputPR

Is this what you did?
So, I believe the issue you are having is becasue you are sending the nonce to mbedtls_hmac_drbg_seed() as well.
Regards

Sorry for the late response.

Correct the format I’m using is EntropyInput || Nonce_Pers || EntropyInputPR || EntropyInputPR

In the above example "Nonce_Pers " contains only Nonce as pers string is 0 length. But if pers string length is not 0, then it’ll be (nonce||pers_string).

Regarding your comment:

I agree that could be the issue. I’ve passed in NULL and results looks okay.
I’ll try it out for other scenarios.

@roneld01: Thanks a lot for all your help. I got my issue resolved with your latest comment.