【问题标题】:Verify JsonWebToken signature验证 JsonWebToken 签名
【发布时间】:2018-12-19 04:13:56
【问题描述】:

我收到一个 JWT 并想验证它的签名。 它未加密,采用 base64 编码并使用 HmacSha256 进行签名。 它是用我知道的秘密签名的。

我似乎找不到任何不使用https://jwt.io/ 上列出的第三方库(即 java-jwt、jpose4j 等)来验证签名的示例......

可以这样做吗?

到目前为止我所拥有的:

private boolean validateSignature( String header, String data, String signature, String secretKey ) throws Exception {
    Base64 base64 = new Base64( true );
    SecretKeySpec secret = new SecretKeySpec( secretKey.getBytes(), "HmacSHA256" );
    Mac mac = Mac.getInstance( "HmacSHA256" );
    mac.init( secret );

    byte[] hmacDataBytes = mac.doFinal( data.getBytes( StandardCharsets.UTF_8.name()) );
    String hmacData = new String( hmacDataBytes );

    return hmacData.equals( signature ); // Compare signatures here...
}

基于@pedrofb 和@jps 的答案,这里是解决方案:

private boolean validToken( String authToken, String key ) throws Exception {
    String[] token = authToken.split( "\\." );
    String header = token[0];
    String payload = token[1];
    String originalSignature = token[2];

    SecretKeySpec secret = new SecretKeySpec( Base64.getDecoder().decode( key ), ALGORITM_HMACSHA256 );
    Mac mac = Mac.getInstance( ALGORITM_HMACSHA256 );
    mac.init( secret );

    StringBuilder headerAndPayload = new StringBuilder( header ).append( "." ).append( payload );

    byte[] signatureBytes = mac.doFinal( headerAndPayload.toString().getBytes( StandardCharsets.UTF_8.name() ) );
    String calculatedSignature = Base64.getUrlEncoder().withoutPadding().encodeToString( signatureBytes );

    return calculatedSignature.equals( originalSignature );
}

【问题讨论】:

    标签: java jwt digital-signature


    【解决方案1】:

    要验证签名,您需要获取 Base64Url 编码的标头和有效负载,使用您的秘密计算 HMACSha256 哈希,Base64Url 编码结果并将其与原始签名进行比较。您当然需要 HMACSha256 算法和 Base64#Url 编码的库,但不需要特定的 JWT 库。

    写成伪代码来说明原理:

    hash = HmacSHA256(header + "." + payload , secret)
    

    这里header是Base64Url编码的头部,payload是Base64Url编码的payload

    result = Base64UrlEncode(hash)
    

    result现在可以与原始签名进行比较。

    在另一个answer 中,我用nodes.js 和在线工具描述了这个过程。

    【讨论】:

    • 感谢 jps,请看下面我对 pedro 的回复。
    【解决方案2】:

    一个 JWT 由 base64url 编码的三个部分组成,由点分隔

    header.payload.signature
    

    签名是在header.payload上计算的

    假设你的方法接收到base64url中的元素,你需要在header + "." + data上计算HMAC,将结果编码为base64url,并与签名字段进行比较

    类似这样的:

    private boolean validateSignature( String header, String data, String signature, String secretKey ) throws Exception {
    
        SecretKeySpec secret = new SecretKeySpec( secretKey.getBytes(), "HmacSHA256" );
        Mac mac = Mac.getInstance( "HmacSHA256" );
        mac.init( secret );
    
        String body = header + "." + data;
        byte[] hmacDataBytes = mac.doFinal( body.getBytes( StandardCharsets.UTF_8.name()) );
        String hmacData = Base64.getUrlEncoder().encodeToString( hmacDataBytes );
    
        return hmacData.equals( signature ); // Compare signatures here...
    }
    

    【讨论】:

    • 谢谢佩德罗。已经尝试过了,但 hmac 仍然不等于原始签名。在 Jwt.io 上 - 当我在编码字段输入中输入标题、正文和签名时,它还告诉我签名无效。如果我在 Verfify Signature 容器中选中“secret base64 encoded”复选框,它会自动更新解码输入字段中的签名部分并说签名已验证!有什么想法我可能做错了吗?
    • @ThomasBuckley 你能展示你的令牌和秘密吗(当然只是为了说明的例子,而不是真实的生产例子)如果我们看到你在说什么就更容易了。
    • 你的密钥是用base64编码的吗?然后将secretKey.getBytes() 替换为Base64.getDecoder().decode(secretKey)。如果它不起作用,请在此处发布示例令牌和密钥作为建议 @jps。 jwt.io 验证起来不是很直观。首先插入密钥,然后粘贴令牌。
    • 是的,这个秘密似乎也是 base64 编码的。替换为您建议的代码和计算的签名是 j4jBeXXXXXXJBl-uDXX-dcKzr4O8c3CXXXXXgxKW5WA= 和剥离的令牌签名是 j4jBeXXXXXXJBl-uDXX-dcKzr4O8c3CXXXXXgxKW5WA。所以我使用 Base64.getUrlEncoder().withoutPadding() 来获取计算签名。感谢 Predo 和 jps
    猜你喜欢
    • 2017-08-15
    • 2022-01-09
    • 1970-01-01
    • 2012-07-23
    • 2016-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-28
    相关资源
    最近更新 更多