Jwt.php 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace OAuth2\Encryption;
  3. use Exception;
  4. use InvalidArgumentException;
  5. /**
  6. * @link https://github.com/F21/jwt
  7. * @author F21
  8. */
  9. class Jwt implements EncryptionInterface
  10. {
  11. /**
  12. * @param $payload
  13. * @param $key
  14. * @param string $algo
  15. * @return string
  16. */
  17. public function encode($payload, $key, $algo = 'HS256')
  18. {
  19. $header = $this->generateJwtHeader($payload, $algo);
  20. $segments = array(
  21. $this->urlSafeB64Encode(json_encode($header)),
  22. $this->urlSafeB64Encode(json_encode($payload))
  23. );
  24. $signing_input = implode('.', $segments);
  25. $signature = $this->sign($signing_input, $key, $algo);
  26. $segments[] = $this->urlsafeB64Encode($signature);
  27. return implode('.', $segments);
  28. }
  29. /**
  30. * @param string $jwt
  31. * @param null $key
  32. * @param array|bool $allowedAlgorithms
  33. * @return bool|mixed
  34. */
  35. public function decode($jwt, $key = null, $allowedAlgorithms = true)
  36. {
  37. if (!strpos($jwt, '.')) {
  38. return false;
  39. }
  40. $tks = explode('.', $jwt);
  41. if (count($tks) != 3) {
  42. return false;
  43. }
  44. list($headb64, $payloadb64, $cryptob64) = $tks;
  45. if (null === ($header = json_decode($this->urlSafeB64Decode($headb64), true))) {
  46. return false;
  47. }
  48. if (null === $payload = json_decode($this->urlSafeB64Decode($payloadb64), true)) {
  49. return false;
  50. }
  51. $sig = $this->urlSafeB64Decode($cryptob64);
  52. if ((bool) $allowedAlgorithms) {
  53. if (!isset($header['alg'])) {
  54. return false;
  55. }
  56. // check if bool arg supplied here to maintain BC
  57. if (is_array($allowedAlgorithms) && !in_array($header['alg'], $allowedAlgorithms)) {
  58. return false;
  59. }
  60. if (!$this->verifySignature($sig, "$headb64.$payloadb64", $key, $header['alg'])) {
  61. return false;
  62. }
  63. }
  64. return $payload;
  65. }
  66. /**
  67. * @param $signature
  68. * @param $input
  69. * @param $key
  70. * @param string $algo
  71. * @return bool
  72. * @throws InvalidArgumentException
  73. */
  74. private function verifySignature($signature, $input, $key, $algo = 'HS256')
  75. {
  76. // use constants when possible, for HipHop support
  77. switch ($algo) {
  78. case'HS256':
  79. case'HS384':
  80. case'HS512':
  81. return $this->hash_equals(
  82. $this->sign($input, $key, $algo),
  83. $signature
  84. );
  85. case 'RS256':
  86. return openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256') === 1;
  87. case 'RS384':
  88. return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384') === 1;
  89. case 'RS512':
  90. return @openssl_verify($input, $signature, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512') === 1;
  91. default:
  92. throw new InvalidArgumentException("Unsupported or invalid signing algorithm.");
  93. }
  94. }
  95. /**
  96. * @param $input
  97. * @param $key
  98. * @param string $algo
  99. * @return string
  100. * @throws Exception
  101. */
  102. private function sign($input, $key, $algo = 'HS256')
  103. {
  104. switch ($algo) {
  105. case 'HS256':
  106. return hash_hmac('sha256', $input, $key, true);
  107. case 'HS384':
  108. return hash_hmac('sha384', $input, $key, true);
  109. case 'HS512':
  110. return hash_hmac('sha512', $input, $key, true);
  111. case 'RS256':
  112. return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA256') ? OPENSSL_ALGO_SHA256 : 'sha256');
  113. case 'RS384':
  114. return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA384') ? OPENSSL_ALGO_SHA384 : 'sha384');
  115. case 'RS512':
  116. return $this->generateRSASignature($input, $key, defined('OPENSSL_ALGO_SHA512') ? OPENSSL_ALGO_SHA512 : 'sha512');
  117. default:
  118. throw new Exception("Unsupported or invalid signing algorithm.");
  119. }
  120. }
  121. /**
  122. * @param $input
  123. * @param $key
  124. * @param string $algo
  125. * @return mixed
  126. * @throws Exception
  127. */
  128. private function generateRSASignature($input, $key, $algo)
  129. {
  130. if (!openssl_sign($input, $signature, $key, $algo)) {
  131. throw new Exception("Unable to sign data.");
  132. }
  133. return $signature;
  134. }
  135. /**
  136. * @param string $data
  137. * @return string
  138. */
  139. public function urlSafeB64Encode($data)
  140. {
  141. $b64 = base64_encode($data);
  142. $b64 = str_replace(array('+', '/', "\r", "\n", '='),
  143. array('-', '_'),
  144. $b64);
  145. return $b64;
  146. }
  147. /**
  148. * @param string $b64
  149. * @return mixed|string
  150. */
  151. public function urlSafeB64Decode($b64)
  152. {
  153. $b64 = str_replace(array('-', '_'),
  154. array('+', '/'),
  155. $b64);
  156. return base64_decode($b64);
  157. }
  158. /**
  159. * Override to create a custom header
  160. */
  161. protected function generateJwtHeader($payload, $algorithm)
  162. {
  163. return array(
  164. 'typ' => 'JWT',
  165. 'alg' => $algorithm,
  166. );
  167. }
  168. /**
  169. * @param string $a
  170. * @param string $b
  171. * @return bool
  172. */
  173. protected function hash_equals($a, $b)
  174. {
  175. if (function_exists('hash_equals')) {
  176. return hash_equals($a, $b);
  177. }
  178. $diff = strlen($a) ^ strlen($b);
  179. for ($i = 0; $i < strlen($a) && $i < strlen($b); $i++) {
  180. $diff |= ord($a[$i]) ^ ord($b[$i]);
  181. }
  182. return $diff === 0;
  183. }
  184. }