windindind 发表于 2024-8-29 22:29

这个python代码要如何修改,才能使得token结果与java代码一致?

本帖最后由 windindind 于 2024-8-30 10:29 编辑

我从一个jar包中查询到jwt token生成的过程,关键代码如下
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret1.getBytes(StandardCharsets.UTF_8),"HmacSHA256"));
mac.update(secret2.getBytes(StandardCharsets.UTF_8));
jwtSecret = new String(mac.doFinal());

Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
String token = JWT.create()
.withClaim("a", a)
……
.sign(algorithm);
System.out.println("Generated JWT Token: " + token);

ai告诉我等效的python代码如下
mac = hmac.new(secret1.encode('utf-8'), digestmod=hashlib.sha256)
mac.update(secret2.encode('utf-8'))
jwt_secret = mac.digest()

token = jwt.encode({
"a": a,
……
}, jwt_secret, headers={"typ": "JWT", "alg": "HS256"}, sort_headers=False)
print(token)

经过反复调试,终于使得两段代码生成的JWT token中,header和payload完全一致(其实就是增加“sort_headers=False”,python默认会对header排序)
问题是,我传入相同的secret1、secret2两段代码生成的token,signature部分完全不同
经过调试,我觉得关键在于jwt_secret的生成上,python的mac.digest()无法等同java的mac.doFinal(),但是我不知道要怎么处理

还请大佬指点一下,python下应该怎么写,才能使得生成的token相同

爱飞的猫 发表于 2024-8-30 05:34

代码不全,有部分代码省略了。
建议放出完整的可执行的部分代码。

windindind 发表于 2024-8-30 08:04

爱飞的猫 发表于 2024-8-30 05:34
代码不全,有部分代码省略了。
建议放出完整的可执行的部分代码。

补充完整可执行的代码
package com.example;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

public class test {
    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {

      String jwtSecret;
      String secret1 = "key1";
      String secret2 = "key2";
      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(new SecretKeySpec(secret1.getBytes(StandardCharsets.UTF_8),
                "HmacSHA256"));
      mac.update(secret2.getBytes(StandardCharsets.UTF_8));
      jwtSecret = new String(mac.doFinal(), StandardCharsets.UTF_8);
      System.out.println("jwtSecret: " + jwtSecret);
      Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
      String token = JWT.create()
                .withClaim("a", "b")
                .sign(algorithm);
      System.out.println("Generated JWT Token: " + token);
    }
}
以上代码运行结果应该输出的是
Generated JWT Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYiJ9.53lahvsO7tuBgvpEM5iVtB8p7y6Lm3vx9cWa6aPZ_YE

import hmac
import hashlib
import jwt

secret1 = "key1"
secret2 = "key2"
mac = hmac.new(secret1.encode('utf-8'), digestmod=hashlib.sha256)
mac.update(secret2.encode('utf-8'))
jwt_secret = mac.digest()

token = jwt.encode({"a": "b"}, jwt_secret, headers={"typ": "JWT", "alg": "HS256"}, sort_headers=False)
print(f"Generated JWT Token: {token}")
以上代码运行结果应该输出的是
Generated JWT Token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoiYiJ9.siAARkN0H1OXXdPg9N10ARjK9ga2-D_eC_O8SL3urHM

可以看到signature部分是不同的

爱飞的猫 发表于 2024-8-30 09:29

本帖最后由 爱飞的猫 于 2024-8-30 09:32 编辑

Java 处理文字转码的情况和 Python 不一样,遇到非法字符的时候会替换为 `EF BF BD`

Python 也让他这么处理就行。

此外,或许是我找的 Java JWT 库版本不同,我找的这个版本会对 header 进行排序,因此我在 py 版本也做了对应的更改。最终生成的 token 一致。
实际上不需要管它要不要进行排序,因为签名验证是对你提供的**编码后的数据**进行校验的(先校验,后反序列化)。

---

Java 可以观察到这一现象:

```java
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;

public class Main {
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
      String jwtSecret;
      String secret1 = "key1";
      String secret2 = "key2";
      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(new SecretKeySpec(secret1.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
      mac.update(secret2.getBytes(StandardCharsets.UTF_8));
      byte[] hmac_digest = mac.doFinal();
      System.out.print("hmac_digest: ");
      for (byte b : hmac_digest) {
            System.out.printf("%02X ", b);
      }
      System.out.println();

      jwtSecret = new String(hmac_digest, StandardCharsets.UTF_8);
      System.out.println("jwtSecret: " + jwtSecret);
      byte[] secretBytes = jwtSecret.getBytes(StandardCharsets.UTF_8);
      for (byte b : secretBytes) {
            System.out.printf("%02X ", b);
      }
      System.out.println();

      {
            Algorithm algorithm = Algorithm.HMAC256(jwtSecret);
            String token = JWT.create()
                  .withClaim("a", "b")
                  .sign(algorithm);
            System.out.println("Generated JWT Token (jwtSecret): " + token);
      }
      {
            Algorithm algorithm = Algorithm.HMAC256(secretBytes);
            String token = JWT.create()
                  .withClaim("a", "b")
                  .sign(algorithm);
            System.out.println("Generated JWT Token (secretBytes): " + token);
      }
    }
}
```

