如何在 PHP 中检查没有密钥的 JWT 完整性?

Posted

技术标签:

【中文标题】如何在 PHP 中检查没有密钥的 JWT 完整性?【英文标题】:How to check JWT integrity without secret key in PHP? 【发布时间】:2020-12-26 23:37:49 【问题描述】:

我开发了一个应用程序,它使用了一些不是由它生成的 JWT 令牌。到目前为止,我存储令牌并严格比较标题中给出的令牌与我在数据库中得到的令牌。

我发现的所有 JWT php 库都按此顺序显示示例:

    使用秘密 ky 生成令牌 使用此密钥解码 JWT

我需要检查 JWT 是否有效(关于给定的标头、有效负载和签名),这将添加一个安全层。

如何在没有密钥的情况下“简单地”检查令牌完整性?

【问题讨论】:

【参考方案1】:

JWT 可以是加密签名(或两者,顺序),使用对称非对称算法。

您描述的过程使用 对称 算法,其中使用相同的密钥对令牌进行签名或加密,然后对其进行验证或解密。这适用于由同一应用程序创建和使用的令牌 - 例如,Web 应用程序向浏览器发送令牌,并从请求中读取它。

如果一个应用程序正在创建令牌以供另一个应用程序使用,它应该使用 非对称 算法:

加密某些东西,你需要一个公钥,应用加密算法,最后得到一个只能解密的字符串私钥。一个常见的类比是,公钥就像挂锁:任何人都可以将其锁上,但只有私钥的持有者才能打开它。 要签署什么东西,你需要一个私人密钥,应用签名算法,最终得到任何人都可以阅读的消息,并且可验证 任何拥有 public 密钥的人。类比是,公钥就像某人签名的样本:伪造他们的签名是不够的,但足以判断签名是否真实。

在您的情况下,您需要生成令牌的应用程序使用只有该应用程序知道的私钥签署它。然后可以将相应的 public 密钥共享给服务的每个用户,并存储在需要验证令牌的应用程序的配置中。

您没有指定您正在使用什么库,因此很难具体说明,但您需要寻找的是 sign 您的令牌的选项,而不是 encrypt它。然后您(或控制其他应用程序的任何人)将 public 密钥分发到需要验证它的每个端点,并验证它是否包含具有预期算法和密钥的签名。

例如,使用the lcobucci/jwt library,创建令牌的应用程序将如下所示:

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;

$signer = new Sha256();
$privateKey = new Key('file://path to your private key');

$token = (new Builder())->withClaim('some', 'claim')
                        ->getToken($signer,  $privateKey);

接收它的应用程序会像这样验证它:

use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Key;

$publicKey = new Key('file://path to your public key');

$token = (new Parser())->parse('token taken from request');
if ( $token->verify($signer, $publicKey) ) 
    var_dump($token->getClaims());

else 
    echo 'Token does not have valid signature!';


顺便说一句,如果您根据数据库中的已知列表检查令牌,您实际上并不需要 JWT,您可以使用 GUID 之类的东西并将额外的数据存储在数据库中。 JWT 的想法是您不需要任何先验知识,并依赖于检查签名和令牌内的声明(例如,使用“过期时间”声明来避免攻击者重用他们在日志中找到的旧令牌) .这权衡了令牌的长度和复杂性,以允许去中心化和无状态的应用程序读取令牌。

【讨论】:

【参考方案2】:

请找我的sn-p解码payload,不用key,不用任何库,简单的php代码。

function decodeJWTPayloadOnly($token)
        $tks = explode('.', $token);
        if (count($tks) != 3) 
            return null;
        
        list($headb64, $bodyb64, $cryptob64) = $tks;
        $input=$bodyb64;
        $remainder = strlen($input) % 4;
        if ($remainder) 
            $padlen = 4 - $remainder;
            $input .= str_repeat('=', $padlen);
        
        $input = (base64_decode(strtr($input, '-_', '+/')));

        if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) 
            $obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
         else 
            $max_int_length = strlen((string) PHP_INT_MAX) - 1;
            $json_without_bigints = preg_replace('/:\s*(-?\d'.$max_int_length.',)/', ': "$1"', $input);
            $obj = json_decode($json_without_bigints);
        
        return $obj;

【讨论】:

为什么要重新发明***,自己做 JWT? 请在您的答案中添加一些解释,以便其他人可以从中学习 你拯救了我的一天。 这不安全!它可能会解码您的 jwt - 但它不会验证它【参考方案3】:

如何在没有密钥的情况下“简单地”检查令牌完整性?

假设问题意味着您根本无法访问任何类型的公钥(如果使用非对称签名算法)或秘密(如果使用对称算法),答案是:

你不能!

签名是一个哈希值,由您的标头 + 有效负载 + 密钥/密钥的值计算得出。验证基本上意味着重新计算该哈希并与令牌的签名进行比较。如果您没有密钥或秘密,则无法计算哈希,因此无法证明签名与内容是否匹配。

您没有详细介绍用例、令牌发行者的角色和您的应用程序。但是,如果您想验证由第三方身份验证系统颁发的令牌,它使用非对称算法,您可能会在令牌中找到密钥 id (kid) 和 JWK (JSON Web Key) 端点的 url ,您可以从中获取 JWK 形式的公钥。

而且,如果您只想读取令牌的内容,假设您正在谈论已签名(未加密)的令牌,那么您可以通过简单地解码来访问内容。标头和有效负载只是 Base64url 编码。您无需密钥即可对其进行解码。

【讨论】:

正如我在此页面上的回答中详细解释的那样,非对称密钥加密很久以前就解决了这个用例:任何人都可以使用公钥进行验证,不需要以任何方式保密. 他的问题是关于没有秘密或密钥的验证(它看不到他在哪里指定对称或非对称算法,他写的是“秘密密钥”,而不是“私钥”!)。没有钥匙,他无法验证,对吧?请再次阅读问题和我的回答。 实际上我在写我的答案之前阅读了你的答案,只是因为我认为你可能误解了这个问题而做出了回答。我希望OP能澄清他的真正意思。 “密钥”和“私钥”是同一个意思,因为根据定义,公钥不是“秘密”——任何想要验证令牌的人都可以拥有一个副本并共享它随意。 对于像 HS256 这样的对称算法,使用术语“秘密”或“秘密密钥”,即使它不是密钥。而且我通常不会假设术语总是以最高精度使用。这是您对问题的解释,这很好,但我想 OP 并不是加密术语方面的专家,无法做出如此细微的区分。顺便说一句,我什至提到了通过 JWK 验证公钥。

以上是关于如何在 PHP 中检查没有密钥的 JWT 完整性?的主要内容,如果未能解决你的问题,请参考以下文章

Django JWT - 如果正文中的密钥有效,则允许没有凭据的请求

DefaultJwtParser:如何仅解码 JWT? (没有密钥,没有验证)

使用项目的所有程序员都同意的一个密钥来加密 JWT 是不是更安全或没有必要?

Angular 和 JWT - 客户端如何验证令牌?

如何使用 sha512 为 JWT 生成 RSA 密钥?

如何为 php-jwt 生成密钥对?