From 0bd6889427951e1f5200cbcb134647048bcf691a Mon Sep 17 00:00:00 2001 From: Dominique Fournier Date: Wed, 4 Dec 2019 09:44:15 +0000 Subject: [PATCH] JWT : allow to encrypt the payload git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5782 bf3deb0d-5f1a-0410-827f-c0cc1f45334c --- Tests/jwtTest.php | 40 ++++++++++++++++++++++ jwt.php | 84 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/Tests/jwtTest.php b/Tests/jwtTest.php index 92ab9c5..2d18562 100644 --- a/Tests/jwtTest.php +++ b/Tests/jwtTest.php @@ -133,4 +133,44 @@ class test_jwt extends PHPUnit_Framework_TestCase "eyJwYXlsb2FkIjoidmFsdWUifQ", "key to use"); } + + /////////////////////////////// + // ENCRYPT THE PAYLOAD // + /////////////////////////////// + /** Check the length of the otken with cipher + */ + public function testEncrypt1 () + { + $jwt = new jwt (); + $key = $jwt->createKey (); + $res = $jwt->encode ( + ["email" => "toto@example.com", "password" => "ToTo"], + $key, "HS256", "123456789012345678901234"); + $this->assertSame (strlen ($res), 156); + } + + /** Check if the encrypt/decrypt process return the same result + */ + public function testEncrypyt2 () + { + $jwt = new jwt (); + $key = $jwt->createKey (); + $payload = (object)["email" => "toto@example.com", "password" => "ToTo"]; + $token = $jwt->encode ($payload, $key, "HS256", "123456789012345678901234"); + $res = $jwt->decode ($token, $key, null, "123456789012345678901234"); + $this->assertSame ($res, $payload); + } + + /** Check if the encrypted part is well unreadable + */ + public function testEncrypt3 () + { + $jwt = new jwt (); + $key = $jwt->createKey (); + $payload = (object)["email" => "toto@example.com", "password" => "ToTo"]; + $token = $jwt->encode ($payload, $key, "HS256", "123456789012345678901234"); + list ($header, $payload, $signature) = explode (".", $token); + $res = strpos (base64_decode ($payload), "email"); + $this->assertSame ($res, false); + } } diff --git a/jwt.php b/jwt.php index 4445f2c..9411078 100644 --- a/jwt.php +++ b/jwt.php @@ -26,9 +26,11 @@ class jwt * @param string|null $alg The algorithm to use to sign the token (default * is HS256) * Allowed algorithms : HS256, HS512, HS384 + * @param string|null $ckey The cipher key to encrypt the payload (24 chars + * length) * @return string The Token */ - public function encode ($payload, $key, $alg = "HS256") + public function encode ($payload, $key, $alg = "HS256", $ckey = null) // {{{ { if (! key_exists ($alg, $this->supportedAlgs)) @@ -37,7 +39,10 @@ class jwt $header = array ("typ" => "JWT", "alg" => $alg); $segments = array (); $segments[] = $this->urlsafeB64Encode ($this->jsonEncode ($header)); - $segments[] = $this->urlsafeB64Encode ($this->jsonEncode ($payload)); + $payload = $this->jsonEncode ($payload); + if ($ckey) + $payload = $this->encrypt ($payload, $ckey); + $segments[] = $this->urlsafeB64Encode ($payload); $toBeSigned = implode ('.', $segments); $signature = $this->sign ($toBeSigned, $key, $alg); $segments[] = $this->urlsafeB64Encode ($signature); @@ -50,11 +55,13 @@ class jwt * @param string $key The key used to sign the message * @param array|null $allowedAlg List of allowed algorithms. If null, all the * algorithms defined in $this->supportedAlgs are allowed + * @param string|null $ckey The cipher key to decrypt the payload (24 chars + * length) * @return array the decoded payload * @throw Exception if the key is not able to verify the token with the * provided password */ - public function decode ($jwt, $key, $allowedAlg = null) + public function decode ($jwt, $key, $allowedAlg = null, $ckey = null) // {{{ { if ($allowedAlg === null) @@ -68,7 +75,10 @@ class jwt 403); list ($headerb64, $payloadb64, $signb64) = $tks; $header = $this->jsonDecode ($this->urlsafeB64Decode ($headerb64)); - $payload = $this->jsonDecode ($this->urlsafeB64Decode ($payloadb64)); + $payload = $this->urlsafeB64Decode ($payloadb64); + if ($ckey) + $payload = $this->decrypt ($payload, $ckey); + $payload = $this->jsonDecode ($payload); $signature = $this->urlsafeB64Decode ($signb64); if ($header === null) throw new \Exception (dgettext ("domframework", @@ -134,6 +144,72 @@ class jwt } // }}} + /** Encrypt the payload to not be readable by anybody + * @param string $payload The payload to encrypt + * @param string $ckey The 24 chars for the cipher key + * @param string|null $cipherMethod DES-EDE3-CBC by default + * @return base64 encrypted payload + */ + private function encrypt ($payload, $ckey, $cipherMethod = "des-ede3-cbc") + // {{{ + { + if (! in_array ($cipherMethod, openssl_get_cipher_methods())) + throw new \Exception (dgettext ("domframework", + "Invalid cipher provided to encrypt method : ". + "doesn't exists in OpenSSL"), 500); + if (! is_string ($payload)) + throw new \Exception (dgettext ("domframework", + "Invalid payload provided to encrypt method : ". + "Not a string"), 500); + if (strlen ($ckey) !== 24) + throw new \Exception (dgettext ("domframework", + "Invalid cipherKey provided to encrypt method :" . + " length different of 24 chars"), 500); + // Must be the same as decrypt + $options = true; + $ivlen = openssl_cipher_iv_length ($cipherMethod); + $iv = openssl_random_pseudo_bytes ($ivlen); + $ciphertext = openssl_encrypt ($payload, $cipherMethod, $ckey, $options, + $iv); + if ($ciphertext === false) + throw new \Exception (dgettext ("domframework", + "Can not encrypt the payload"), 500); + $ciphertext = $iv . $ciphertext; + return ($ciphertext); + } + // }}} + + /** Decrypt the payload + * @param string $ciphertext The payload to decrypt + * @param string $ckey The 24 chars for the cipher key + * @param string|null $cipherMethod DES-EDE3-CBC by default + * @return decrypted payload + */ + private function decrypt ($ciphertext, $ckey, $cipherMethod = "des-ede3-cbc") + // {{{ + { + if (! is_string ($ciphertext)) + throw new \Exception (dgettext ("domframework", + "Invalid ciphertext provided to decrypt method : not a string"), 500); + if (trim ($ciphertext) === "") + throw new \Exception (dgettext ("domframework", + "Invalid ciphertext provided to decrypt method : empty string"), 500); + if (strlen ($ckey) !== 24) + throw new \Exception (dgettext ("domframework", + "Invalid cipherKey provided to decrypt method :" . + " length different of 24 chars"), 500); + $ivlen = openssl_cipher_iv_length ($cipherMethod); + $iv = substr ($ciphertext, 0, $ivlen); + if (strlen ($iv) != $ivlen) + throw new \Exception (dgettext ("domframework", + "Can not decrypt the payload : invalid salt"), 500); + // Must be the same as encrypt + $options = true; + $ciphertext = substr ($ciphertext, $ivlen); + return openssl_decrypt ($ciphertext, $cipherMethod, $ckey, $options, $iv); + } + // }}} + /** Sign the requested string with the provided key and based on the algorithm * @param string $input The string to sign * @param string $key The key to use