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
"Feng Li" lifeng8425@stu.xmu.edu.cn writes:
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.
Thanks for the report.
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.
I agree it looks bad that AES_K(J0) (intended to hide the raw ghash value) in this case happen to be the same as the ghash subkey H.
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.
One question on this recovery: I think the polynomial equation can have multiple roots. My abstract algebra is a bit rusty, but I would expect at most m distinct roots, with actual number depending on what the factors of the polynomial look like: each unique linear factor corresponds to a root, and hence both repeated linear factors, and irreducible factors of higher degree, reduce the number of roots. To the attacker, each of the roots is a candidate for H, and as far as I understand, for a successful attack, the attacker has to identify, or guess, the right one. Does that make sense?
We suggest following the NIST standard and prohibiting an empty IV.
I agree this makes a lot of sense. This would be a documentation change, backed up by an
assert (length > 0);
in gcm_set_iv. Does that seem right?
Regards, /Niels
Dear Niels,
Thank you for your prompt and thoughtful response.
One question on this recovery: I think the polynomial equation can have multiple roots. My abstract algebra is a bit rusty, but I would expect at most m distinct roots, with actual number depending on what the factors of the polynomial look like: each unique linear factor corresponds to a root, and hence both repeated linear factors, and irreducible factors of higher degree, reduce the number of roots. To the attacker, each of the roots is a candidate for H, and as far as I understand, for a successful attack, the attacker has to identify, or guess, the right one. Does that make sense?
Yes, you are absolutely correct. The polynomial can have up to m distinct roots, each a candidate for H. An attacker would need to try at most m candidates to recover the correct H. For short messages, e.g., when m = 2, the attacker would need to guess at most 2 times.
I agree this makes a lot of sense. This would be a documentation change, backed up by an
assert (length > 0);
in gcm_set_iv. Does that seem right?
We agree with this approach. We believe updating the documentation to explicitly require a non‑zero IV length is necessary. As for the code change, we suggest adding assert(length > 0); at the beginning of the function body of gcm_set_iv in nettle/gcm.c.
Sincerely, Feng and Yaobin
-----Original Message----- From: "Niels Möller" nisse@lysator.liu.se Sent:Saturday, 08/08/2026 04:52:58 To: "Feng Li" lifeng8425@stu.xmu.edu.cn Cc: nettle-bugs@lists.lysator.liu.se, "yaobin.shen@xmu.edu.cn" yaobin.shen@xmu.edu.cn Subject: Re: Vulnerability Report
"Feng Li" lifeng8425@stu.xmu.edu.cn writes:
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.
Thanks for the report.
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.
I agree it looks bad that AES_K(J0) (intended to hide the raw ghash value) in this case happen to be the same as the ghash subkey H.
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.
One question on this recovery: I think the polynomial equation can have multiple roots. My abstract algebra is a bit rusty, but I would expect at most m distinct roots, with actual number depending on what the factors of the polynomial look like: each unique linear factor corresponds to a root, and hence both repeated linear factors, and irreducible factors of higher degree, reduce the number of roots. To the attacker, each of the roots is a candidate for H, and as far as I understand, for a successful attack, the attacker has to identify, or guess, the right one. Does that make sense?
We suggest following the NIST standard and prohibiting an empty IV.
I agree this makes a lot of sense. This would be a documentation change, backed up by an
assert (length > 0);
in gcm_set_iv. Does that seem right?
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-leave@lists.lysator.liu.se
"Feng Li" lifeng8425@stu.xmu.edu.cn writes:
We agree with this approach. We believe updating the documentation to explicitly require a non‑zero IV length is necessary. As for the code change, we suggest adding assert(length > 0); at the beginning of the function body of gcm_set_iv in nettle/gcm.c.
Done now, see https://git.lysator.liu.se/nettle/nettle/-/commit/bb9bba13cc327d578e7dab2921...
Regards, /Niels
btw the page is 429ing..
On Thu, Aug 13, 2026, 8:59 AM Niels Möller nisse@lysator.liu.se wrote:
"Feng Li" lifeng8425@stu.xmu.edu.cn writes:
We agree with this approach. We believe updating the documentation to explicitly require a non‑zero IV length is necessary. As for the code change, we suggest adding assert(length > 0); at the beginning of the function body of gcm_set_iv in nettle/gcm.c.
Done now, see
https://git.lysator.liu.se/nettle/nettle/-/commit/bb9bba13cc327d578e7dab2921...
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-leave@lists.lysator.liu.se
Maamoun TK maamoun.tk@googlemail.com writes:
btw the page is 429ing..
Sorry about that. I think the gitlab instance is struggling with bots (likely the same ai crawlers trying to kill most of the open internet). It may help to try again later, or login, for those that have an account on that instance.
For reference, see commit below.
Regards, /Niels
commit bb9bba13cc327d578e7dab2921b021bad9af8ee1 Author: Niels Möller nisse@lysator.liu.se Date: Wed Aug 5 16:17:42 2026 +0200
Require non-empty IV for gcm_set_iv.
diff --git a/ChangeLog b/ChangeLog index beecc55b..606cfd23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2026-08-05 Niels Möller nisse@lysator.liu.se + + * gcm.c (gcm_set_iv): Reject empty IV, with an assertion failure. + Empty IV is not allowed by the spec, and results in leakage of the + ghash authentication subkey. Reported by Feng Li and Yaobin Shen. + * nettle.texinfo (GCM): Document that IV must be non-empty. + 2026-06-30 Niels Möller nisse@lysator.liu.se
* slh-dsa.c (_slh_dsa_sign): Change return type from void to int. diff --git a/gcm.c b/gcm.c index ab74b72d..ab6933c0 100644 --- a/gcm.c +++ b/gcm.c @@ -108,6 +108,7 @@ void gcm_set_iv(struct gcm_ctx *ctx, const struct gcm_key *key, size_t length, const uint8_t *iv) { + assert (length > 0); if (length == GCM_IV_SIZE) { memcpy (ctx->iv.b, iv, GCM_BLOCK_SIZE - 4); diff --git a/nettle.texinfo b/nettle.texinfo index 3047b836..51d0c20d 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -3081,7 +3081,8 @@ Size of the @acronym{GCM} digest, also 16. @end defvr
@defvr Constant GCM_IV_SIZE -Recommended size of the @acronym{IV}, 12. Arbitrary sizes are allowed. +Recommended size of the @acronym{IV}, 12. Arbitrary non-zero sizes are +allowed. @end defvr
@deftypefun void gcm_set_key (struct gcm_key *@var{key}, const void *@var{cipher}, nettle_cipher_func *@var{f}) @@ -3091,9 +3092,9 @@ encryption, and @var{f} is the encryption function. @end deftypefun
@deftypefun void gcm_set_iv (struct gcm_ctx *@var{ctx}, const struct gcm_key *@var{key}, size_t @var{length}, const uint8_t *@var{iv}) -Initializes @var{ctx} using the given @acronym{IV}. The @var{key} -argument is actually needed only if @var{length} differs from -@code{GCM_IV_SIZE}. +Initializes @var{ctx} using the given @acronym{IV}, which must be +non-empty. The @var{key} argument is actually needed only if +@var{length} differs from @code{GCM_IV_SIZE}. @end deftypefun
@deftypefun void gcm_update (struct gcm_ctx *@var{ctx}, const struct gcm_key *@var{key}, size_t @var{length}, const uint8_t *@var{data})
On Thu, Aug 13, 2026 at 10:16 AM Niels Möller nisse@lysator.liu.se wrote:
Maamoun TK maamoun.tk@googlemail.com writes:
btw the page is 429ing..
Sorry about that. I think the gitlab instance is struggling with bots (likely the same ai crawlers trying to kill most of the open internet). It may help to try again later, or login, for those that have an account on that instance.
Logging in works, thanks. I agree that empty IV should be rejected, I have the spec in an updated document that may be worth referencing in this case. I will look for the document and try to send it later today.
For reference, see commit below.
Regards, /Niels
commit bb9bba13cc327d578e7dab2921b021bad9af8ee1 Author: Niels Möller nisse@lysator.liu.se Date: Wed Aug 5 16:17:42 2026 +0200
Require non-empty IV for gcm_set_iv.diff --git a/ChangeLog b/ChangeLog index beecc55b..606cfd23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2026-08-05 Niels Möller nisse@lysator.liu.se
* gcm.c (gcm_set_iv): Reject empty IV, with an assertion failure.Empty IV is not allowed by the spec, and results in leakage of theghash authentication subkey. Reported by Feng Li and Yaobin Shen.* nettle.texinfo (GCM): Document that IV must be non-empty.2026-06-30 Niels Möller nisse@lysator.liu.se
* slh-dsa.c (_slh_dsa_sign): Change return type from void to int.diff --git a/gcm.c b/gcm.c index ab74b72d..ab6933c0 100644 --- a/gcm.c +++ b/gcm.c @@ -108,6 +108,7 @@ void gcm_set_iv(struct gcm_ctx *ctx, const struct gcm_key *key, size_t length, const uint8_t *iv) {
- assert (length > 0); if (length == GCM_IV_SIZE) { memcpy (ctx->iv.b, iv, GCM_BLOCK_SIZE - 4);
diff --git a/nettle.texinfo b/nettle.texinfo index 3047b836..51d0c20d 100644 --- a/nettle.texinfo +++ b/nettle.texinfo @@ -3081,7 +3081,8 @@ Size of the @acronym{GCM} digest, also 16. @end defvr
@defvr Constant GCM_IV_SIZE -Recommended size of the @acronym{IV}, 12. Arbitrary sizes are allowed. +Recommended size of the @acronym{IV}, 12. Arbitrary non-zero sizes are +allowed. @end defvr
@deftypefun void gcm_set_key (struct gcm_key *@var{key}, const void *@var{cipher}, nettle_cipher_func *@var{f}) @@ -3091,9 +3092,9 @@ encryption, and @var{f} is the encryption function. @end deftypefun
@deftypefun void gcm_set_iv (struct gcm_ctx *@var{ctx}, const struct gcm_key *@var{key}, size_t @var{length}, const uint8_t *@var{iv}) -Initializes @var{ctx} using the given @acronym{IV}. The @var{key} -argument is actually needed only if @var{length} differs from -@code{GCM_IV_SIZE}. +Initializes @var{ctx} using the given @acronym{IV}, which must be +non-empty. The @var{key} argument is actually needed only if +@var{length} differs from @code{GCM_IV_SIZE}. @end deftypefun
@deftypefun void gcm_update (struct gcm_ctx *@var{ctx}, const struct gcm_key *@var{key}, size_t @var{length}, const uint8_t *@var{data})
-- Niels Möller. PGP key CB4962D070D77D7FCB8BA36271D8F1FF368C6677. Internet email is subject to wholesale government surveillance.
nettle-bugs@lists.lysator.liu.se