我在解密由 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)
从技术上讲,我认为我需要执行以下步骤:
但是此代码会产生错误:
cryptography.hazmat.primitives.keywrap.InvalidUnwrap
我做错了什么?
我需要一个能够完成这项工作的 python 脚本的基线。
谢谢你
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}