Skip to content

fix(key-mgmt): analyze RSA n and e and EC coordinates by their integer value - #825

Merged
Spomky merged 3 commits into
4.2.xfrom
fix/key-analyzers-integer-encoding
Nov 10, 2026
Merged

Spomky merged 3 commits into
4.2.xfrom
fix/key-analyzers-integer-encoding

Conversation

@Spomky

@Spomky Spomky commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Target branch: 4.2.x

Resolves #823

Supersedes #824 by @lav45, as a bug fix for 4.2.x: their commit is cherry-picked as is, so they remain its
author, with two commits on top.

  • It is a Bug fix
  • It is a New feature
  • It is related to dependencies

Includes:

  • Breaks BC
  • Deprecations

The defect

RsaAnalyzer and ESKeyAnalyzer report no message for keys that are weak or unusable (#823).

  • RSA. checkModulus() counted the bits as 8 × the octet length of n, and checkExponent() read e with
    unpack('l', …): machine byte order, signed, first four octets. Leading zero octets were not stripped, although RFC
    7518 §2 (Base64urlUInt) requires the minimum number of octets. A key with n = 15 and e = 1, written with leading
    zero octets, gave no message: its length was counted as 2048 bits, and e = 00 00 00 01 was read as 16777216.
  • EC. The point was checked with Ecc\Curve::contains(), which reduces modulo p. A coordinate y + p that still
    fits the coordinate length (always possible on P-521: 66 octets, p = 2^521 − 1) passed, while OpenSSL refuses the key,
    so every verification with it fails.

The fix

  • RsaAnalyzer reads n and e as big-endian unsigned integers (Util\BigInteger::createFromBinaryString()), and
    compares the bit length of the modulus and the value of the exponent. An empty n or e gives
    The modulus is not valid. / The exponent is not valid., the messages already used for a value that is not a
    string.
  • ESKeyAnalyzer requires x and y to be lower than the field prime (SEC 1 §3.2.2.1, NIST SP 800-56A §5.6.2.3)
    before the on-curve check, with the new message
    Invalid key. The components "x" and "y" shall be lower than the field prime.

The length checks on octet strings (x/y in ESKeyAnalyzer, k in HSKeyAnalyzer and OctAnalyzer) are unchanged:
these values are fixed-length octet strings, not Base64urlUInt.

Tests

Two tests in tests/Component/KeyManagement/JWKAnalyzerTest.php, both failing without the fix:

  • theRsaKeyWithLeadingZeroOctetsIsAnalyzedByItsValue: n = 15 and e = 1 with leading zero octets get the key length
    and exponent messages;
  • theEcKeyWithACoordinateAboveTheFieldPrimeIsRejected: a P-521 key with y + p gets the field prime message.

PHPUnit, PHPStan, ECS, Rector and Deptrac pass with ghcr.io/spomky-labs/phpqa:8.4.

Changes on top of #824

The commit of #824 is cherry-picked onto 4.2.x. The only conflict was the exception thrown by the code it removes.

  • RsaAnalyzer no longer converts n and e into big integers. The analyzed keys may come from a third party and
    nothing bounds their length: without GMP, a modulus of 2048 octets took 0.57 s with BCMath and 109 s in pure PHP. The
    bit length of the modulus is now read from its octets, leading zero octets ignored, as RSAKey does; the exponent is
    converted only when it fits in three octets. The verdicts are unchanged. New tests cover 2047 and 2048-bit moduli,
    and exponents with leading zero octets or longer than four octets.
  • ESKeyAnalyzer stops after the size message when the coordinates have the wrong size, instead of converting them
    anyway (coordinates of 1 KiB took 27.5 s in pure PHP), and creates the curve once.

PHPUnit (1390 tests), PHPStan, ECS, Rector and Deptrac pass with ghcr.io/spomky-labs/phpqa:8.4, and the changed files
lint with PHP 8.2.

lav45 and others added 3 commits November 10, 2026 17:17
…r value

RsaAnalyzer counted the modulus as 8 × octets and read the exponent with
unpack('l'), so leading zero octets (RFC 7518 §2 forbids them, Base64urlUInt)
let a key with n = 15 and e = 1 pass with no message. Both values are now read
as big-endian integers with BigInteger.

ESKeyAnalyzer checked the point with Curve::contains(), which reduces modulo p,
so a coordinate >= p passed while OpenSSL refuses the key. Coordinates are now
required to be lower than the field prime.
RsaAnalyzer converted "n" and "e" into big integers. The analyzed keys may
come from a third party and nothing bounds their length: without GMP, a
modulus of 2048 octets cost 0.57 s with BCMath and 109 s in pure PHP, and
32 KiB cost 155 s with BCMath.

The bit length of the modulus is now read from its octets, leading zero
octets ignored, as RSAKey does. The exponent is converted only when it fits
in three octets: a longer value is at least 2^24.

The verdicts are those of the previous commit. The new tests cover the
bounds: 2047 and 2048-bit moduli, exponents with leading zero octets or
longer than four octets.
ESKeyAnalyzer reported coordinates of the wrong size, then converted them
into big integers anyway. Without GMP, coordinates of 1 KiB cost 27.5 s in
pure PHP and 8 KiB cost 2 s with BCMath. The analysis now stops after the
size message, as it does for empty coordinates, and the curve is created
once.
@Spomky Spomky added this to the 4.2.8 milestone Nov 10, 2026
@Spomky Spomky added the bug label Nov 10, 2026
@Spomky
Spomky merged commit 0893d6f into 4.2.x Nov 10, 2026
17 checks passed
@Spomky
Spomky deleted the fix/key-analyzers-integer-encoding branch November 10, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KeyAnalyzer: RsaAnalyzer ignores leading zero octets (n = 15, e = 1 pass) and ESKeyAnalyzer accepts coordinates >= p

2 participants