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.