根据 Base64 编码密钥 (HS256) 解密 JWE 对象的 Python 标准

问题描述 投票:0回答:1

我在解密由 IBM 产品生成的 JWK 时遇到问题。 特别是看看我的例子:

JWE: eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTI1NktXIiwiY3R5IjoiSldUIn0.5Dx7B_0XI8F2ZZzkHjiJkeNsw11LlOuMzln9Z6OuGCAMpLeCOXnnPw.VEV_6HmnlroYO483zJdHFw.jS97NRZaPQfO46J9UvG9YsQ0po2SnUJuCe7M9VNIghD8lyUgdqaGx6xXH6MnAD01VLbjYROwh0z8CFGQ5PbamoiNxzMGM3UHDqvKU4j1pdRkcyPZbyZ6oo-NtY5dlwT6FhMMgu3kk7JKaFKXz0mhyNnvx22QTHKWHpMReEuc4AwdeDBL47iX8kT9cyqBzlGWKl-jLvEM73gUzPLC8RxG9_mtyIzEqyiGWtbDavD4yqf7lgo39jBIvwBu-VDVW05A.o15bGBayvRp9Dgzlqd2WAw

JWK: { "alg": "A256KW", "kty": "oct", "use": "enc", "k": "hD-S5Ll-StGTM6K0N891J3KdAgLVdUNRuKCpiweXJh8", "kid": "test"}

现在,这是我的 Python3 代码:

import base64

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap

encrypted_jwe = ('eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiQTI1NktXIiwiY3R5IjoiSldUIn0'
                 '.5Dx7B_0XI8F2ZZzkHjiJkeNsw11LlOuMzln9Z6OuGCAMpLeCOXnnPw.VEV_6HmnlroYO483zJdHFw'
                 '.jS97NRZaPQfO46J9UvG9YsQ0po2SnUJuCe7M9VNIghD8lyUgdqaGx6xXH6MnAD01VLbjYROwh0z8CFGQ5PbamoiNxzMGM3UHDqvKU4j1pdRkcyPZbyZ6oo-NtY5dlwT6FhMMgu3kk7JKaFKXz0mhyNnvx22QTHKWHpMReEuc4AwdeDBL47iX8kT9cyqBzlGWKl-jLvEM73gUzPLC8RxG9_mtyIzEqyiGWtbDavD4yqf7lgo39jBIvwBu-VDVW05A.o15bGBayvRp9Dgzlqd2WAw')


jwk = {
    "kty": "oct",
    "k": "hD-S5Ll-StGTM6K0N891J3KdAgLVdUNRuKCpiweXJh8"
}

parts = encrypted_jwe.split('.')

if len(parts) != 5:
    print("invalid JWE")
    exit(1)

header, encrypted_key, iv, ciphertext, tag = parts


cek_encrypted = base64.urlsafe_b64decode( encrypted_key + '=' * (-len(encrypted_key) % 4))

cek_key = base64.urlsafe_b64decode(jwk['k'] + '=' )

cek = aes_key_unwrap(cek_key, cek_encrypted)


print("decrypted CEK:", cek)


decoded_ciphertext = base64.urlsafe_b64decode(ciphertext + '=' * (-len(ciphertext) % 4))

decoded_iv = base64.urlsafe_b64decode(iv + '=' * (-len(iv) % 4))

cipher = Cipher(algorithms.AES(cek), modes.CBC(decoded_iv), backend=default_backend())

decryptor = cipher.decryptor()
decrypted_payload = decryptor.update(decoded_ciphertext) + decryptor.finalize()

print("decrypted Payload:", decrypted_payload)

从技术上讲,我认为我需要执行以下步骤:

  1. 我的 JWK 对象的 k 值的 Base64Decode
  2. 对加密的CEK进行Base64解码,并使用从(1)中获得的密钥使用A256KW进行解密
  3. 使用现已解密的 CEK 创建 AES128CBC 解密器,以便使用 JWE 提供的解码 IV 来解密密文的 Base64Decode

但是此代码会产生错误:

cryptography.hazmat.primitives.keywrap.InvalidUnwrap

我做错了什么?

我需要一个能够完成这项工作的 python 脚本的基线。

谢谢你

python-3.x encryption jwt aes jwe
1个回答
0
投票

JWE 令牌无法用 JWK 解密,问题发生在解包主密钥时。可能的解释是 JWE 令牌和 JWK 不匹配。

发布的 Python 代码适用于有效的 JWE 令牌和 JWK,至少就解包主键而言是这样。第二部分,即payload的解密失败,因为使用主密钥作为解密的密钥。

