Unable to decrypt JWE Token from Python jwcrypt to NodeJS jose

题意:无法将使用Python的jwcrypt库加密的JWE(JSON Web Encryption)令牌解密为NodeJS的jose库

问题背景:

I am unable to decrypt a JWE Token generated by jwcrypt (Python library for JWE Encryption and Decryption) to jose (Javascript library for JWE Encryption and Decryption)

我无法使用NodeJS的jose库(一个用于JWE加密和解密的JavaScript库)来解密由Python的jwcrypt库(一个用于JWE加密和解密的Python库)生成的JWE令牌。

I have explicitly defined the A256KW and A256CBC-HS512 on both jwcrypt and jose as well. I also need to provide a "Secret Key" to the JWK's "k" parameter, which will be used for the Symmetric Encryption and Decryption.

我已经在jwcrypt和jose中明确指定了A256KW和A256CBC-HS512。我还需要将一个“密钥”提供给JWK的“k”参数,该参数将用于对称加密和解密。

Below is the Javascript code for jose

下面是使用jose库的JavaScript代码:

import {
  CompactEncrypt,
  compactDecrypt,
  CompactDecryptResult,
  KeyLike,
  importJWK,
} from 'jose';

class JweService {

  async createSecretKey(jwtKey: string): Promise<KeyLike | Uint8Array> {
    const base64Key = Buffer.from(jwtKey).toString('base64');

    return importJWK(
      {
        kty: 'oct',
        use: 'enc',
        k: base64Key
      },
      'A256KW',
    );
  }

  async createAccessToken(
    payload,
    secretKey: KeyLike | Uint8Array,
  ): Promise<string> {
    const encoder = new TextEncoder();

    return new CompactEncrypt(encoder.encode(payload))
      .setProtectedHeader({
        alg: 'A256KW',
        enc: 'A256CBC-HS512',
      })
      .encrypt(secretKey);
  }

  async decryptAccessToken(
    token: string,
    secretKey: KeyLike | Uint8Array,
  ): Promise<string> {
    const decoder = new TextDecoder();
    const { plaintext }: CompactDecryptResult = await compactDecrypt(
      token,
      secretKey,
    );

    return decoder.decode(plaintext);
  }
}

This is how I use the JweService

这是我如何使用JweService

  async createAccessTokenForDemo(reqBody): Promise<string> {
    const secretKey = await this.jweService.createSecretKey(
      'QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2',
    );

    return this.jweService.createAccessToken(
      JSON.stringify(reqBody),
      secretKey,
    );
  }

  async decryptAccessTokenForDemo(reqBody): Promise<any> {
    const secretKey = await this.jweService.createSecretKey(
      'QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2',
    );

    const decrypted = await this.jweService.decryptAccessToken(
      reqBody.Token,
      secretKey,
    );

    return JSON.parse(decrypted);
  }

Now for Python which uses jwcrypto:

现在来看看使用jwcrypto的Python代码:

from jwcrypto import jwk, jwe
from jwcrypto.common import json_encode
import base64

def create_secret_key(jwt_key, alg='A256KW'):
    jwt_key_bytes = jwt_key.encode('utf-8')  # Convert string to bytes
    jwt_key_base64 = base64.b64encode(jwt_key_bytes)  # Base64 encode the bytes

    return jwk.JWK.generate(
        kty = "oct",
        use = "enc",
        k = jwt_key_base64,
        alg = alg
    )

def create_access_token(payload, secret_key, alg='A256KW', enc='A256CBC-HS512'):
    jwetoken = jwe.JWE(payload.encode('utf-8'), json_encode({"alg": alg, "enc": enc}))
    jwetoken.add_recipient(secret_key)
    return jwetoken.serialize(compact=True)

def decrypt_access_token(token, secret_key):
    jwetoken = jwe.JWE()
    jwetoken.deserialize(token)
    jwetoken.decrypt(secret_key)
    return jwetoken.payload.decode('utf-8')

# Secret key and payload
jwt_key = 'QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2'
payload = '{"name": "John"}'

# Create secret key
secret_key = create_secret_key(jwt_key)

# Create JWE access token
access_token = create_access_token(payload, secret_key)
print("Encrypted Payload:", access_token)

# Decrypt JWE access token
decrypted_payload = decrypt_access_token(access_token, secret_key)

print("Decrypted Payload:", decrypted_payload)

This is an example JWE Token from the Python script: (When decrypted using NodeJS jose, jose library returns an error of "Error: decryption operation failed")

这是来自Python脚本的一个JWE令牌示例:(当使用NodeJS的jose库进行解密时,jose库返回了一个“Error: decryption operation failed”(解密操作失败)的错误)

