I have been trying without success to decrypt payload, a JSON web token that should contain personal information as the user data part of OAuth2 flow. According to what I have read of JWT it contains three parts: header, payload and signature - base64 encoded strings separated by a dot.
The JWT in question is this:
eyJjdHkiOiJKV1QiLCJhbGciOiJSU0EtT0FFUCIsImtpZCI6IkJGTDBzcVpEZEtPdnBEX29YSllPNDhlbEhQaldTWVB1WmZSczBtT3VLMUEiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.GFxv1iIUdktUO9f-pf_eS_J34QnBHJdSJPokE3p5XE1nPCRwIYmf3n4kq4X9T7tOAvOIEs7-BoeFedzjRQnXKetrLY5wWHCALt4C_Y8Ibu--WseiQJnIKzyEvuhZHonRN6GWaMZB724M2NhtHHXY4pLX7RNsXquaMBFG1sFny20zHWEjSGx4jDOfKkH-f9Ty5hRIKvZXmQ36RsJI4oF1i4j1aPZj-BKiyiorm5c5F-IUcukhm2QObf88K3EFS0mbVvPM7yNAure51LWptBLIKBBq7VPMfq0NXLL_bBzMZ1XdFBDt2qkIteoE9ts44esTeVvsvNSRHGqAtl8EO-10wg.jOvZCvp2DULxkMyiybRjXw.CCeHLVU2GjsSrdND6UYxIN9Ny_QKig7CC_E3uDsHsb1mRaDV9gQ432NGYpD0RndPQoepOppcDsI2zT2yoKCYLYKuig6XDcOl3eK4-MooDjZOHn_9ahYlAiauTFRtiJfHU9rTSMU_eLkH-z1eyzoe3fbEqkK6hWxHELzlYByeof0pINcDRsxwcNG0GDAqrcJ3IdQjLwoupVGLZh-wb3WsqpZbV5Abz2LkV3BK_TqVjlEBEtKBXOZohdieIwEo1kUm_MZJpXzF6HIJkasrrN1c3-uVmMMJbRBDfZv0GBZ4OyNv9q5vTKCY90RF3QVmN604L9v2JAalOmhLgRq1q8on_dk35nKuhynSrr9lMy2ieTD5VcpbDODpwGHPBQJnn99dbYZqu3uWKYURR2mmfCq1IDEgtq3aczgmNmU6QKQ8vUgljSQ75cqwS9GaHHPHuJZeMXRa1ifkuywf7z5kgk8Z4z_jKA1fqnlYSmRASponkuHqkVx-P5ZlRqfjvR9qpG2qz3B4a1Cjmnp2Di8_5jTg09CLYKBMfa42Fgw5lnlivEcGvWIWWd4a0FvYQlDCjDkRfv21DFkleJ23CyJ7Zjsm2CTPZXk9y6G3HzOYgFxyriIw_52VpnzIrgmYsmpm5amBKGbYYeZbthPwVnf1W76yqBziYljAoIP7g1uM0HdjvkqeDTe3Q5_WQAdrmIz8Yjh2z0edjc0ynPPqKLMfEs8SQWMYsodgQ6KcVMGWalYPCV6eF79Ldf5gpah_oXTMP0nn7rKccoiftlNFsgem2p81yTN7AwWKm_fO70EFdpQ1-kez7lQ5OtamdZWR-W3sEtn3K0wdeLbMYtD4bD8RdwKoZ_DZgBLdJenmXAa7P5dkOzxaeUAKTAaYOSq_EKxIH8AtnqyTTXEfRXLxyoFGJlN_N6bSUWytbJr4c1ktxBG4RKWWJpusVA2hiZ2GCFs7PfWRPC7hSFd8V52O1hwuZsAz6BN4pJv1rIicVSJNozMgyv9ZqY5HkOTIUUztsu_Urkre1ItfVcAg1Ia5Fj-OZ7ymm7sNA9JKBV14T4EPtfAcnoLLRr95ngTzsBPu7KKb-EO7k7m1na_sisQaQxlQNJset5_Hi_Oj2Sj1g7tB7Uld64MlQbvOBxBIw_D9hmmjm_v1cbDcLV2XmmXQotLout197vcJahNQOvSb7W9BqXFvxVlSHa89_H75acO4IMYem3lRJXT2ZdwsQhvFZ8BRSN7h-kKl6Z_v3aRNTrbwA_qQDYlH67uP84F9FKqejejCgNGtqzbvNs4SYv64Wsq9Uz6xhAeCXBuxn9EQc83cKsNkUnLwREHTlng3CpQHpZje6dAEpKKOIN5XHOcj04BNwAZP0TnCeGUWqZpxPI3vyFupC5vb5Wt26BOC1MWb0Upn08xAxwY5urtkX4cRFMpnXFHnv-ypfr2UY5KP6Nh2tbahEgZ-1bBw3GaEPYAqros31pyY6E51nCQ2j3kdmFxVuW3fTp5CzHpAPuTSi6zX6r8sttUi-bJK81CTey8TMFtr5sqpYtlLHbUpSsi-YfYeNCfOmSMLESMQRDSh7LmBFbGK9zDavuTALTNMNPVWeJUQOeAcScQWFZLEg8-attDjdcpxVEZAJIXb9frYD4aDyE4WE0vsRj-kP3d0txJ1_c-mG-PjW0_IVIo_9bUHteHGZdyMS1y6NNOhoTlq0XXAs0oRcd7RVojwUYpbVGAvWT7_CDPTKvnJDFALbAu2D7KWFw1dCnDrUW5HzxW274Y0wx25ev3BKpVE5tekBujVNVmqNjIh._JyU-CpPzHItUbTb_syKsg
The payload comes from a service that is used by the Finnish government, so I would guess that its format should be correct, but first I am confused that the JWT contains five parts. First part is headers:
{"cty":"JWT","alg":"RSA-OAEP","kid":"BFL0sqZDdKOvpD_oXJYO48elHPjWSYPuZfRs0mOuK1A","enc":"A128CBC-HS256"}
Payload should be encrypted with this public/private key:
const jsonKey = {
p: '7C8pWx6iaZDypKgElaJoDGN_OdZh-mAI6jAZkoA9Io5vQrTNzqdFyTF1j_AsU4KMKATvihtKoTAnZnfH1Dk-nCX6GLpiDOeZAXyv-tTGfINl50YhbI-qu4B8h-CPohzOXpBMrOCplQwR60NcW827aNVu1OTBPRboWgtAyqduWGE',
kty: 'RSA',
q: '3Pxz70nzwiNWQwfXQEXA529HoGP6aFBFY2HRnOWF1M7OlN4OnHE-WRlUeyMFA1junBOtUbBhq8hO3X0zPNZVwUrA1IbzoBZUmCTaL4ClZ6dDATfFMrebj8455wCqJNfpkiAXLhA4eh5j1daAWqMyhr9skx5ZslSYbeRM52zmWUc',
d: 'l6a9RMaox9dYchDoOWHZ_IpXLfZZyN2k47fljDluwkIs4wCGjBbvt9nAX2mjsNttWi7mihLcea0PO5wt9dHhzwrB9xbAKlHNh4_IK9YJhdCg47rECtdDhIHqnvTHx4zYWViC6MBorR094pOQy57Hg2kkJhVkeTLeruBmL1QFf7AkoIICJ11FCIwYHJW8bRfWTBbqh8Rnwq2z620ZE16ZsHTp1TfXWWyOXIuMZXAZfdiQq95zR6npkEdGs_es8mwqgXroXmE2UQMEz-r-9DpTXk3_cbXLFbo8PhrpfD8Mxe8hQOdkD5yGqzGz4Xgnl62iCTRZOwdxH3ayH6z9vXDTAQ',
e: 'AQAB',
use: 'enc',
kid: 'BFL0sqZDdKOvpD_oXJYO48elHPjWSYPuZfRs0mOuK1A',
qi: 'XCkgo29q4ha8iwGc9VJq9y2TsAK7WxjcTZlcOoYFkJqzAdxlBvYKBJ4H_2OQvURo5sVhwWQbHrd_nfHVKe8vos9AYz3b0ylQJODse8Xm2w3gaTrwdb4Z5EN2I_2513O60XIeg2WSLMSkvkfaSkJ20OCM7OeZwVyE_N8dTyafy84',
dp: 'dw1y0D6J6aKp3Lvgy7h4sD37JKFe9AEynTGvwjwoFOItTTesQk3pDHiE5RBQl6vHkGikgj4tiUCnq6wXK2_LkpRGE-7ne8_GPYynfE2C28K0PDcKpBlrG2ax3yAf5ryUffBI5h-8-6eA5NEonhH_NOHZIzFIAs4oQzNG7qAlLCE',
alg: 'RS256',
dq: 'WpjVHuD-ojTChLCOOrdeIoOopcTXQDTIfbn4qY4fk-NFJhrzeoeiu_x6ehEdWQX4rNwUTk01faudSYiunN5yQdBXxcmdz5_YBpf1K0xeg2Q7QCCRI_3KNOajLmVDW994zoOBfU0BGm-jFmPeM0p8yGlqJdZnh4jPBR53uNkYhrk',
n: 'y-Fw7PfbnBie2s408nZICscdQ0-spOTeli0LIkFi5QMgGnQ2mQlTXcypm8UdkYu-Up4tzFUmkd-n0gPKES6cP1YGWn9gXC9DP1GmHQVXuGGAxMLkctYK2-CSCckgLtNcmxzdEtwbtwOmWHFdtBUFvwLKnqU0XEv_wdFEakKHwp12foSUoUH7FbzMbLu5BHo4rjuTU2paHQpiHqoG9qNg-jZFXLdjRGWslJKCJGDUBXB3HIlGykr8ghOdsMlpzRn3zQ8WI77bPN58QlGMXVOQcKivsKhoF9mZOHEceAfPLKgucmI-KwJ7w-6DTAuwCI6eo6hlQc_Gz0JxjPJdH3o75w'
}
Currently I am only able to decrypt the payload with this library:
const { Crypto } = require('@peculiar/webcrypto')
const crypto = new Crypto()
// Payload defined above
const parts = payload.split('.')
function ab2str (buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf))
}
const decrypt = async () => {
try {
const k = await crypto.subtle.importKey('jwk', jsonKey, { name: 'RSA-OAEP', hash: 'SHA-1' }, false, ['decrypt'])
const data = await crypto.subtle.decrypt('RSA-OAEP', k, Buffer.from(`${parts[1]}`, 'base64'))
return ab2str(data)
} catch (err) {
console.log(err)
}
}
decrypt()
Decrypting yields ñDõ^îó"ìût=©Z]¬úí³ê which is not a JSON object. Other parts (or combination of them) will throw an error from the library. Changing anything in the payload or key will throw exceptions, so I guess that the phase should be correct, but the output is not.
My question is: how can I decrypt this payload using node.js or is it actually broken?
P.S. Private keys and JWT are using testing environment, so they don't contain any real secrets.
ab2str? What you are probably getting is the direct output of OAEP decryption, which is likely a 128 or 256 bit AES data key (hybrid cryptography). This can then be used to decrypt the next part.Uint8Contentsabove is not correct, it's for a different JWE.