Advertisement

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](https://www.rfc-editor.org/rfc/rfc7518#section-6.4 ""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.

可以检查导入的密钥:kUUVPODlLblo4R0pOQVpFaEJHUmxRYUJWdHVBOWFzZDI,这对应于 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代码来解密它。

全部评论 (0)

还没有任何评论哟~