Encrypted JWE token: b'eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.4xd2sw9FFFBZjzOo7ofz-Y7M_rUK_2-DACZPZyvVEHtFVa29eOu43s5rCtlh3xP8_9oMBOuoWXnzJWLEOyxyzl3axDYo-a8Y.QH-iHG2dqduw6r8agd5ZIg.W7MmKBaBEe5EwaGTHfXAUI2MhPFOHMGA5ZkHBNHWqUY.xRUr1jaYxwN5JDkazJ6mQYeSafiXCeidgPurl9qMJLs'
Decrypted JWE token: b'{"name": "John Doe"}'

Is there any configuration I am missing? or is there some encoding that is different between NodeJS and Python behind the scenes which could be the reason why?

我是否遗漏了什么配置?或者,在幕后NodeJS和Python之间是否存在某种不同的编码方式,这可能是导致这个问题的原因?

问题解决:

In the Python code, a new, random 32 bytes key is generated in create_secret_key() with jwk.JWK.generate() with each call. The passed jwt_key is ignored (i.e. k is not the Base64url encoding of QEO8... at all). This can be easily verified by exporting the generated key with secret_key.export() and comparing the k parameter.

在Python代码中,每次调用create_secret_key()函数时,都会使用jwk.JWK.generate()生成一个新的、随机的32字节密钥。传入的jwt_key被忽略了(即k根本不是QEO8...的Base64url编码)。这可以通过导出生成的密钥(使用secret_key.export())并比较k参数来轻松验证。

As a consequence, the decryption of the token with the NodeJS code fails when the key QEO8... is applied.

因此,当应用密钥QEO8...时,使用NodeJS代码解密令牌会失败。


To import a key (instead of generating a new one), the following implementation can be used, s. here:

为了导入一个密钥(而不是生成一个新的),可以使用以下实现,如下所示

def create_secret_key(jwt_key, alg='A256KW'):
    jwt_key_bytes = jwt_key.encode('utf-8')  
    jwt_key_base64url = base64.urlsafe_b64encode(jwt_key_bytes).replace(b"=", b"").decode('utf-8')  

    key = {
        'kty': 'oct',
        'use': 'enc',
        'k': jwt_key_base64url,
        "alg": alg
    }
    
    return jwk.JWK(**key)

Also note that the implementation applies Base64url and not Base64 as defined for JWKs.

还请注意,该实现使用的是Base64url编码,而不是为JWK(JSON Web Key)定义的Base64编码。

With the following code:

使用以下代码:

jwt_key = 'QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2'
secret_key = create_secret_key(jwt_key)
print('Key:' + secret_key.export()) 

the import can be checked: k is UUVPODlLblo4R0pOQVpFaEJHUmxRYUJWdHVBOWFzZDI, which corresponds to the Base64url encoding of QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2.

可以检查导入的密钥:k 是 UUVPODlLblo4R0pOQVpFaEJHUmxRYUJWdHVBOWFzZDI,这对应于 QEO89KnZ8GJNAZEhBGRlQaBVtuA9asd2 的 Base64url 编码。


Also in the corresponding method in the NodeJS code, Base64url must be used instead of Base64:

同样,在NodeJS代码中对应的方法中,也必须使用Base64url而不是Base64:

async createSecretKey(jwtKey) {
    const base64urlKey = Buffer.from(jwtKey).toString('base64url');

    return importJWK(
        {
            kty: 'oct',
            use: 'enc',
            k: base64urlKey
        },
        'A256KW',
    );
}

Now, if an encrypted token is generated with the fixed Python code, e.g.

现在,如果使用固定的Python代码生成一个加密令牌,例如:

eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2Q0JDLUhTNTEyIn0.BAhqPzCOT6u0xEGd40z6sYBU7XWJqwLMWxY1L6B2YYK0YdLvVe6WA8ypHJ6Q0cm4TWFw2xL4n6DeqqyoC9zkEd4fLqU04U4D.pK_f_0tBwXIj7hy79IjC2g.9vtRX6kKCBFoPD2UcfuUyIMevY3d7Pj7ydOM9XBWiJU.cYVtCc9O_8xuBxXg21316rmeNA2NHYPLF3NOjk7RSrw

this can be decrypted with the key QEO8... and the adapted NodeJS code.

那么就可以使用密钥QEO8...和修改后的NodeJS代码来解密它。

作者:营赢盈英

物联沃分享整理
物联沃-IOTWORD物联网 » Unable to decrypt JWE Token from Python jwcrypt to NodeJS jose

发表回复