CVE-2025-45769
Description
php-jwt v6.11.0 was discovered to contain weak encryption. NOTE: this issue has been disputed on the basis that key lengths are expected to be set by an application, not by this library. This dispute is subject to review under CNA rules 4.1.4, 4.1.14, and other rules; the dispute tagging is not meant to recommend an outcome for this CVE Record.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
php-jwt v6.11.0 has disputed weak encryption claims; the library does not enforce key length, leaving it to the application.
CVE-2025-45769 describes a reportedly weak encryption issue in php-jwt version 6.11.0, a library for encoding and decoding JSON Web Tokens (JWT) in PHP, hosted under the Firebase organization on GitHub [1][2]. The core claim is that the library's encryption implementation is insufficient, potentially allowing attackers to compromise JWT security.
The vulnerability is contested on the grounds that key length enforcement is the responsibility of the application using the library, not the library itself [1]. The official documentation for php-jwt demonstrates that the application must provide an $key (e.g., 'example_key_of_sufficient_length') and explicitly lists supported algorithms [2]; the library does not enforce a minimum key length or algorithm strength. An attacker could exploit this if an application provides a weak or insufficiently long key, but that would be a misconfiguration at the application layer, not a flaw in the library's own code.
If exploited, an attacker could forge valid JWTs, bypass authentication, or gain unauthorized access to protected resources, but only in scenarios where the application fails to set an adequately strong key [1][4]. The GitHub Advisory reflects these exploitability considerations, including metrics for attack vector and complexity [4].
As of the publication date, no patch is mentioned because the issue is disputed and stems from application-level configuration rather than a library bug. Users should ensure they provide keys of adequate length and use strong algorithms (e.g., HS256) as recommended in the library's examples [2]. The CVE remains under review by CNA rules [1].
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
firebase/php-jwtPackagist | < 7.0.0 | 7.0.0 |
Affected products
2- php-jwt/php-jwtdescription
Patches
16b80341bf578feat: add key size validation (#613)
2 files changed · +218 −82
src/JWT.php+51 −2 modified@@ -31,6 +31,8 @@ class JWT private const ASN1_SEQUENCE = 0x10; private const ASN1_BIT_STRING = 0x03; + private const RSA_KEY_MIN_LENGTH=2048; + /** * When checking nbf, iat or expiration times, * we want to provide some extra leeway time to @@ -259,11 +261,19 @@ public static function sign( if (!\is_string($key)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($key, $algorithm); return \hash_hmac($algorithm, $msg, $key, true); case 'openssl': $signature = ''; - if (!\is_resource($key) && !openssl_pkey_get_private($key)) { - throw new DomainException('OpenSSL unable to validate key'); + if (!\is_resource($key)) { + /** @var OpenSSLAsymmetricKey|OpenSSLCertificate|string $key */ + $key = $key; + if (!$key = openssl_pkey_get_private($key)) { + throw new DomainException('OpenSSL unable to validate key'); + } + if (str_starts_with($alg, 'RS')) { + self::validateRsaKeyLength($key); + } } $success = \openssl_sign($msg, $signature, $key, $algorithm); // @phpstan-ignore-line if (!$success) { @@ -324,6 +334,13 @@ private static function verify( list($function, $algorithm) = static::$supported_algs[$alg]; switch ($function) { case 'openssl': + if (!\is_resource($keyMaterial) && str_starts_with($algorithm, 'RS')) { + /** @var OpenSSLAsymmetricKey|OpenSSLCertificate|string $keyMaterial */ + $keyMaterial = $keyMaterial; + if ($key = openssl_pkey_get_private($keyMaterial)) { + self::validateRsaKeyLength($key); + } + } $success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm); // @phpstan-ignore-line if ($success === 1) { return true; @@ -361,6 +378,7 @@ private static function verify( if (!\is_string($keyMaterial)) { throw new InvalidArgumentException('key must be a string when using hmac'); } + self::validateHmacKeyLength($keyMaterial, $algorithm); $hash = \hash_hmac($algorithm, $msg, $keyMaterial, true); return self::constantTimeEquals($hash, $signature); } @@ -675,4 +693,35 @@ private static function readDER(string $der, int $offset = 0): array return [$pos, $data]; } + + /** + * Validate HMAC key length + * + * @param string $key HMAC key material + * @param string $algorithm The algorithm + * @throws DomainException Provided key is too short + */ + private static function validateHmacKeyLength(string $key, string $algorithm): void + { + $keyLength = \strlen($key) * 8; + $minKeyLength = (int) \str_replace('SHA', '', $algorithm); + if ($keyLength < $minKeyLength) { + throw new DomainException('Provided key is too short'); + } + } + + /** + * Validate RSA key length + * + * @param OpenSSLAsymmetricKey $key RSA key material + * @throws DomainException Provided key is too short + */ + private static function validateRsaKeyLength(OpenSSLAsymmetricKey $key): void + { + if ($keyDetails = \openssl_pkey_get_details($key)) { + if ($keyDetails['bits'] < self::RSA_KEY_MIN_LENGTH) { + throw new DomainException('Provided key is too short'); + } + } + } }
tests/JWTTest.php+167 −80 modified@@ -2,7 +2,6 @@ namespace Firebase\JWT; -use ArrayObject; use DomainException; use InvalidArgumentException; use PHPUnit\Framework\TestCase; @@ -12,18 +11,26 @@ class JWTTest extends TestCase { + private Key $hmacKey; + + public function setUp(): void + { + $this->hmacKey = $this->generateHmac256(); + } + public function testUrlSafeCharacters() { - $encoded = JWT::encode(['message' => 'f?'], 'a', 'HS256'); + $encoded = JWT::encode(['message' => 'f?'], $this->hmacKey->getKeyMaterial(), 'HS256'); $expected = new stdClass(); $expected->message = 'f?'; - $this->assertEquals($expected, JWT::decode($encoded, new Key('a', 'HS256'))); + $this->assertEquals($expected, JWT::decode($encoded, $this->hmacKey)); } public function testMalformedUtf8StringsFail() { + $this->expectException(DomainException::class); - JWT::encode(['message' => pack('c', 128)], 'a', 'HS256'); + JWT::encode(['message' => pack('c', 128)], $this->hmacKey->getKeyMaterial(), 'HS256'); } public function testInvalidKeyOpensslSignFail() @@ -45,8 +52,9 @@ public function testExpiredToken() 'message' => 'abc', 'exp' => time() - 20, // time in the past ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey); } public function testBeforeValidTokenWithNbf() @@ -56,8 +64,8 @@ public function testBeforeValidTokenWithNbf() 'message' => 'abc', 'nbf' => time() + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey); } public function testBeforeValidTokenWithIat() @@ -67,8 +75,8 @@ public function testBeforeValidTokenWithIat() 'message' => 'abc', 'iat' => time() + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey); } public function testValidToken() @@ -77,8 +85,8 @@ public function testValidToken() 'message' => 'abc', 'exp' => time() + JWT::$leeway + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -92,8 +100,8 @@ public function testValidTokenWithLeeway() 'message' => 'abc', 'exp' => time() - 20, // time in the past ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -102,14 +110,14 @@ public function testValidTokenWithLeeway() */ public function testExpiredTokenWithLeeway() { + $this->expectException(ExpiredException::class); JWT::$leeway = 60; $payload = [ 'message' => 'abc', 'exp' => time() - 70, // time far in the past ]; - $this->expectException(ExpiredException::class); - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -120,9 +128,9 @@ public function testExpiredExceptionPayload() 'message' => 'abc', 'exp' => time() - 100, // time in the past ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); try { - JWT::decode($encoded, new Key('my_key', 'HS256')); + JWT::decode($encoded, $this->hmacKey); } catch (ExpiredException $e) { $exceptionPayload = (array) $e->getPayload(); $this->assertEquals($exceptionPayload, $payload); @@ -142,10 +150,10 @@ public function testExpiredExceptionTimestamp() 'message' => 'abc', 'exp' => 1234, ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); try { - JWT::decode($encoded, new Key('my_key', 'HS256')); + JWT::decode($encoded, $this->hmacKey); } catch (ExpiredException $e) { $exTimestamp = $e->getTimestamp(); $this->assertSame(98765, $exTimestamp); @@ -160,9 +168,9 @@ public function testBeforeValidExceptionPayload() 'message' => 'abc', 'iat' => time() + 100, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); try { - JWT::decode($encoded, new Key('my_key', 'HS256')); + JWT::decode($encoded, $this->hmacKey); } catch (BeforeValidException $e) { $exceptionPayload = (array) $e->getPayload(); $this->assertEquals($exceptionPayload, $payload); @@ -178,8 +186,8 @@ public function testValidTokenWithNbf() 'exp' => time() + 20, // time in the future 'nbf' => time() - 20 ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -193,8 +201,8 @@ public function testValidTokenWithNbfLeeway() 'message' => 'abc', 'nbf' => time() + 20, // not before in near (leeway) future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -208,10 +216,10 @@ public function testInvalidTokenWithNbfLeeway() 'message' => 'abc', 'nbf' => time() + 65, // not before too far in future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(BeforeValidException::class); $this->expectExceptionMessage('Cannot handle token with nbf prior to'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + JWT::decode($encoded, $this->hmacKey); } public function testValidTokenWithNbfIgnoresIat() @@ -221,8 +229,8 @@ public function testValidTokenWithNbfIgnoresIat() 'nbf' => time() - 20, // time in the future 'iat' => time() + 20, // time in the past ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertEquals('abc', $decoded->message); } @@ -232,8 +240,8 @@ public function testValidTokenWithNbfMicrotime() 'message' => 'abc', 'nbf' => microtime(true), // use microtime ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertEquals('abc', $decoded->message); } @@ -245,8 +253,8 @@ public function testInvalidTokenWithNbfMicrotime() 'message' => 'abc', 'nbf' => microtime(true) + 20, // use microtime in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey); } /** @@ -259,8 +267,8 @@ public function testValidTokenWithIatLeeway() 'message' => 'abc', 'iat' => time() + 20, // issued in near (leeway) future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertSame($decoded->message, 'abc'); } @@ -274,10 +282,10 @@ public function testInvalidTokenWithIatLeeway() 'message' => 'abc', 'iat' => time() + 65, // issued too far in future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(BeforeValidException::class); $this->expectExceptionMessage('Cannot handle token with iat prior to'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + JWT::decode($encoded, $this->hmacKey); } public function testValidTokenWithIatMicrotime() @@ -286,8 +294,8 @@ public function testValidTokenWithIatMicrotime() 'message' => 'abc', 'iat' => microtime(true), // use microtime ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - $decoded = JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + $decoded = JWT::decode($encoded, $this->hmacKey); $this->assertEquals('abc', $decoded->message); } @@ -299,19 +307,21 @@ public function testInvalidTokenWithIatMicrotime() 'message' => 'abc', 'iat' => microtime(true) + 20, // use microtime in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256')); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey); } public function testInvalidToken() { + $encodeKey = $this->generateHmac256(); + $decodeKey = $this->generateHmac256(); $payload = [ 'message' => 'abc', 'exp' => time() + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $encodeKey->getKeyMaterial(), $encodeKey->getAlgorithm()); $this->expectException(SignatureInvalidException::class); - JWT::decode($encoded, new Key('my_key2', 'HS256')); + JWT::decode($encoded, $decodeKey); } public function testNullKeyFails() @@ -320,7 +330,7 @@ public function testNullKeyFails() 'message' => 'abc', 'exp' => time() + JWT::$leeway + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(TypeError::class); JWT::decode($encoded, new Key(null, 'HS256')); } @@ -331,17 +341,17 @@ public function testEmptyKeyFails() 'message' => 'abc', 'exp' => time() + JWT::$leeway + 20, // time in the future ]; - $encoded = JWT::encode($payload, 'my_key', 'HS256'); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(InvalidArgumentException::class); JWT::decode($encoded, new Key('', 'HS256')); } public function testKIDChooser() { $keys = [ - '0' => new Key('my_key0', 'HS256'), - '1' => new Key('my_key1', 'HS256'), - '2' => new Key('my_key2', 'HS256') + '0' => $this->generateHmac256(), + '1' => $this->generateHmac256(), + '2' => $this->generateHmac256() ]; $msg = JWT::encode(['message' => 'abc'], $keys['0']->getKeyMaterial(), 'HS256', '0'); $decoded = JWT::decode($msg, $keys); @@ -352,11 +362,11 @@ public function testKIDChooser() public function testArrayAccessKIDChooser() { - $keys = new ArrayObject([ - '0' => new Key('my_key0', 'HS256'), - '1' => new Key('my_key1', 'HS256'), - '2' => new Key('my_key2', 'HS256'), - ]); + $keys = [ + '0' => $this->generateHmac256(), + '1' => $this->generateHmac256(), + '2' => $this->generateHmac256() + ]; $msg = JWT::encode(['message' => 'abc'], $keys['0']->getKeyMaterial(), 'HS256', '0'); $decoded = JWT::decode($msg, $keys); $expected = new stdClass(); @@ -366,59 +376,62 @@ public function testArrayAccessKIDChooser() public function testNoneAlgorithm() { - $msg = JWT::encode(['message' => 'abc'], 'my_key', 'HS256'); + $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(UnexpectedValueException::class); - JWT::decode($msg, new Key('my_key', 'none')); + JWT::decode($msg, new Key($this->hmacKey->getKeyMaterial(), 'none')); } public function testIncorrectAlgorithm() { - $msg = JWT::encode(['message' => 'abc'], 'my_key', 'HS256'); + $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(UnexpectedValueException::class); - JWT::decode($msg, new Key('my_key', 'RS256')); + // TODO: Generate proper RS256 key + JWT::decode($msg, new Key($this->hmacKey->getKeyMaterial(), 'RS256')); } public function testEmptyAlgorithm() { - $msg = JWT::encode(['message' => 'abc'], 'my_key', 'HS256'); + $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256'); $this->expectException(InvalidArgumentException::class); - JWT::decode($msg, new Key('my_key', '')); + JWT::decode($msg, new Key($this->hmacKey->getKeyMaterial(), '')); } public function testAdditionalHeaders() { - $msg = JWT::encode(['message' => 'abc'], 'my_key', 'HS256', null, ['cty' => 'test-eit;v=1']); + $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256', null, ['cty' => 'test-eit;v=1']); $expected = new stdClass(); $expected->message = 'abc'; - $this->assertEquals(JWT::decode($msg, new Key('my_key', 'HS256')), $expected); + $this->assertEquals(JWT::decode($msg, $this->hmacKey), $expected); } public function testInvalidSegmentCount() { $this->expectException(UnexpectedValueException::class); - JWT::decode('brokenheader.brokenbody', new Key('my_key', 'HS256')); + JWT::decode('brokenheader.brokenbody', $this->hmacKey); } public function testInvalidSignatureEncoding() { $msg = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwibmFtZSI6ImZvbyJ9.Q4Kee9E8o0Xfo4ADXvYA8t7dN_X_bU9K5w6tXuiSjlUxx'; $this->expectException(UnexpectedValueException::class); - JWT::decode($msg, new Key('secret', 'HS256')); + JWT::decode($msg, $this->hmacKey); } public function testHSEncodeDecode() { - $msg = JWT::encode(['message' => 'abc'], 'my_key', 'HS256'); + $msg = JWT::encode(['message' => 'abc'], $this->hmacKey->getKeyMaterial(), 'HS256'); $expected = new stdClass(); $expected->message = 'abc'; - $this->assertEquals(JWT::decode($msg, new Key('my_key', 'HS256')), $expected); + $this->assertEquals(JWT::decode($msg, $this->hmacKey), $expected); } public function testRSEncodeDecode() { - $privKey = openssl_pkey_new(['digest_alg' => 'sha256', - 'private_key_bits' => 1024, - 'private_key_type' => OPENSSL_KEYTYPE_RSA]); + $privKey = openssl_pkey_new([ + 'digest_alg' => 'sha256', + 'private_key_bits' => 2048, + 'private_key_type' => OPENSSL_KEYTYPE_RSA + ]); $msg = JWT::encode(['message' => 'abc'], $privKey, 'RS256'); $pubKey = openssl_pkey_get_details($privKey); $pubKey = $pubKey['key']; @@ -541,8 +554,8 @@ public function testGetHeaders() ]; $headers = new stdClass(); - $encoded = JWT::encode($payload, 'my_key', 'HS256'); - JWT::decode($encoded, new Key('my_key', 'HS256'), $headers); + $encoded = JWT::encode($payload, $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($encoded, $this->hmacKey, $headers); $this->assertEquals($headers->typ, 'JWT'); $this->assertEquals($headers->alg, 'HS256'); @@ -552,7 +565,7 @@ public function testAdditionalHeaderOverrides() { $msg = JWT::encode( ['message' => 'abc'], - 'my_key', + $this->hmacKey->getKeyMaterial(), 'HS256', 'my_key_id', [ @@ -563,7 +576,7 @@ public function testAdditionalHeaderOverrides() ] ); $headers = new stdClass(); - JWT::decode($msg, new Key('my_key', 'HS256'), $headers); + JWT::decode($msg, $this->hmacKey, $headers); $this->assertEquals('test-eit;v=1', $headers->cty, 'additional field works'); $this->assertEquals('JOSE', $headers->typ, 'typ override works'); $this->assertEquals('my_key_id', $headers->kid, 'key param not overridden'); @@ -575,25 +588,99 @@ public function testDecodeExpectsIntegerIat() $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Payload iat must be a number'); - $payload = JWT::encode(['iat' => 'not-an-int'], 'secret', 'HS256'); - JWT::decode($payload, new Key('secret', 'HS256')); + $payload = JWT::encode(['iat' => 'not-an-int'], $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($payload, $this->hmacKey); } public function testDecodeExpectsIntegerNbf() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Payload nbf must be a number'); - $payload = JWT::encode(['nbf' => 'not-an-int'], 'secret', 'HS256'); - JWT::decode($payload, new Key('secret', 'HS256')); + $payload = JWT::encode(['nbf' => 'not-an-int'], $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($payload, $this->hmacKey); } public function testDecodeExpectsIntegerExp() { $this->expectException(UnexpectedValueException::class); $this->expectExceptionMessage('Payload exp must be a number'); - $payload = JWT::encode(['exp' => 'not-an-int'], 'secret', 'HS256'); - JWT::decode($payload, new Key('secret', 'HS256')); + $payload = JWT::encode(['exp' => 'not-an-int'], $this->hmacKey->getKeyMaterial(), 'HS256'); + JWT::decode($payload, $this->hmacKey); + } + + public function testRsaKeyLengthValidationThrowsException(): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage('Provided key is too short'); + + // Generate an RSA key that is smaller than the 2048-bit minimum + $shortRsaKey = openssl_pkey_new([ + 'private_key_bits' => 1024, + 'private_key_type' => OPENSSL_KEYTYPE_RSA, + ]); + + self::assertNotFalse($shortRsaKey, 'Failed to generate a short RSA key for testing.'); + $payload = ['message' => 'abc']; + JWT::encode($payload, $shortRsaKey, 'RS256'); + } + + /** @dataProvider provideHmac */ + public function testHmacKeyLengthValidationThrowsExceptionEncode(string $alg, int $minLength): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage('Provided key is too short'); + + $tooShortKeyBytes = str_repeat('b', $minLength - 1); + $payload = ['message' => 'abc']; + + JWT::encode($payload, $tooShortKeyBytes, $alg); + } + + /** @dataProvider provideHmac */ + public function testHmacKeyLengthValidationThrowsExceptionDecode(string $alg, int $minLength): void + { + $this->expectException(DomainException::class); + $this->expectExceptionMessage('Provided key is too short'); + + $tooShortKeyBytes = str_repeat('b', $minLength - 1); + $payload = ['message' => 'abc']; + + $validKeyBytes = str_repeat('b', $minLength); + $encoded = JWT::encode($payload, $validKeyBytes, $alg); + + JWT::decode($encoded, new Key($tooShortKeyBytes, $alg)); + } + + /** @dataProvider provideHmac */ + public function testHmacKeyLengthValidationPassesWithCorrectLength(string $alg, int $minLength): void + { + $payload = ['message' => 'test hmac length']; + + // Test with a key that is exactly the required length + $minKeyBytes = str_repeat('b', $minLength); + $encoded48 = JWT::encode($payload, $minKeyBytes, $alg); + $decoded48 = JWT::decode($encoded48, new Key($minKeyBytes, $alg)); + $this->assertEquals($payload['message'], $decoded48->message); + + // Test with a key that is longer than the required length + $largeKeyBytes = str_repeat('c', $minLength * 2); // Longer than min bytes + $encoded64 = JWT::encode($payload, $largeKeyBytes, $alg); + $decoded64 = JWT::decode($encoded64, new Key($largeKeyBytes, $alg)); + $this->assertEquals($payload['message'], $decoded64->message); + } + + public function provideHmac() + { + return [ + ['HS384', 48], + ['HS256', 32], + ]; + } + + private function generateHmac256(): Key + { + return new Key(random_bytes(32), 'HS256'); } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/advisories/GHSA-2x45-7fc3-mxwqghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-45769ghsaADVISORY
- gist.github.com/ZupeiNie/83756316c4c24fe97a50176a92608db3ghsaWEB
- github.com/firebase/php-jwt/commit/6b80341bf57838ea2d011487917337901cd71576ghsaWEB
- github.com/firebase/php-jwt/issues/611ghsaWEB
- github.com/firebase/php-jwt/issues/618ghsaWEB
- github.com/firebase/php-jwt/issues/620ghsaWEB
- github.com/firebase/php-jwt/pull/613ghsaWEB
- github.com/firebase/php-jwt/releases/tag/v7.0.0ghsaWEB
- github.com/github/advisory-database/pull/6954ghsaWEB
News mentions
0No linked articles in our index yet.