Hi,
I've now merge Daiki's ML-KEM implementation, see
https://git.lysator.liu.se/nettle/nettle/-/merge_requests/67.
I think there's some further changes I think I'd like to do before
release:
1. Add more randomized tests, and add assert_maybe for some of the
invariants, in particular for arithmetics.
2. Probably remove the ml_kem_params from the api, and instead use
separate functions for ML-KEM 768 and ML-KEM 1024.
3. Add functions exposing structs for expanded keys. To avoid having to
expand them over and over again if using the same key repeatedly. And
for all-in-one functions, these structs could also be used as the
types for needed scratch space, to make things a bit more
type/alignment safe, and have a natural way to allocate needed
storage without the _itch functions.
Should then be done in a consistent way also for sntrup. (The reason
sntrup api doesn't have any itch function is that its expanded keys
are smaller, and it's more reasonable to just allocate them on the
stack).
4. Micro optimize various internals.
Regards,
/Niels
--
Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677.
Internet email is subject to wholesale government surveillance.
Hi,
I'm trying to implement chacha20-poly1305 as used by SSH
(https://datatracker.ietf.org/doc/html/draft-ietf-sshm-chacha20-poly1305-04),
which differs from RFC 8439's ChaCha-Poly1035 as implemented by Nettle.
While Nettle internally has the necessary building blocks, it lacks a
public API to directly use poly1305. I think this is fairly
straightforward to add one though. Please see below for my initial
attempt. If you think this is the right approach, I'd be happy to work
on a proper patch including tests and documentation.
#define POLY1305_KEY_SIZE 32
#define POLY1305_DIGEST_SIZE 16
struct poly1305_mac_ctx {
struct poly1305_ctx pctx;
union nettle_block16 s;
uint8_t block[POLY1305_BLOCK_SIZE];
unsigned index;
};
void
poly1305_set_key(struct poly1305_mac_ctx* ctx, const uint8_t *key)
{
_nettle_poly1305_set_key(&ctx->pctx, key);
memcpy(ctx->s.b, key + 16, 16);
ctx->index = 0;
}
void
poly1305_update(struct poly1305_mac_ctx* ctx, size_t length, const
uint8_t *data)
{
ctx->index = _nettle_poly1305_update(&ctx->pctx, ctx->block,
ctx->index, length, data);
}
/* After calling this function, context must be re-keyed before
calling poly1305_update/poly1305_digest again. */
void
poly1305_digest(struct poly1305_mac_ctx* ctx, uint8_t* digest)
{
if (ctx->index > 0)
{
ctx->block[ctx->index] = 1;
memset (ctx->block + ctx->index + 1,
0, POLY1305_BLOCK_SIZE - 1 - ctx->index);
_nettle_poly1305_block (&ctx->pctx, ctx->block, 0);
}
_nettle_poly1305_digest(&ctx->pctx, &ctx->s);
memcpy(digest, ctx->s.b, POLY1305_DIGEST_SIZE);
}
Regards,
Tim
Dear Nettle maintainers,
We have identified a security issue in Nettle’s GCM implementation. An adversary can exploit this to recover the authentication subkey H and forge arbitrary messages with valid tags. We provide the detail below.
Recall GCM:
Given: Key K, Nonce IV, Plaintext P, Additional authenticated data A
Step 1. Compute the subkey H: H = AES_K(0^128).
Step 2. Compute the initial counter block J0: If len(IV) == 96 bits: J0 = IV||0^31||1 Else: s = 128 * ceil(len(IV)/128) - len(IV) J0 = GHASH_H(IV||0^(s+64)||[len(IV)]_64) where [len(IV)]_64 is the 64-bit big‑endian representation of the bit length of IV.
Step 3. Generate the keystream: The counter blocks are inc_32(J0), inc_32(inc_32(J0)), ... where inc_32 increments the lowest 32 bits modulo 2^32. Each counter block is encrypted with AES under key K to produce the keystream blocks. The plaintext blocks are XORed with the keystream blocks to obtain ciphertext C.
Step 4. Compute the authentication tag: S = GHASH_H( A||C||[len(A)]_64||[len(C)]_64 ), T = S XOR AES_K(J0).
What happens when the nonce (IV) is empty (Corresponding to this part of the code https://github.com/gnutls/nettle/blob/master/gcm.c ).
let len(IV) = 0 bits.
Step 1. H = AES_K(0^128)
Step 2. Compute J0:
s = 128 * ceil(0/128) - 0 = 0
The input to GHASH is: IV||0^(s+64)||[len(IV)]_64 = (empty)||0^64||0^64 = 0^128.
So J0 = GHASH_H(0^128) = 0^128. (The GHASH accumulator starts at zero, XOR with the zero block remains zero, then multiply by H gives zero. So GHASH_H(0^128) = 0^128.)
Step 4. Authentication tag:
The mask used to XOR the GHASH result is AES_K(J0) = AES_K(0^128).
But by definition this is exactly H. Therefore: T = S XOR H (where S = GHASH_H(A||C||length block)).
This equation contains only one unknown variable: H. All other values (A, C, lenblock, T) are known to an attacker.
Now, GHASH_H is a polynomial function in H over GF(2^128). For m blocks of input (A, C, and the length block), the GHASH result can be expressed as: S = B1 * H^m XOR B2 * H^(m-1) XOR ... XOR Bm * H where the B_i are known from the input blocks. Therefore the tag equation becomes: B1 * H^m XOR B2 * H^(m-1) XOR ... XOR (Bm XOR 1) * H XOR T = 0. This is a polynomial equation of degree m in H over the finite field GF(2^128).
Once the attacker recovers H, they have the authentication subkey. They can now compute valid GHASH for any chosen A' and C' and generate a valid tag for any forged message: T' = GHASH_H(A'||C'||lenblock) XOR H.
We suggest following the NIST standard and prohibiting an empty IV.
Sincerely,
Feng Li and Yaobin Shen