Zoltan Fridrich <zfrid...@redhat.com> writes:

> sorry for bothering but I would like to ask you to review the updated
> balloon hashing patch. I have fixed all of the issues you mentioned and I
> would like to continue working on it until it gets approved for Nettle.
> Could you please take a look? Attaching the updated patch again, just in
> case.

Thanks for your patience. A new round of comments below.

> diff --color -ruNp a/balloon.c b/balloon.c
> --- a/balloon.c       1970-01-01 01:00:00.000000000 +0100
> +++ b/balloon.c       2022-08-19 11:11:11.591188729 +0200
> @@ -0,0 +1,127 @@
> +/* balloon.c - Balloon password-hashing algorithm
> +

Would be good with a comment providing a link to authoritative
specification. Here, and/or in the header file. (When design is
finished, it also needs a section in the reference manual).

> +static inline void
> +hash(const struct nettle_hash *alg,
> +     uint64_t cnt,
> +     size_t a_len, const uint8_t *a,
> +     size_t b_len, const uint8_t *b,
> +     uint8_t *result)

Is there a good reason to declare this and other utility function
inline? May be better to leave that to the compiler, depending on number
of call sites, size of generated code, etc.

> +{
> +    uint8_t ctx[NETTLE_MAX_HASH_CONTEXT_SIZE];

I'm afraid this won't guarantee required alignment for the context
struct. If you have to allocate it, you need to use the
TMP_DECLARE_ALIGN / TMP_ALLOC_ALIGN macros.

That said, maybe you can avoid allocating it at all, and leave that to
the caller (more on that below).

> +static inline void
> +hash_ints(const struct nettle_hash *alg,
> +          uint64_t i, uint64_t j, uint64_t k, uint8_t *result)
> +{
> +    uint8_t ctx[NETTLE_MAX_HASH_CONTEXT_SIZE];
> +    uint8_t tmp[sizeof(uint64_t)];
> +
> +    alg->init(ctx);
> +    LE_WRITE_UINT64(tmp, i);
> +    alg->update(ctx, sizeof(tmp), tmp);
> +    LE_WRITE_UINT64(tmp, j);
> +    alg->update(ctx, sizeof(tmp), tmp);
> +    LE_WRITE_UINT64(tmp, k);
> +    alg->update(ctx, sizeof(tmp), tmp);
> +    alg->digest(ctx, alg->digest_size, result);
> +}

It will reduce overhead a bit if you process all three numbers with a
single update call, 

  uint8_t tmp[24];
  LE_WRITE_UINT64(tmp, i);
  LE_WRITE_UINT64(tmp + 8, j);
  LE_WRITE_UINT64(tmp + 16, k);
  ...update...

> +static inline unsigned
> +block_to_int(size_t length, const uint8_t *block, unsigned mod)

I think this function deserves a comment. 

> +{
> +    unsigned r = 0;
> +
> +    for (int i = length - 1; i >= 0; --i) {

Minor nit, but in nettle, loops like this are usually written with an
unsigned loop variable, according to this pattern:

   unsigned i;
   for (i = length; i-- > 0; )

Also, code should stick to c89, so the loop variable must be declared
outside of the for statement.

> +        r <<= 8;

What's the valid range for mod? Assuming unsigned is 32 bits, this could
overflow if mod is larger than 24 bits. Would be good to back up
limitations with asserts.

> +        r += (block[i] & 0xFF);

The & 0xFF looks redundant, since block[i] is uint8_t.

> +        r %= mod;

Division is a pretty expensive operation. If this loop is in any way
performance critical, there are ways to speed up this arithmetic.

> +    }
> +    return r;
> +}
> +
> +void
> +balloon(size_t passwd_len, const uint8_t *passwd,
> +        size_t salt_len, const uint8_t *salt,
> +        unsigned s_cost, unsigned t_cost, 
> +        const struct nettle_hash *alg,
> +        uint8_t *buf, uint8_t *result)
> +{

For consistency, can you see if you can make the interface/function
signature closer to the pbkdf2 function (see pbkdf2.h)? It takes a
pointer to a mac context, already initialized by the caller (in this
case, it would instead be a hash context), function pointers for update
and digest, and the digest size.

Note that the digest function will always reset the context to its
initial state, and if I read the code correctly, you need only a single
context, if you reuse it.

What are reasonable values for the s_cost and t_cost? Will an unsigned
(usually 32 bits, but might be as small as 16 on some compilers for
embedded systems) always be large enough? Alternatives include uint32_t,
uint64_t (likely much overkill? But in principle, I guess one could run
ballon with a buffer size of a few dozen GB?) and size_t. 

> +size_t
> +balloon_buf_size(const struct nettle_hash *alg, unsigned s_cost)
> +{
> +    return s_cost * alg->digest_size;
> +}

I think one of the operands to the multiplication operator should be
cast to size_t, to avoid overflow when size_t is larger than an
unsigned.

I'd like to think a bit more on the name of this function. Maybe
ballon_scratch_size, or (to follow a somewhat silly convention used by
some gmp and nettle functions), balloon_itch.

> +void
> +test_main(void)
> +{
> +    test_balloon(8, "hunter42", 11, "examplesalt", 1024, 3, &nettle_sha256,
> +                 
> SHEX("716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb"));
> +    test_balloon(0, "", 4, "salt", 3, 3, &nettle_sha256,
> +                 
> SHEX("5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378"));
> +    test_balloon(8, "password", 0, "", 3, 3, &nettle_sha256,
> +                 
> SHEX("20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc"));
> +    test_balloon(1, "", 1, "", 3, 3, &nettle_sha256,
> +                 
> SHEX("4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4"));
> +    test_balloon(8, "password", 4, "salt", 1, 1, &nettle_sha256,
> +                 
> SHEX("eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545"));
> +}

It would be good to document the source of these test vectors.

Regards,
/Niels

-- 
Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677.
Internet email is subject to wholesale government surveillance.
_______________________________________________
nettle-bugs mailing list -- nettle-bugs@lists.lysator.liu.se
To unsubscribe send an email to nettle-bugs-le...@lists.lysator.liu.se

Reply via email to