输出(注意打印 jwtSecret 数组时,多次出现的 `EF BF BD`):

```
hmac_digest: BA 20 B4 F6 37 E6 2A 43 D8 36 81 FA 4C 22 24 EA AA 06 DA 9D CE 9C 23 EA 63 E3 07 2D A0 B2 B8 04
jwtSecret: ? ??7?*C?6??L"$???#?c?-???
EF BF BD 20 EF BF BD EF BF BD 37 EF BF BD 2A 43 EF BF BD 36 EF BF BD EF BF BD 4C 22 24 EF BF BD 06 DA 9D CE 9C 23 EF BF BD 63 EF BF BD 07 2D EF BF BD EF BF BD EF BF BD 04
Generated JWT Token (jwtSecret): eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.5bL_MzQ0Kv6RcnfeG9e5vw5YC-dHJTwFTUAv6roVdBA
Generated JWT Token (secretBytes): eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.5bL_MzQ0Kv6RcnfeG9e5vw5YC-dHJTwFTUAv6roVdBA
```

Python:

```py
import hmac
import hashlib
import jwt

secret1 = "key1"
secret2 = "key2"
mac = hmac.new(secret1.encode('utf-8'), digestmod=hashlib.sha256)
mac.update(secret2.encode('utf-8'))
jwt_secret = mac.digest()

jwt_secret_bytes = jwt_secret.decode('utf-8', errors='replace')

print(f'JWT Secret (bytes): {jwt_secret_bytes.encode('utf-8')}')
print(f'JWT Secret (bytes/old): {jwt_secret}')

token = jwt.encode({"a": "b"}, jwt_secret_bytes, headers={"typ": "JWT", "alg": "HS256"}, sort_headers=True)
print(f"Generated JWT Token: {token}")
```

跑 py 版本可以看到也加入了对应的 `ef bf bd` 字符:

```
JWT Secret (bytes): b'\xef\xbf\xbd \xef\xbf\xbd\xef\xbf\xbd7\xef\xbf\xbd*C\xef\xbf\xbd6\xef\xbf\xbd\xef\xbf\xbdL"$\xef\xbf\xbd\x06\xda\x9d\xce\x9c#\xef\xbf\xbdc\xef\xbf\xbd\x07-\xef\xbf\xbd\xef\xbf\xbd\xef\xbf\xbd\x04'
JWT Secret (bytes/old): b'\xba \xb4\xf67\xe6*C\xd86\x81\xfaL"$\xea\xaa\x06\xda\x9d\xce\x9c#\xeac\xe3\x07-\xa0\xb2\xb8\x04'
Generated JWT Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhIjoiYiJ9.5bL_MzQ0Kv6RcnfeG9e5vw5YC-dHJTwFTUAv6roVdBA
```

windindind 发表于 2024-8-30 10:13

爱飞的猫 发表于 2024-8-30 09:29
Java 处理文字转码的情况和 Python 不一样,遇到非法字符的时候会替换为 `EF BF BD`

Python 也让他 ...

感谢指导

关于header 进行排序,我知道签名的过程是先编码后加密:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),secret)
一开始我发现生成的token签名不一致的时候,header部分也不一致,我以为是这个原因造成的,所以死磕header排序
当然,签名验证的时候,只要secret和token正确,与header排序无关了

“Java 处理文字转码的情况和 Python 不一样,遇到非法字符的时候会替换为EF BF BD”
这点我在逐字节对比的时候有发现,当数值为正数的时候,两者是一致的,当出现负数的时候,就对应EF BF BD,但是我不知道原因,也就无从下手了

爱飞的猫 发表于 2024-8-30 16:42

windindind 发表于 2024-8-30 10:13
感谢指导

关于header 进行排序,我知道签名的过程是先编码后加密:HMACSHA256(base64UrlEncode(header ...

负数通常是遇到非法字节啦。utf8 编码规则可以有负数,但是需要遵守它的编码规则。不过我不清楚它会不会验证是否能映射到具体字符。

如果你去搜索EF BF BD,可以发现它的名字是“REPLACEMENT CHARACTER”,即无法正确编码时,替换为这个字符表示错误。
页: [1]
查看完整版本: 这个python代码要如何修改,才能使得token结果与java代码一致?