Jwt.php 4.9 KB

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