正确的方法是将主键减半。最后16个字节是解密的密钥。前 16 个字节是通过 HMAC/SHA256 进行身份验证的密钥。
您在评论中写道您不需要身份验证。但这是一个不应被忽视的安全组件(特别是因为身份验证工作量很小)。

为了演示这一点,我使用以下有效数据:

JWK:

{'kty': 'oct', 'k': 'c4OqgE8K3OMf-9W0lxa8EGSY5eeebSnVKvfZu9AssAg'}

JWE代币:

eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.5ij3S9PQkJ0YPfVfoZ9Mzlu0m6_xQJU6JJnljm0IZOFvEGIYhbWk9Q.iDQqf4q20QVCMPMS-g64kw.uKD4LKZosgXcmKWMZtI4JwpjjGZMa0qDo8jRZsLnSRkI02Fr3trUTJMo4isr0TouSVnrHezgIRU0_jF-KCCDUA.4AwFYtD0o8JXaA2_Ha0AOw

标头经过 base64url 解码:

{"alg":"A256KW","enc":"A128CBC-HS256"}

并应用与问题中发布的 JWE 令牌相同的

alg
enc
值。 IE。主密钥用A256KW包裹,用于明文加密A128CBC-HS256

以下Python代码基于您发布的代码,并添加了身份验证:

import base64
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.keywrap import aes_key_unwrap
from cryptography.hazmat.primitives import hashes, hmac

def b64url_decode(b64data):
    b64data = b64data.encode('ascii')
    return base64.urlsafe_b64decode(b64data + b'=' * (-len(b64data) % 4))
  
# valid data
encrypted_jwe = 'eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.5ij3S9PQkJ0YPfVfoZ9Mzlu0m6_xQJU6JJnljm0IZOFvEGIYhbWk9Q.iDQqf4q20QVCMPMS-g64kw.uKD4LKZosgXcmKWMZtI4JwpjjGZMa0qDo8jRZsLnSRkI02Fr3trUTJMo4isr0TouSVnrHezgIRU0_jF-KCCDUA.4AwFYtD0o8JXaA2_Ha0AOw'
jwk = {'kty': 'oct', 'k': 'c4OqgE8K3OMf-9W0lxa8EGSY5eeebSnVKvfZu9AssAg'}

parts = encrypted_jwe.split('.')
if len(parts) != 5:
    print("invalid JWE")
    exit(1)
header, encrypted_key, iv, ciphertext, tag = parts

cek_encrypted = b64url_decode(encrypted_key)
cek_key = b64url_decode(jwk['k'])
cek = aes_key_unwrap(cek_key, cek_encrypted)
auth_key = cek[:16]
enc_key = cek[16:]
decoded_ciphertext = b64url_decode(ciphertext)
decoded_iv = b64url_decode(iv)

# authenticate
decoded_tag = b64url_decode(tag)
authData = header.encode('ascii') + decoded_iv + decoded_ciphertext + (len(header) * 8).to_bytes(8, 'big')
h = hmac.HMAC(auth_key, hashes.SHA256())
h.update(authData)
tag_calc = h.finalize()
auth_suceeded = tag_calc[:16] == decoded_tag # compare the first 16 bytes from the calculated tag

# on success, decrypt
if auth_suceeded:
    cipher = Cipher(algorithms.AES(enc_key), modes.CBC(decoded_iv), backend=default_backend())
    decryptor = cipher.decryptor()
    decrypted_payload = decryptor.update(decoded_ciphertext) + decryptor.finalize()
    print("decrypted Payload:", decrypted_payload.decode()) # decrypted Payload: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
else:
    print("decrypted failed")

但是,使用 JOSE 实现效率更高,例如JWC加密

from jwcrypto import jwk, jwe

encrypted_jwe = 'eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.5ij3S9PQkJ0YPfVfoZ9Mzlu0m6_xQJU6JJnljm0IZOFvEGIYhbWk9Q.iDQqf4q20QVCMPMS-g64kw.uKD4LKZosgXcmKWMZtI4JwpjjGZMa0qDo8jRZsLnSRkI02Fr3trUTJMo4isr0TouSVnrHezgIRU0_jF-KCCDUA.4AwFYtD0o8JXaA2_Ha0AOw'
jwkey = {'kty': 'oct', 'k': 'c4OqgE8K3OMf-9W0lxa8EGSY5eeebSnVKvfZu9AssAg'}

jwetoken = jwe.JWE()
jwetoken.deserialize(encrypted_jwe)
jwetoken.decrypt(jwk.JWK(**jwkey))
payload = jwetoken.payload

print("with JWCrypto:", payload.decode('utf8')) # with JWCrypto: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
© www.soinside.com 2019 - 2024. All rights reserved.