Roku Developer Program

Join our online forum to talk to Roku developers and fellow channel creators. Ask questions, share tips with the community, and find helpful resources.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
danielFav
Channel Surfer

How to handle encrypted content

Jump to solution

Hi everyone, im new to the roku community.

For the last couple of days i've been trying to decrypt a JWE without any progress so far. The Web service response comes in the following format: aaaa.bbbb.cccc.dddd.eeee

  • a: The JWE header, it comes with these algorithm and encryption methods: {"alg":"A256KW","enc":"A128CBC-HS256"}
  • b: The key
  • c: iv
  • d: payload
  • e: Authenticated Tag

A made a simple code to try and break the encryption, but i couldn't get past the setup stage, because it is always returning -1. The documentation says it needs to be a 0 for a successful atempt. Here is the pseudo code:

myPrivateKey = "..." 

responseKey = "..."
responseIv = "..."

responseKey = base64UrlToBase64(responseKey)
responseIv = base64UrlToBase64(responseIv)

pkba = CreateObject("roByteArray")
ivba = CreateObject("roByteArray")

pkba.fromBase64String(responseKey)
ivba.fromBase64String(responseIv)

cipher = CreateObject("roEVPcipher")
result = cipher.setup(false, "aes-256", UCase(pkba.toHexString()), UCase(ivba.toHexString(), 0)

print result

//it always shows -1

Does anyone have a hint about what i'm doing wrong and what could i do to try and get around this problem? If needed i can provide real values for testing

 

0 Kudos
1 Solution

Accepted Solutions
danielFav
Channel Surfer

Re: How to handle encrypted content

Jump to solution

In the end, i decided to implement the key unwrap algorithm manually, and it was able to correctly decrypt the jwe. Here is the full solution: 

jwe =
"eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.EoOZqNOs89BXG4KUq8VY4MuMw3iRjPvO2OfgTCXaWDV7CiOrT-RDrA.EEblXQ90Va5dRM2GagfX1A.2Cr-DulZOR68eZsewEfSAczf82EbPHVwQDhuSUA6KAihDZ1JZoHSExrhnMl1PqoEHyHe5c8oxty7MWACp4PC0Dlt7KqIyERfB0LURk7H_Btm25V0NqlghiTVd0YPmkiCuiEUduPMWGeUyJWJ3GcvYUxDNJKvc6Vm0cZbpiRnn-Se-vEDZQP9WNiHLSvrvjPlbk_m0rq5-_4rHlNwN4ubDnqJQv6PWvuE2gzxs6G2E5KYULClhcHyZOjyIBKO9sNZI0RTff9MfRQ2O6SLn9hBtLOFbf43P3693wBM2CzS8cVOJokH_jn22TD-og4nDPaDfzTcmR4379TW__0S-ZE1vVLMM5acZ2l-Its_St5Mu5-krKThJjW2tN8PS5h7pFiRWVQmSAeTTcsZidwh-ffIXikDkSa61ChADfaUx86yee438idA2RIR50ONSuoiBoD8Vpom4cHhvqiVY5CEqoVsDnMsN1lPXEBWthX9Iz0KJTg2SlTMQDy8vGZEu9HQTZZHiuLCNLcQN4OFKot0jvHLaFs61lOW7_nWX_BKI626WecF3SLsqOEPSaHXWKHU6oxbwG7VF3Kjhk9bGB61bkitRb6ijJopD1R3tUpFk4t3pv8P6DXlC1b0t_MO1F1ubiiHSL60eCZbXcaSM9SJ92YzXy0xZOp_mLfTL7yunzanYcB0mhwQeisXLwYlu_xLm8_OS1DZ3RZFlFNiv0ibf9vuc-Br6RhFbG3ICbCqwqWy7uMhe4_5Eg4FcS0gxpqaMuW8QoyNylP6HEDOvXedGhlCnACaGAHxhToygREI-jQuDza3vf5bBXI5E8kle5j2N5wmM5OXxGruHaOiKuuZYZFsFLhPROQU6xnS8TxaLUMcay4xAygiAfIch5VFm7jmVRXiasj-ZhBmGb4dg8PHuAxBxCjWy7H6BRFaFCz6-qoTCviKsujFPlZL50vW5qfBWymkFaOd1p9R3qB7Tmd5YfqdlK6hBogfwIFxm6ZUfP1mFVYDumXBgmc3ZLWAxfRx3kVbS3_vdZkpBhDqznQR5EO1gwmCNeWQa1s9Lm2w6oYBXA7X9WFOW2ubWTqUG-vpqcYWBaLgWwlWu_wUphdzBtduoNq-vRe1ms0OGzXzn8P78eGsKZ-C5KOFgpc8APhbm9SJPaXyv3ZsLImYELRPSLJob5wCLnAgbz6zvLfODCH3LasulgqriEw3i1nnmEgeyNSbrw-Z3KAAd4uPyObQrTeOcnKGQ2NUGudST9P7kjglpTLiTGsIs_QED2kaTyFVgLoXk0LLHKjANO0EtS9Ncb_5zJQdbu_IaG4b6PHdnMkzYHRCA4qHk66dcUlfzCcbHRn8Wrft8aN99Xv7n6zg0mbSqT-c2rgInslxCmkoDr3ZzpohADi9M_-6tmzLPw1FUl1aWBG6nflYq42cjAAz4GdvVaNh-Z8WHfsPZCeziEhZeBeiI-oj2RJBQcAQ-KvgxxJjQ_vBxclMCh4M9EZWzZ0d7t2WoqGHag24_pl9o30XppUhzRtl0z9evzg1d8-VsQT-KKgQK75E4Vn0KsLUBpU2z8_QyB-QNSzzSd4P3JRVOpxiPMRMC_vHRgvy1Td2k6RO9rK2sQ5Y7FhtYk1IiFPkMVZxpCxqnffB80vncWkxOhGmbXtP1_1vitLEXHX7RKIKefee3RVUo2jdDBrC2Ksfrd6fwiFKUQInuC_5PkOvz610PwwauAFGAOq5nNtS1hPh-LvdFVnf-8zB0LgSSm6tOVIbMsLyidF3D7gUiddv7sX0J07WIkZI8H_tQJAHo-LNHA3JPmGT8w7cI-XWKo-q7hMSO5Kt8GLJ4G_b7yYDcS3chnEDWpAMaN3mas6qq4JoymvHjYtCE5tuox8gBzgzlNhmXYzbyBiD4H5MlvkHya3I_TeVinl09RaNXi-sYoNpsisDobDn1QQBI2vjMull1lTwIJJlfekeKFhWzZl1NdacJwMapvopewpLs3VQmpAaRVu35Q4zpJXG3K57bVYKO8DIME-KyD1WvgS0DlsQVIomCKFUlWgOd68ITMOxe3jEmk_0ndDfVObhtFj-bVby2NbvGw2004o4nEF8-pwT0jCmcfkz8uytsAOkqfXIhzwxI8A-gK4oV4CpEbBpiEqa8K8hT9EnDy1xnMZssy-CiQbL9CP9iJIoidhq5y8yKcW0FTfZauOooagI6mRvx03TVJvjX-umAD0JQSEFE0hWSD5ELhFNF2M-GQ9gSQsBKhzwdwWS4_h_fc-JIXMiZOd01Si2TI1rfqjtENt8w3TEg4ATqsRsm1qUiA9rzTxleP9OqwHwEDqpOwsIkWd8xX0eyoeQBEMJS02UHNNLdm87IoW4ZJxkiGkde6wPj0n-K8gxL3_bl_dygptuVJ5J4Qx5JXjOHEhA451QtLKz0SXTR0wvjp6zKFYsObEV-bN1Q5gOJptzUMh98huNhRCANbx6rMugAArKTRDDygGqIbnMEeT84ap3dhSw0h-RV_QSpHvYxg9PFSVsl1X1F5ZcL9bkssR31qTAdXiVJONg_gtmRoZFDjAfOjsiJChT8kbGhNP-JqFAiiYH4ilwT-zT9oEPkTwHveV638ote3_NhvOPKsVxiY84jV0AYtCUF82eYTjrqe1knSt4k3xZ_chR34g5oRVzERdhA_wfibk0tpc6rhKQEXXlKXl1-Boh_Hw5KLbWGMEcIXW6ms4XAqMHjhsIeX4livD8l2C3bLmGzLwwa1h19g2ZUJr9HY3T0WrG23alMkH20DtFn5WGgZmuXyJSzgVsiUajw0AZKK9MREIX90JE_UcSnk9uXogmeE88P65H05Nw6AAezc8b-_ym8X9-0xp7W8hLZPIQ1S2-cZRIvJMBvVVoYLD7VGBqjkugc58SnoETXX4QNAooH8uBIiVC3vDM0zo2OzBIRVUSG0SCvKMG6Q-w3fnSWcnHGVfHuURJV2vFJypwjkF0-MzH9VD2ydbcOcD49KkSK6ILdtMMeQ4YmWi5jTpoJyDzo5u8FURQ0vAK3X0lG8kpqI-ib8GhgL89Or64XktayAcD-pNDWXQ87siPeqYFxQOm_WvWDOLo4RTk9aPsXdLj-4728J1sJHSKmN3PEFINMO2F3ldhUlazfhFv6Z9-GfFLX4fWSLGeH1wCqYOOdaiJWulIQCsxzP3QYyM5LdBynC2N4qSsIGndpiO0trC1BWrRawmefh3JbvzEU5poLyYYbImKU6fjshet4AwEyYbl4NFISD3OXfYdnIvRIpxosXJhWnQjgBRHlRMa7RcVD_S65eop49giaGxGpy7_-LkCw2rQilwPy7y9sZBqBJw6okZYvPfodxu5mPjqnghJx9t3pbi8eWMf1JAttfuUZd1anqHZFg3jRmwR2qX8gmmIk_6lLzwLNkVAsAnTaUlPnQzdLQBPsUpp3WJv0v-VpJE0OpoEkzuhHPXzIkEioxLhXJsjOXz1Vc5wohP-uWsT9atjNhe8lT0j_YV5O70h_qVjG39uZjNOJvXlH603tU1O-vo6PJVe3nYnxOw5GfxQ1bUCo2G0DxrjlQ.ubwItxMBO1_Qsnf6s5VRJQ"

key = "q2nj7sc1m30as24m4nf1mnv[]q981.,a"

result = jwe.split(".")

resultingKey = aesKeyUnwrap(key, result[1])

encodedIvBa = CreateObject("roByteArray")
encodedIv = base64UrlToBase64(result[2])
encodedIvBa.fromBase64String(encodedIv)

payloadBa = CreateObject("roByteArray")
payload = base64UrlToBase64(result[3])
payloadBa.fromBase64String(payload)

cipher = CreateObject("roEvpCipher")
cipher.setup(false, "aes-128-cbc", resultingKey, encodedIvBa.toHexString(), 0)

text = cipher.process(payloadBa)

? text.toAsciiString()

Im not going to explain how the algorithm works. You can read about it in the RFC 3394. Below is the implementation for aes key unwrap

function xor(p as object,q as object) as object
    pSize = p.count()
    qSize = q.count()

    counter = 0
    for i = pSize - 1 to pSize - qSize step -1
        p[i] = intxor(p[i], q[counter])
        counter ++
    end for
    return p
end function

function intxor(p as integer, q as integer) as integer
    bitwiseAnd = p and q
    bitwiseOr = p or q
    return bitwiseOr and not bitwiseAnd
end function

function unWrapCore(privateKey as object, a as object, r as object)

    cipher = CreateObject("roEvpCipher")
    dd = cipher.setup(false, "aes-256-ecb", privateKey.toHexString(), "", 0)

    if dd = 0
        n = r.count()
        for j = 5 to 0 step -1:
            for i = n - 1 to 0 step -1:
                x = ((n * j) + i + 1)
                ba = CreateObject("roByteArray")
                ba.push(x)
                y = xor(a, ba)
    
                y.append(r[i])
    
                b = cipher.update(y)
    
                if b<> invalid
                    a = CreateObject("roByteArray")
                
                    for l = 0 to 7 step 1:
                        a.push(b[l])
                    end for
        
                    r[i] = CreateObject("roByteArray")
                    for o = 8 to b.count() - 1 step 1:
                        r[i].push(b[o])
                    end for
        
                end if
               
            end for
        end for
    
        newArray = CreateObject("roByteArray")

        for each item in r
            newArray.append(item)
        end for

        result = CreateObject("roByteArray")

        for t = 16 to newArray.count() -1 step 1:
            result.push(newArray[t])
            
        end for

        return result.toHexString()
    end if

    return -1
    

end function

function aesKeyUnwrap(privateKey as string, encodedKey as string)

    privateKeyBa = CreateObject("roByteArray")
    privateKeyBa.fromAsciiString(privateKey)

    encodedKeyBa = CreateObject("roByteArray")
    encodedKey = base64UrlToBase64(encodedKey)
    encodedKeyBa.fromBase64String(encodedKey)

    array = []

    for i = 0 to encodedKeyBa.count() step 8
        ba = CreateObject("roByteArray")
        for j = i to i + 7 step 1
            ba.push(encodedKeyBa[j])
        end for
        array.push(ba)
    end for

    array.pop()
    a = array.shift()
    ba = CreateObject("roByteArray")
    ba.push(24)

    return unWrapCore(privateKeyBa, a, array)

end function

View solution in original post

0 Kudos
8 REPLIES 8
renojim
Community Streaming Expert

Re: How to handle encrypted content

Jump to solution

It's been a while since I've used the EVP stuff and I recall having difficulties when I first tried.  I would suspect something with the hex strings for the key and IV.  If they're not exactly the correct number of characters it won't work (e.g., "0" won't work for an IV that should be 8 hex values - "0000000000000000").  You might want to start by setting up an encoder with example key and IV, encode a string, and then decode it leaving out all the ba and base64 stuff.

If you want to PM me some real data I'll see if I can rattle my brain into remembering how it's done.

Roku Community Streaming Expert

Help others find this answer and click "Accept as Solution."
If you appreciate my answer, maybe give me a Kudo.

I am not a Roku employee.
danielFav
Channel Surfer

Re: How to handle encrypted content

Jump to solution

I've managed to get a real JWE example we will be using . This is just automated testing data and does not contain sensitive information. Below is an example of a JWE:

eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.j1RBCCV2E6YRUD_RI1oPtQLNDjg2tWELjNit29PzUrSvwx060YcKeg.OJecPTIfpng9qZCIJh2-3g.AOYr-AJ_AqDgx7HjzNFPaiDJjhmQCMkwDX31bQO1Rh_Ui2MI3PECVow28Ym2RrUG6jxrUeSGA1Q6G_RdVk1P9-Op4NIFm26XgYx5dq57LXY9-9Be0WaRGq2HvXNFeb-LpmC1UjQ2BDReTwHynOrg98ETjz8j1XQunHSlmdhT1A42_PRsUTo1yyi7kRtIshPSXUlfxZA0hFubnJf2KQWy_hU42b01UUcBnuPEza6Ne_Y2Et-_qmGNRQDDj34R0afXRiByWJJ8YoUNAZrmVydvFEASYMIvB7HQE1dB8QfFhabfipA875bDi98ix5jdPprELykBnwG9KpOZS1RS1hfXBjKuZDuu7PKQdDbDmxe0CtwdA6mWkohDpqX1UeRdVqmcQZs906z0bQ3CvaOdHCx4dSMDRJIflIwsfw3PkzENSEpHLfOJVdi-CabE0iBeGjgcuffkEDqEf2llSj-wqiIfzwn9kyWH_8_rVWwWX6TTWSbaGIMWcxGgRogVKiusYCqqYFtQmtQc6SwkJOARkHQkscZGV9YE1wCQNl_ZtgD6QfRJRHYRU_LOMCb3HKrFyeyIlSfjFMDS5vY1nP5xWOZLqU_ELLMOOEeyBCWzJMwH-t-NMpg8NlUnK5mrcGlIVGmlSdQd1rh1GyfZGQHW6AmQvNSDOMydX-AM_r1ZkuLt3q5yvu6iwKphbZwFz4b9-CgxOGAKH2MI7pAeJHXbaklDisSNZyMJ1MIkyl8sxBQpe9DZRG1WXN1ySYJ4obOaqNahX9tz8-5a3uWscinI016gOQK3zG1dUx43ta3-Q6E7QOlV1SWYSAmrR-bTLT4q2BafWYuhNsR-9_9SYkAHUEFO6hu9yXqvkpbLV7HBz4m-nMQ-iBmrnhSdm1UZRxZQjJOpcaLEEscfD5h6FuliPCE0fdxMge6QM9emsGczonQ2CD2zlgMSHg_PX1VQ98wQy0H6tZBEHiReNTU7_ezUg_YR-ocfgvcQj2h0orvy62gNLL_4Ro2sU8dft0KRVeLF4qInrcnS2gUEs0Gjv-y3zFZ30XLPIGUxe7W5dkB4v1w7V3Ogvw6X8p8je3oMUyUXFlUr6e-TUf-C8PBFXazHaLDzyRCvqXHfFihaPx0bMGQ4V3H3RvmMG4I6wejoOXXIHzp8gmFLmUqV7j4P6Qop_h96icaT0SBXFLt_BZVcUXbuNrkeOEeDvs2w9DE14vTAja-qMn6VMEpZ1h573xtt6o-BIM9hJIdHxfH6qTFtFPINR5lYBfpek2Ljw6pFO3_yXPz1Vgqv2snX8cZ-SSykeefeoMTuyZVpoSEp0VJm6B89oPG6qOjnh6TSWJGvCAQo6Op-fcKhErMAwZc5DBcm1lwTny8K9HUmShiXYvnPr8FAmPxbYOOiVUJkfAHeyqoQ5F06PHjVqaslyuAS4O-gHuYnlhk1kMe32xZKkuKm5eBdxDSAQzASO9exhNQr-l8g4IXzOALZ_pzRJTOcCPsnhgH7XE-N8tyfKjrFMcHCDQvmpDVMf4UIAJUcULNPhVCx7B3rbhrd0lHnDLetwUDh_ytMq6NazQe8sIWDBJBLjB_IGrtUba532gFzMI3cZ0Vxr_2O_Mm2lWzRW9AmE4m3yP6VSeGBo8jCGlsqacKMM2RMalcbQuU8qD8e2XSJl_D2jigzpSYIQTYm0hOfTA7-g6I6Yfp1hwC5q_gU1RUBgxzA8WMzIEVxFqGWr-0Q8JQ_xiCsHE5KUMwnCWL1m8SSDFayw97bAo8o4vNr_wdUBZE9EQ4NU0FGYKyaSHEnSghNgB-RI7t723V7xaiyg78xwkcX39XQBZlvg7gp68_Cq34lK04gBnboZTREYGgNNak5O0V1D-077JXlaNOKqHcPGLcnAQyk_gweT1_U9oOrcOf1IVRXr6ke4Kzgc-KypowR80Cui5KY4L9rxlTPBTY15KxMaNo_uo0FP46WobeYzALcZuw81duDfXgJbqESyjmwx4R44orAr3xU2vjNBIgvfzWbwOW0_93Kr8AvRyqYrsJZfI9XDTAnmklKW-1QNgYljOiBESLCAY1_ueiCYYgT70jn9ADhmw6IFOrcGAFoKnwtgAhnHuc-_gg0ZpghJwhmPYbs5ZQhFthC6U55JixnkroDOfxTnxoGion4dX7d1svAiX5IfeuvoiXHJxNAysj4GuVgQctLsVwVUuDMvznTKwIFfmUl3AKuq1QPZz2EhWfpab632JoGWiVQvCVBwLlW3rBnN1AvJlMPpOydTie2Q1AC7d69QYEGpZQxWCk9H3TgaXh9Jf5ESGqexQEk8UjLALHDdAwZ8Lo_vnaQzIs0vAVDr6EJkjTQ1K9wVAMV5kEgSomAoIYwd4tw4K-hvRjrV38MEbv92DeRSVKsZIcjmfuxao-ymJJoKwD_cHxeH_REKIPyXTOknveP-d0pimW17mQO_-dYqZnPJZYhlVl61yDPbrmNaVcH4VaaWU8vqtQkydYiBTUd_HoF55xGZtGScc7FMOnbpVrGu7mYN6ShZH7FEZ2BY4pw_Iu2NrHxA99zUtSZTIeUgg4AUscGJH8V9C46jch7G1cGGHJZ0Qf2Dms97F2eOlGzVjMo1BuO15KL9gFXesA6_Ezk0KhLQP-UQZRvfwrmuxS0D0HhxHzJx4NunuZXuKTTM3OFHNxp0NoMoHw8dMXvfUWu2WcSs5ibz7KC_ioY9tew8-IyBn2ADrAlr5Eyb5StjD-IZn_xyVCP0X5rtxRpzGXStwWoOsi7D6k4HaqJFMlS5hv6T22OeuXFo09v5JC27225XpykBWdx8ondYIve72eCL6xlq9XguHUOPDNs9PD9zvFbDzH7YDpRUXOYwGNS3fP2zOEMDNtFL1nzv5wzUaWkyNt1q1ocBbYIq_kK-3iCpoYSh3ecQJdCKI6OTBtt5Ec8TaIguI2Ir7RY6L5JZXbhZBzZ6Uc430Bwba4I_c_DhewJzyj_GeNKjNNy7R5kCCWjyZ30l6rdshXl1kthne7R1TUXA9TKwKJhTiilSWMt0k31iCaZuZDVnjDHE6JN-yCIzI17qB5MjgrzAOwATGRbkvTtkF_X2lNjF1tPs9lSH0AVOP8UW50gL-0MNGimnK-qH6Lj4dD7Mxk4V-PeOyDuNxesKnRXohgaJcq4Hj5BtcIEw_0mQ5yAHBgWkPgEusAPh6c66F90ugBTrYdSbgbKAoQI1irZ4Fj8sAgbqkzRKNP-ZE1ChlfmePCPMohANWWT6z3L8jdLuULRTpwNtHOZ3H0O2qKXJZiu2_QdBnrl9P25FMHuZ3V7ZntFMod32gtIeXn-VpA9v-A2feZv6KXlsc7GakEroWpnTpR_2kLlIXfMVdSYEbzsyeXclQuzAOJqbX-tCwM_Y4_t_J30yh3pgyRRQR0St-IG54bA663x01BDg3XWaBF7VTcj3ztQOFUKeVKRy5ta3qmpREcVYo2jzIqbwPx0InvGzJT2SMgk2L4mMgMiFZIViMzOc7ywjTCBNruDjlMVho7jZgMt_lfFSblSPdr0Z2Vt-nhTNA_QUikWoJxFfAmZ2w.fsvwXsfwYGiuNLsoMTB-Vw

The private key is the following: 

q2nj7sc1m30as24m4nf1mnv[]q981.,a 

A corresponding code example would be something like:

privateKey = "q2nj7sc1m30as24m4nf1mnv[]q981.,a"

jweKey = "j1RBCCV2E6YRUD_RI1oPtQLNDjg2tWELjNit29PzUrSvwx060YcKeg"
jweIv = "OJecPTIfpng9qZCIJh2-3g"

What is really bugging me is that everything has different bytes. The private key has 32, while jweKey 40 and jweIv 22. From what you're saying they should be the same. Should i complete the iv with empty zeros until it reach 32 or something like that?

0 Kudos
renojim
Community Streaming Expert

Re: How to handle encrypted content

Jump to solution

I'm not saying that they should all be the same.  I'd have to look up what the key and IV lengths should be, but they can certainly be different and depend on what cipher you're using.

Roku Community Streaming Expert

Help others find this answer and click "Accept as Solution."
If you appreciate my answer, maybe give me a Kudo.

I am not a Roku employee.
0 Kudos
danielFav
Channel Surfer

Re: How to handle encrypted content

Jump to solution

I've tried to brute force every possible evp in the roEvpCipher object, but none was able to decrypt the payload. First, i try to decrypt the simmetric key passing the private key and initialization vector, forming a list of every decrypted simmetric key. And then, with every assembled key i try to decrypt the payload. Here is the code: 

 

evpList =  ["bf-cbc","bf","bf-cfb","bf-ecb","bf-ofb","des-cbc","des","des-cfb","des-ecb","des-ofb","des-ede-cbc",
"des-ede","des-ede-cfb","des-ede-ofb","des-ede3-cbc","des-ede3","des3","des-ede3-cfb","des-ede3-ofb","desx","desx-cbc", "aes-256-cbc", "aes-256", "aes-256-cfb", "aes-256-cfb1", "aes-256-cfb8", "aes-256-ecb", "aes-256-ofb","aes-192-cbc", "aes-192", "aes-192-cfb", "aes-192-cfb1", "aes-192-cfb8", "aes-192-ecb", "aes-192-ofb","aes-128-cbc", "aes-128", "aes-128-cfb", "aes-128-cfb1", "aes-128-cfb8", "aes-128-ecb", "aes-128-ofb"]

jwe = "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.j1RBCCV2E6YRUD_RI1oPtQLNDjg2tWELjNit29PzUrSvwx060YcKeg.OJecPTIfpng9qZCIJh2-3g.AOYr-AJ_AqDgx7HjzNFPaiDJjhmQCMkwDX31bQO1Rh_Ui2MI3PECVow28Ym2RrUG6jxrUeSGA1Q6G_RdVk1P9-Op4NIFm26XgYx5dq57LXY9-9Be0WaRGq2HvXNFeb-LpmC1UjQ2BDReTwHynOrg98ETjz8j1XQunHSlmdhT1A42_PRsUTo1yyi7kRtIshPSXUlfxZA0hFubnJf2KQWy_hU42b01UUcBnuPEza6Ne_Y2Et-_qmGNRQDDj34R0afXRiByWJJ8YoUNAZrmVydvFEASYMIvB7HQE1dB8QfFhabfipA875bDi98ix5jdPprELykBnwG9KpOZS1RS1hfXBjKuZDuu7PKQdDbDmxe0CtwdA6mWkohDpqX1UeRdVqmcQZs906z0bQ3CvaOdHCx4dSMDRJIflIwsfw3PkzENSEpHLfOJVdi-CabE0iBeGjgcuffkEDqEf2llSj-wqiIfzwn9kyWH_8_rVWwWX6TTWSbaGIMWcxGgRogVKiusYCqqYFtQmtQc6SwkJOARkHQkscZGV9YE1wCQNl_ZtgD6QfRJRHYRU_LOMCb3HKrFyeyIlSfjFMDS5vY1nP5xWOZLqU_ELLMOOEeyBCWzJMwH-t-NMpg8NlUnK5mrcGlIVGmlSdQd1rh1GyfZGQHW6AmQvNSDOMydX-AM_r1ZkuLt3q5yvu6iwKphbZwFz4b9-CgxOGAKH2MI7pAeJHXbaklDisSNZyMJ1MIkyl8sxBQpe9DZRG1WXN1ySYJ4obOaqNahX9tz8-5a3uWscinI016gOQK3zG1dUx43ta3-Q6E7QOlV1SWYSAmrR-bTLT4q2BafWYuhNsR-9_9SYkAHUEFO6hu9yXqvkpbLV7HBz4m-nMQ-iBmrnhSdm1UZRxZQjJOpcaLEEscfD5h6FuliPCE0fdxMge6QM9emsGczonQ2CD2zlgMSHg_PX1VQ98wQy0H6tZBEHiReNTU7_ezUg_YR-ocfgvcQj2h0orvy62gNLL_4Ro2sU8dft0KRVeLF4qInrcnS2gUEs0Gjv-y3zFZ30XLPIGUxe7W5dkB4v1w7V3Ogvw6X8p8je3oMUyUXFlUr6e-TUf-C8PBFXazHaLDzyRCvqXHfFihaPx0bMGQ4V3H3RvmMG4I6wejoOXXIHzp8gmFLmUqV7j4P6Qop_h96icaT0SBXFLt_BZVcUXbuNrkeOEeDvs2w9DE14vTAja-qMn6VMEpZ1h573xtt6o-BIM9hJIdHxfH6qTFtFPINR5lYBfpek2Ljw6pFO3_yXPz1Vgqv2snX8cZ-SSykeefeoMTuyZVpoSEp0VJm6B89oPG6qOjnh6TSWJGvCAQo6Op-fcKhErMAwZc5DBcm1lwTny8K9HUmShiXYvnPr8FAmPxbYOOiVUJkfAHeyqoQ5F06PHjVqaslyuAS4O-gHuYnlhk1kMe32xZKkuKm5eBdxDSAQzASO9exhNQr-l8g4IXzOALZ_pzRJTOcCPsnhgH7XE-N8tyfKjrFMcHCDQvmpDVMf4UIAJUcULNPhVCx7B3rbhrd0lHnDLetwUDh_ytMq6NazQe8sIWDBJBLjB_IGrtUba532gFzMI3cZ0Vxr_2O_Mm2lWzRW9AmE4m3yP6VSeGBo8jCGlsqacKMM2RMalcbQuU8qD8e2XSJl_D2jigzpSYIQTYm0hOfTA7-g6I6Yfp1hwC5q_gU1RUBgxzA8WMzIEVxFqGWr-0Q8JQ_xiCsHE5KUMwnCWL1m8SSDFayw97bAo8o4vNr_wdUBZE9EQ4NU0FGYKyaSHEnSghNgB-RI7t723V7xaiyg78xwkcX39XQBZlvg7gp68_Cq34lK04gBnboZTREYGgNNak5O0V1D-077JXlaNOKqHcPGLcnAQyk_gweT1_U9oOrcOf1IVRXr6ke4Kzgc-KypowR80Cui5KY4L9rxlTPBTY15KxMaNo_uo0FP46WobeYzALcZuw81duDfXgJbqESyjmwx4R44orAr3xU2vjNBIgvfzWbwOW0_93Kr8AvRyqYrsJZfI9XDTAnmklKW-1QNgYljOiBESLCAY1_ueiCYYgT70jn9ADhmw6IFOrcGAFoKnwtgAhnHuc-_gg0ZpghJwhmPYbs5ZQhFthC6U55JixnkroDOfxTnxoGion4dX7d1svAiX5IfeuvoiXHJxNAysj4GuVgQctLsVwVUuDMvznTKwIFfmUl3AKuq1QPZz2EhWfpab632JoGWiVQvCVBwLlW3rBnN1AvJlMPpOydTie2Q1AC7d69QYEGpZQxWCk9H3TgaXh9Jf5ESGqexQEk8UjLALHDdAwZ8Lo_vnaQzIs0vAVDr6EJkjTQ1K9wVAMV5kEgSomAoIYwd4tw4K-hvRjrV38MEbv92DeRSVKsZIcjmfuxao-ymJJoKwD_cHxeH_REKIPyXTOknveP-d0pimW17mQO_-dYqZnPJZYhlVl61yDPbrmNaVcH4VaaWU8vqtQkydYiBTUd_HoF55xGZtGScc7FMOnbpVrGu7mYN6ShZH7FEZ2BY4pw_Iu2NrHxA99zUtSZTIeUgg4AUscGJH8V9C46jch7G1cGGHJZ0Qf2Dms97F2eOlGzVjMo1BuO15KL9gFXesA6_Ezk0KhLQP-UQZRvfwrmuxS0D0HhxHzJx4NunuZXuKTTM3OFHNxp0NoMoHw8dMXvfUWu2WcSs5ibz7KC_ioY9tew8-IyBn2ADrAlr5Eyb5StjD-IZn_xyVCP0X5rtxRpzGXStwWoOsi7D6k4HaqJFMlS5hv6T22OeuXFo09v5JC27225XpykBWdx8ondYIve72eCL6xlq9XguHUOPDNs9PD9zvFbDzH7YDpRUXOYwGNS3fP2zOEMDNtFL1nzv5wzUaWkyNt1q1ocBbYIq_kK-3iCpoYSh3ecQJdCKI6OTBtt5Ec8TaIguI2Ir7RY6L5JZXbhZBzZ6Uc430Bwba4I_c_DhewJzyj_GeNKjNNy7R5kCCWjyZ30l6rdshXl1kthne7R1TUXA9TKwKJhTiilSWMt0k31iCaZuZDVnjDHE6JN-yCIzI17qB5MjgrzAOwATGRbkvTtkF_X2lNjF1tPs9lSH0AVOP8UW50gL-0MNGimnK-qH6Lj4dD7Mxk4V-PeOyDuNxesKnRXohgaJcq4Hj5BtcIEw_0mQ5yAHBgWkPgEusAPh6c66F90ugBTrYdSbgbKAoQI1irZ4Fj8sAgbqkzRKNP-ZE1ChlfmePCPMohANWWT6z3L8jdLuULRTpwNtHOZ3H0O2qKXJZiu2_QdBnrl9P25FMHuZ3V7ZntFMod32gtIeXn-VpA9v-A2feZv6KXlsc7GakEroWpnTpR_2kLlIXfMVdSYEbzsyeXclQuzAOJqbX-tCwM_Y4_t_J30yh3pgyRRQR0St-IG54bA663x01BDg3XWaBF7VTcj3ztQOFUKeVKRy5ta3qmpREcVYo2jzIqbwPx0InvGzJT2SMgk2L4mMgMiFZIViMzOc7ywjTCBNruDjlMVho7jZgMt_lfFSblSPdr0Z2Vt-nhTNA_QUikWoJxFfAmZ2w.fsvwXsfwYGiuNLsoMTB-Vw"

jweArray = jwe.split(".")

decryptedKeyList = []

for each evp in evpList
    privateKeyBa = CreateObject("roByteArray")
    enkba = CreateObject("roByteArray")
    ivba = CreateObject("roByteArray")

    privateKey = "q2nj7sc1m30as24m4nf1mnv[]q981.,a"
    enk = jweArray[1]
    iv = jweArray[2]

    enk = base64UrlToBase64(enk)
    iv = base64UrlToBase64(iv)

    privateKeyBa.fromAsciiString(privateKey)
    enkba.fromBase64String(enk)
    ivba.fromBase64String(iv)

    pkHex =  uCase(privateKeyBa.toHexString())
    ivHex = uCase(ivba.toHexString())

    cipher = CreateObject("roEvpCipher")
    setupResult = cipher.setup(false, evp,pkHex, ivHex, 0)

    if setupResult = 0
        decryptedKey = cipher.process(enkba)

        if(decryptedKey <> invalid)
            decryptedKeyList.push(decryptedKey)
        end if
    end if
end for

for each key in decryptedKeyList
    for each evp in evpList
        payloadBa = CreateObject("roByteArray")
        newKey = CreateObject("roByteArray")

        newKey.append(key)

        payload = jweArray[3]
        payload = lCase(base64UrlToBase64(payload))

        payloadBa.fromBase64String(payload)

        cipher = CreateObject("roEvpCipher")
        setupResult = cipher.setup(false, evp, uCase(newKey.toHexString()), "", 0)

        if setupResult = 0
            decryptedPayload = cipher.process(payloadBa)
            
            if decryptedPayload <> invalid
                ? decryptedPayload.toAsciiString()
            end if
        end if
    end for    
end for

 

I've tried to decrypt the jwe using a public lib like jose and it was able to perform the job without a sweat. Am i missing something? Has anyone done something similar before? Maybe roku does not have support for this key unwrap algorithm. If that's the case, building one from scratch would be too much troublesome. Maybe the solution would be to request for a new endpoint to the backend team that uses an algorithm roku supports.

0 Kudos
renojim
Community Streaming Expert

Re: How to handle encrypted content

Jump to solution

I don't know anything about JWE, so I don't think I can be much help.  Have you tried the steps you're trying to perform on your Roku device using something like openssl instead of a package that understands JWE?

Roku Community Streaming Expert

Help others find this answer and click "Accept as Solution."
If you appreciate my answer, maybe give me a Kudo.

I am not a Roku employee.
0 Kudos
danielFav
Channel Surfer

Re: How to handle encrypted content

Jump to solution

Yea, i made a very simple code using a lib called cryptography, which is a python wrapper for OpenSSL, pretty much the same as roEVPcipher, and it was able to get results. Here is the code if interested:

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

jwe = "eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.j1RBCCV2E6YRUD_RI1oPtQLNDjg2tWELjNit29PzUrSvwx060YcKeg.OJecPTIfpng9qZCIJh2-3g.AOYr-AJ_AqDgx7HjzNFPaiDJjhmQCMkwDX31bQO1Rh_Ui2MI3PECVow28Ym2RrUG6jxrUeSGA1Q6G_RdVk1P9-Op4NIFm26XgYx5dq57LXY9-9Be0WaRGq2HvXNFeb-LpmC1UjQ2BDReTwHynOrg98ETjz8j1XQunHSlmdhT1A42_PRsUTo1yyi7kRtIshPSXUlfxZA0hFubnJf2KQWy_hU42b01UUcBnuPEza6Ne_Y2Et-_qmGNRQDDj34R0afXRiByWJJ8YoUNAZrmVydvFEASYMIvB7HQE1dB8QfFhabfipA875bDi98ix5jdPprELykBnwG9KpOZS1RS1hfXBjKuZDuu7PKQdDbDmxe0CtwdA6mWkohDpqX1UeRdVqmcQZs906z0bQ3CvaOdHCx4dSMDRJIflIwsfw3PkzENSEpHLfOJVdi-CabE0iBeGjgcuffkEDqEf2llSj-wqiIfzwn9kyWH_8_rVWwWX6TTWSbaGIMWcxGgRogVKiusYCqqYFtQmtQc6SwkJOARkHQkscZGV9YE1wCQNl_ZtgD6QfRJRHYRU_LOMCb3HKrFyeyIlSfjFMDS5vY1nP5xWOZLqU_ELLMOOEeyBCWzJMwH-t-NMpg8NlUnK5mrcGlIVGmlSdQd1rh1GyfZGQHW6AmQvNSDOMydX-AM_r1ZkuLt3q5yvu6iwKphbZwFz4b9-CgxOGAKH2MI7pAeJHXbaklDisSNZyMJ1MIkyl8sxBQpe9DZRG1WXN1ySYJ4obOaqNahX9tz8-5a3uWscinI016gOQK3zG1dUx43ta3-Q6E7QOlV1SWYSAmrR-bTLT4q2BafWYuhNsR-9_9SYkAHUEFO6hu9yXqvkpbLV7HBz4m-nMQ-iBmrnhSdm1UZRxZQjJOpcaLEEscfD5h6FuliPCE0fdxMge6QM9emsGczonQ2CD2zlgMSHg_PX1VQ98wQy0H6tZBEHiReNTU7_ezUg_YR-ocfgvcQj2h0orvy62gNLL_4Ro2sU8dft0KRVeLF4qInrcnS2gUEs0Gjv-y3zFZ30XLPIGUxe7W5dkB4v1w7V3Ogvw6X8p8je3oMUyUXFlUr6e-TUf-C8PBFXazHaLDzyRCvqXHfFihaPx0bMGQ4V3H3RvmMG4I6wejoOXXIHzp8gmFLmUqV7j4P6Qop_h96icaT0SBXFLt_BZVcUXbuNrkeOEeDvs2w9DE14vTAja-qMn6VMEpZ1h573xtt6o-BIM9hJIdHxfH6qTFtFPINR5lYBfpek2Ljw6pFO3_yXPz1Vgqv2snX8cZ-SSykeefeoMTuyZVpoSEp0VJm6B89oPG6qOjnh6TSWJGvCAQo6Op-fcKhErMAwZc5DBcm1lwTny8K9HUmShiXYvnPr8FAmPxbYOOiVUJkfAHeyqoQ5F06PHjVqaslyuAS4O-gHuYnlhk1kMe32xZKkuKm5eBdxDSAQzASO9exhNQr-l8g4IXzOALZ_pzRJTOcCPsnhgH7XE-N8tyfKjrFMcHCDQvmpDVMf4UIAJUcULNPhVCx7B3rbhrd0lHnDLetwUDh_ytMq6NazQe8sIWDBJBLjB_IGrtUba532gFzMI3cZ0Vxr_2O_Mm2lWzRW9AmE4m3yP6VSeGBo8jCGlsqacKMM2RMalcbQuU8qD8e2XSJl_D2jigzpSYIQTYm0hOfTA7-g6I6Yfp1hwC5q_gU1RUBgxzA8WMzIEVxFqGWr-0Q8JQ_xiCsHE5KUMwnCWL1m8SSDFayw97bAo8o4vNr_wdUBZE9EQ4NU0FGYKyaSHEnSghNgB-RI7t723V7xaiyg78xwkcX39XQBZlvg7gp68_Cq34lK04gBnboZTREYGgNNak5O0V1D-077JXlaNOKqHcPGLcnAQyk_gweT1_U9oOrcOf1IVRXr6ke4Kzgc-KypowR80Cui5KY4L9rxlTPBTY15KxMaNo_uo0FP46WobeYzALcZuw81duDfXgJbqESyjmwx4R44orAr3xU2vjNBIgvfzWbwOW0_93Kr8AvRyqYrsJZfI9XDTAnmklKW-1QNgYljOiBESLCAY1_ueiCYYgT70jn9ADhmw6IFOrcGAFoKnwtgAhnHuc-_gg0ZpghJwhmPYbs5ZQhFthC6U55JixnkroDOfxTnxoGion4dX7d1svAiX5IfeuvoiXHJxNAysj4GuVgQctLsVwVUuDMvznTKwIFfmUl3AKuq1QPZz2EhWfpab632JoGWiVQvCVBwLlW3rBnN1AvJlMPpOydTie2Q1AC7d69QYEGpZQxWCk9H3TgaXh9Jf5ESGqexQEk8UjLALHDdAwZ8Lo_vnaQzIs0vAVDr6EJkjTQ1K9wVAMV5kEgSomAoIYwd4tw4K-hvRjrV38MEbv92DeRSVKsZIcjmfuxao-ymJJoKwD_cHxeH_REKIPyXTOknveP-d0pimW17mQO_-dYqZnPJZYhlVl61yDPbrmNaVcH4VaaWU8vqtQkydYiBTUd_HoF55xGZtGScc7FMOnbpVrGu7mYN6ShZH7FEZ2BY4pw_Iu2NrHxA99zUtSZTIeUgg4AUscGJH8V9C46jch7G1cGGHJZ0Qf2Dms97F2eOlGzVjMo1BuO15KL9gFXesA6_Ezk0KhLQP-UQZRvfwrmuxS0D0HhxHzJx4NunuZXuKTTM3OFHNxp0NoMoHw8dMXvfUWu2WcSs5ibz7KC_ioY9tew8-IyBn2ADrAlr5Eyb5StjD-IZn_xyVCP0X5rtxRpzGXStwWoOsi7D6k4HaqJFMlS5hv6T22OeuXFo09v5JC27225XpykBWdx8ondYIve72eCL6xlq9XguHUOPDNs9PD9zvFbDzH7YDpRUXOYwGNS3fP2zOEMDNtFL1nzv5wzUaWkyNt1q1ocBbYIq_kK-3iCpoYSh3ecQJdCKI6OTBtt5Ec8TaIguI2Ir7RY6L5JZXbhZBzZ6Uc430Bwba4I_c_DhewJzyj_GeNKjNNy7R5kCCWjyZ30l6rdshXl1kthne7R1TUXA9TKwKJhTiilSWMt0k31iCaZuZDVnjDHE6JN-yCIzI17qB5MjgrzAOwATGRbkvTtkF_X2lNjF1tPs9lSH0AVOP8UW50gL-0MNGimnK-qH6Lj4dD7Mxk4V-PeOyDuNxesKnRXohgaJcq4Hj5BtcIEw_0mQ5yAHBgWkPgEusAPh6c66F90ugBTrYdSbgbKAoQI1irZ4Fj8sAgbqkzRKNP-ZE1ChlfmePCPMohANWWT6z3L8jdLuULRTpwNtHOZ3H0O2qKXJZiu2_QdBnrl9P25FMHuZ3V7ZntFMod32gtIeXn-VpA9v-A2feZv6KXlsc7GakEroWpnTpR_2kLlIXfMVdSYEbzsyeXclQuzAOJqbX-tCwM_Y4_t_J30yh3pgyRRQR0St-IG54bA663x01BDg3XWaBF7VTcj3ztQOFUKeVKRy5ta3qmpREcVYo2jzIqbwPx0InvGzJT2SMgk2L4mMgMiFZIViMzOc7ywjTCBNruDjlMVho7jZgMt_lfFSblSPdr0Z2Vt-nhTNA_QUikWoJxFfAmZ2w.fsvwXsfwYGiuNLsoMTB-Vw"

key = "q2nj7sc1m30as24m4nf1mnv[]q981.,a"

array = jwe.split('.')

#converts key from base64url to base64, then converts to byte array
simmetricKey = base64.urlsafe_b64decode(array[1] + '=' * (4 - len(array[1]) % 4))

#unwraps the simmetric key
result = aes_key_unwrap(str.encode(key), simmetricKey)

#last 16 bits of key
newKey = result[-16:]

#converts iv from base64url to base64, then converts to byte array
iv = modes.CBC(base64.urlsafe_b64decode(array[2] + '=' * (4 - len(array[2]) % 4)))

#creates a Cipher instance, equivalent of roEVPcipher
cipher = Cipher(algorithms.AES128(newKey), iv)
decryptor = cipher.decryptor()

#converts payload from base64url to base64, then converts to byte array
payload = base64.urlsafe_b64decode(array[3] + '=' * (4 - len(array[3]) % 4))

#equivalent of roEvp .process method
data = decryptor.update(payload)

result = data.decode('utf-8')

print(result)

What is seems to me is that roku is missing a key unwrap algorithm, or i can't find it in the docs. Does anyone know how to solve this ? I think it would be too much to write this algorithm with native brightscript if there is already a wrapper for OpenSSL.

danielFav
Channel Surfer

Re: How to handle encrypted content

Jump to solution

In the end, i decided to implement the key unwrap algorithm manually, and it was able to correctly decrypt the jwe. Here is the full solution: 

jwe =
"eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.EoOZqNOs89BXG4KUq8VY4MuMw3iRjPvO2OfgTCXaWDV7CiOrT-RDrA.EEblXQ90Va5dRM2GagfX1A.2Cr-DulZOR68eZsewEfSAczf82EbPHVwQDhuSUA6KAihDZ1JZoHSExrhnMl1PqoEHyHe5c8oxty7MWACp4PC0Dlt7KqIyERfB0LURk7H_Btm25V0NqlghiTVd0YPmkiCuiEUduPMWGeUyJWJ3GcvYUxDNJKvc6Vm0cZbpiRnn-Se-vEDZQP9WNiHLSvrvjPlbk_m0rq5-_4rHlNwN4ubDnqJQv6PWvuE2gzxs6G2E5KYULClhcHyZOjyIBKO9sNZI0RTff9MfRQ2O6SLn9hBtLOFbf43P3693wBM2CzS8cVOJokH_jn22TD-og4nDPaDfzTcmR4379TW__0S-ZE1vVLMM5acZ2l-Its_St5Mu5-krKThJjW2tN8PS5h7pFiRWVQmSAeTTcsZidwh-ffIXikDkSa61ChADfaUx86yee438idA2RIR50ONSuoiBoD8Vpom4cHhvqiVY5CEqoVsDnMsN1lPXEBWthX9Iz0KJTg2SlTMQDy8vGZEu9HQTZZHiuLCNLcQN4OFKot0jvHLaFs61lOW7_nWX_BKI626WecF3SLsqOEPSaHXWKHU6oxbwG7VF3Kjhk9bGB61bkitRb6ijJopD1R3tUpFk4t3pv8P6DXlC1b0t_MO1F1ubiiHSL60eCZbXcaSM9SJ92YzXy0xZOp_mLfTL7yunzanYcB0mhwQeisXLwYlu_xLm8_OS1DZ3RZFlFNiv0ibf9vuc-Br6RhFbG3ICbCqwqWy7uMhe4_5Eg4FcS0gxpqaMuW8QoyNylP6HEDOvXedGhlCnACaGAHxhToygREI-jQuDza3vf5bBXI5E8kle5j2N5wmM5OXxGruHaOiKuuZYZFsFLhPROQU6xnS8TxaLUMcay4xAygiAfIch5VFm7jmVRXiasj-ZhBmGb4dg8PHuAxBxCjWy7H6BRFaFCz6-qoTCviKsujFPlZL50vW5qfBWymkFaOd1p9R3qB7Tmd5YfqdlK6hBogfwIFxm6ZUfP1mFVYDumXBgmc3ZLWAxfRx3kVbS3_vdZkpBhDqznQR5EO1gwmCNeWQa1s9Lm2w6oYBXA7X9WFOW2ubWTqUG-vpqcYWBaLgWwlWu_wUphdzBtduoNq-vRe1ms0OGzXzn8P78eGsKZ-C5KOFgpc8APhbm9SJPaXyv3ZsLImYELRPSLJob5wCLnAgbz6zvLfODCH3LasulgqriEw3i1nnmEgeyNSbrw-Z3KAAd4uPyObQrTeOcnKGQ2NUGudST9P7kjglpTLiTGsIs_QED2kaTyFVgLoXk0LLHKjANO0EtS9Ncb_5zJQdbu_IaG4b6PHdnMkzYHRCA4qHk66dcUlfzCcbHRn8Wrft8aN99Xv7n6zg0mbSqT-c2rgInslxCmkoDr3ZzpohADi9M_-6tmzLPw1FUl1aWBG6nflYq42cjAAz4GdvVaNh-Z8WHfsPZCeziEhZeBeiI-oj2RJBQcAQ-KvgxxJjQ_vBxclMCh4M9EZWzZ0d7t2WoqGHag24_pl9o30XppUhzRtl0z9evzg1d8-VsQT-KKgQK75E4Vn0KsLUBpU2z8_QyB-QNSzzSd4P3JRVOpxiPMRMC_vHRgvy1Td2k6RO9rK2sQ5Y7FhtYk1IiFPkMVZxpCxqnffB80vncWkxOhGmbXtP1_1vitLEXHX7RKIKefee3RVUo2jdDBrC2Ksfrd6fwiFKUQInuC_5PkOvz610PwwauAFGAOq5nNtS1hPh-LvdFVnf-8zB0LgSSm6tOVIbMsLyidF3D7gUiddv7sX0J07WIkZI8H_tQJAHo-LNHA3JPmGT8w7cI-XWKo-q7hMSO5Kt8GLJ4G_b7yYDcS3chnEDWpAMaN3mas6qq4JoymvHjYtCE5tuox8gBzgzlNhmXYzbyBiD4H5MlvkHya3I_TeVinl09RaNXi-sYoNpsisDobDn1QQBI2vjMull1lTwIJJlfekeKFhWzZl1NdacJwMapvopewpLs3VQmpAaRVu35Q4zpJXG3K57bVYKO8DIME-KyD1WvgS0DlsQVIomCKFUlWgOd68ITMOxe3jEmk_0ndDfVObhtFj-bVby2NbvGw2004o4nEF8-pwT0jCmcfkz8uytsAOkqfXIhzwxI8A-gK4oV4CpEbBpiEqa8K8hT9EnDy1xnMZssy-CiQbL9CP9iJIoidhq5y8yKcW0FTfZauOooagI6mRvx03TVJvjX-umAD0JQSEFE0hWSD5ELhFNF2M-GQ9gSQsBKhzwdwWS4_h_fc-JIXMiZOd01Si2TI1rfqjtENt8w3TEg4ATqsRsm1qUiA9rzTxleP9OqwHwEDqpOwsIkWd8xX0eyoeQBEMJS02UHNNLdm87IoW4ZJxkiGkde6wPj0n-K8gxL3_bl_dygptuVJ5J4Qx5JXjOHEhA451QtLKz0SXTR0wvjp6zKFYsObEV-bN1Q5gOJptzUMh98huNhRCANbx6rMugAArKTRDDygGqIbnMEeT84ap3dhSw0h-RV_QSpHvYxg9PFSVsl1X1F5ZcL9bkssR31qTAdXiVJONg_gtmRoZFDjAfOjsiJChT8kbGhNP-JqFAiiYH4ilwT-zT9oEPkTwHveV638ote3_NhvOPKsVxiY84jV0AYtCUF82eYTjrqe1knSt4k3xZ_chR34g5oRVzERdhA_wfibk0tpc6rhKQEXXlKXl1-Boh_Hw5KLbWGMEcIXW6ms4XAqMHjhsIeX4livD8l2C3bLmGzLwwa1h19g2ZUJr9HY3T0WrG23alMkH20DtFn5WGgZmuXyJSzgVsiUajw0AZKK9MREIX90JE_UcSnk9uXogmeE88P65H05Nw6AAezc8b-_ym8X9-0xp7W8hLZPIQ1S2-cZRIvJMBvVVoYLD7VGBqjkugc58SnoETXX4QNAooH8uBIiVC3vDM0zo2OzBIRVUSG0SCvKMG6Q-w3fnSWcnHGVfHuURJV2vFJypwjkF0-MzH9VD2ydbcOcD49KkSK6ILdtMMeQ4YmWi5jTpoJyDzo5u8FURQ0vAK3X0lG8kpqI-ib8GhgL89Or64XktayAcD-pNDWXQ87siPeqYFxQOm_WvWDOLo4RTk9aPsXdLj-4728J1sJHSKmN3PEFINMO2F3ldhUlazfhFv6Z9-GfFLX4fWSLGeH1wCqYOOdaiJWulIQCsxzP3QYyM5LdBynC2N4qSsIGndpiO0trC1BWrRawmefh3JbvzEU5poLyYYbImKU6fjshet4AwEyYbl4NFISD3OXfYdnIvRIpxosXJhWnQjgBRHlRMa7RcVD_S65eop49giaGxGpy7_-LkCw2rQilwPy7y9sZBqBJw6okZYvPfodxu5mPjqnghJx9t3pbi8eWMf1JAttfuUZd1anqHZFg3jRmwR2qX8gmmIk_6lLzwLNkVAsAnTaUlPnQzdLQBPsUpp3WJv0v-VpJE0OpoEkzuhHPXzIkEioxLhXJsjOXz1Vc5wohP-uWsT9atjNhe8lT0j_YV5O70h_qVjG39uZjNOJvXlH603tU1O-vo6PJVe3nYnxOw5GfxQ1bUCo2G0DxrjlQ.ubwItxMBO1_Qsnf6s5VRJQ"

key = "q2nj7sc1m30as24m4nf1mnv[]q981.,a"

result = jwe.split(".")

resultingKey = aesKeyUnwrap(key, result[1])

encodedIvBa = CreateObject("roByteArray")
encodedIv = base64UrlToBase64(result[2])
encodedIvBa.fromBase64String(encodedIv)

payloadBa = CreateObject("roByteArray")
payload = base64UrlToBase64(result[3])
payloadBa.fromBase64String(payload)

cipher = CreateObject("roEvpCipher")
cipher.setup(false, "aes-128-cbc", resultingKey, encodedIvBa.toHexString(), 0)

text = cipher.process(payloadBa)

? text.toAsciiString()

Im not going to explain how the algorithm works. You can read about it in the RFC 3394. Below is the implementation for aes key unwrap

function xor(p as object,q as object) as object
    pSize = p.count()
    qSize = q.count()

    counter = 0
    for i = pSize - 1 to pSize - qSize step -1
        p[i] = intxor(p[i], q[counter])
        counter ++
    end for
    return p
end function

function intxor(p as integer, q as integer) as integer
    bitwiseAnd = p and q
    bitwiseOr = p or q
    return bitwiseOr and not bitwiseAnd
end function

function unWrapCore(privateKey as object, a as object, r as object)

    cipher = CreateObject("roEvpCipher")
    dd = cipher.setup(false, "aes-256-ecb", privateKey.toHexString(), "", 0)

    if dd = 0
        n = r.count()
        for j = 5 to 0 step -1:
            for i = n - 1 to 0 step -1:
                x = ((n * j) + i + 1)
                ba = CreateObject("roByteArray")
                ba.push(x)
                y = xor(a, ba)
    
                y.append(r[i])
    
                b = cipher.update(y)
    
                if b<> invalid
                    a = CreateObject("roByteArray")
                
                    for l = 0 to 7 step 1:
                        a.push(b[l])
                    end for
        
                    r[i] = CreateObject("roByteArray")
                    for o = 8 to b.count() - 1 step 1:
                        r[i].push(b[o])
                    end for
        
                end if
               
            end for
        end for
    
        newArray = CreateObject("roByteArray")

        for each item in r
            newArray.append(item)
        end for

        result = CreateObject("roByteArray")

        for t = 16 to newArray.count() -1 step 1:
            result.push(newArray[t])
            
        end for

        return result.toHexString()
    end if

    return -1
    

end function

function aesKeyUnwrap(privateKey as string, encodedKey as string)

    privateKeyBa = CreateObject("roByteArray")
    privateKeyBa.fromAsciiString(privateKey)

    encodedKeyBa = CreateObject("roByteArray")
    encodedKey = base64UrlToBase64(encodedKey)
    encodedKeyBa.fromBase64String(encodedKey)

    array = []

    for i = 0 to encodedKeyBa.count() step 8
        ba = CreateObject("roByteArray")
        for j = i to i + 7 step 1
            ba.push(encodedKeyBa[j])
        end for
        array.push(ba)
    end for

    array.pop()
    a = array.shift()
    ba = CreateObject("roByteArray")
    ba.push(24)

    return unWrapCore(privateKeyBa, a, array)

end function
0 Kudos
renojim
Community Streaming Expert

Re: How to handle encrypted content

Jump to solution

Nice!  I figured you'd have to go that route.  Thanks for sharing!

Roku Community Streaming Expert

Help others find this answer and click "Accept as Solution."
If you appreciate my answer, maybe give me a Kudo.

I am not a Roku employee.
0 Kudos
Need Assistance?
Welcome to the Roku Community! Feel free to search our Community for answers or post your question to get help.

Become a Roku Streaming Expert!

Share your expertise, help fellow streamers, and unlock exclusive rewards as part of the Roku Community. Learn more.