Search code examples
javascriptcryptographyrsa

How do I sign using RSA private key and crypto.subtle?


I have Python code that generates an RSA signature, summarized here:

import rsa
private_key = rsa.PrivateKey(key[0], key[1], key[2], key[3], key[4])
signature = rsa.sign(data.encode(), private_key, 'SHA-256')

The key is provided as an array of 5 items in the key array, and everything works fine.

I need to do the same in JavaScript, preferably using crypto.subtle, and I can't figure out how to load the private key, starting with those 5 items in the key array.

Starting with this answer, I tried code below. It creates a private key, converts to jwk, and displays the results. I can see slots for all the components here:

decrypted = This text will be encoded UTF8 and may contain special characters like § and €.
jwk = {
    "alg": "RSA-OAEP-256",
    "d": "SlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT_1c2v79cq2Dum5y_-UBl8x8TUKPKSLpCLs-GXkiVKgHXrFlqoN-OYQArG2EUWzuODwczdYPhhupBXwR3oX4g41k_BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfyqmamvmW8bsvc8coiGlZ28UC85_Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX_k1VBXmNOHa-HzGOgO_W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyFEM3gGvC2-cDBI2SL_amhiTUa_VDlTVw_IKbSuar9uQ",
    "dp": "iE6VAxJknM4oeakBiL6JTdXEReY-RMu7e4F2518_lJmoe5CaTCL3cnzFTgFyQAYIvD0MIgSzNMkl6Ni6QEY1y1fIpTVIIAZLWAzZLXPA6yTIJbWsmo9xzXdiIJQ-a433NnClkYDne_xpSnB2kxJ263mIX0drFq1i8STsqDH7lVs",
    "dq": "VqUJsxXqpTQt8Sjxo-UE3y21UM9U2me0_iHQ2DE9eA8rw-D6ADVRZLLgyi4aD-HOR0dqP2J_IuUJfn3xrkmhPhLTH9l5Ud38s0jya2NxHMPpwx17uB0Vuktvk1KMgDKuwgBfiHG-meqI5hF4-RUjPSIsbOKJoxt8zCWSvG-b8tE",
    "e": "AQAB",
    "ext": true,
    "key_ops": [
        "decrypt"
    ],
    "kty": "RSA",
    "n": "unF5aDa6HCfLMMI_MZLT5hDk304CU-ypFMFiBjowQdUMQKYHZ-fklB7GpLxCatxYJ_hZ7rjfHH3Klq20_Y1EbYDRopyTSfkrTzPzwsX4Ur_l25CtdQldhHCTMgwf_Ev_buBNobfzdZE-Dhdv5lQwKtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1xH9FLojQfyia89_EykiOO7_3UWwd-MATZ9HLjSx2_Lf3g2jr81eifEmYDlri_OZp4OhZu-0Bo1LXloCTe-vmIQ2YCX7EatUOuyQMt2Vwx4uV-d_A3DP6PtMGBKpF8St4iGw",
    "p": "3e-jND6OS6ofGYUN6G4RapHzuRAV8ux1C9eXMOdZFbcBehn_ydhzR48LIPTW9HiRE00um27lXfW5_POCaEUvfOp1UxTWeHZ4xICo40PBo383ZKW1MbES1oiMbjkEqSFGRnTItnLU07bKbzLA7I0UWHWCEAnv0g7HRxk973FAsm8",
    "q": "1w8-olZ2POBYeYgw1a0DkeJWKMQi_4pAgyYwustZo0dHlRXQT0OI9XQ0j1PZWoQS28tFcmoEAg6f5MUDpdM9swS0SOCPI1Lc_f_Slus3u1O3UCezk37pneSPezskDhvV2cClJEYH8m_zwDAUlEi4KLIt_H_jgtyDd6pbxxc78RU",
    "qi": "s9Fu1JsTak-C84codMY-vuApuaxZVs5xADysbzTVPfxb9Q97Ve3KcwSPPNDb05pV5DC9Q334PEVcnpi_CPqKHhZ2rXT2Ls6jV8OcxzM5A30MpyHZ40Aes1I4zIsMIGb77BvIcCxLZPRU7z6DMsAG-JmbkAUJBZ-R7gtmjmY5LXQ"
}

and I was hoping that adjusting the jwk would let me then insert my values (see Python code above), import the key, and use the sign feature. But any change I make to the jwk results in a key I can't import. I can't switch from decrypt to sign, for example.

// See https://stackoverflow.com/a/62967202/24558

// PEM encoded PKCS#8 key
const privateKey = 
`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8sw
wj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqW
rbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4O
F2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtL
zd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOW
uL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEq
kXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1
c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODw
czdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfy
qmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBX
mNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyF
EM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3o
bhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884Jo
RS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDs
jRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCD
JjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9
/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3
qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpM
IvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltaya
j3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFal
CbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdH
aj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIA
X4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTG
Pr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6
ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLA
BviZm5AFCQWfke4LZo5mOS10
-----END PRIVATE KEY-----`;

importPrivateKeyAndDecrypt();
    
async function importPrivateKeyAndDecrypt() {

    // A ciphertext produced with the first code
    const ciphertextB64 = "q/g0YQ+CbFwCb9QxAeKk/X8vjUUKpBGCVe6OvFoBlTfRF24BQlWpLFhxVQv+Gn29CzAXfSJjU+C8taYXQ4wofyOaRx0etkATDbmIV1gVdxNnqVKTx2RSj1L3uACZ3aWYIGRjtaBMBNAW81mPEjxEWCvRW3uI/rOn3LAc4N05CkofOnsIpaafgcEjhZoTxp1Dpkm328bwRJ3g1Dn+vQk6JBiAXSiF7GHvMvnD6q+CQiO1dcv0lrrXlibE8/P2LHWpqQ9g5xWWUHl70q2WB+IxLgX9OkqX8XQ1GHjP5EaQFfo1HerBpa+Uf5DaienI/XT4n64DWM1S7t0dbhFDskc9HQ==";

    try {
        const priv = await importPrivateKey(privateKey);
        const decrypted = await decryptRSA(priv, str2ab(atob(ciphertextB64)));
        say `decrypted = ${decrypted}`
        
        const jwk = await crypto.subtle.exportKey('jwk', priv)
        say `jwk = ${JSON.stringify(jwk, null, 4)}`



        // jwk.ext = false
        // jwk.alg = 'RSASSA-PKCS1-v1_5'
        // const priv2 = await crypto.subtle.importKey('jwk', jwk, {
        //     name: "RSASSA-PKCS1-v1_5",
        //     hash: "SHA-256",     // SHA-1, SHA-256, SHA-384, or SHA-512
        //     publicExponent: new Uint8Array([1, 0, 1]), // 0x03 or 0x010001
        //     modulusLength: 2048, // 1024, 2048, or 4096
        // }, true, ['decrypt'])
        // say `after`
        // const jwk2 = await crypto.subtle.exportKey('jwk', priv2)
        // say `jwk2 = ${JSON.stringify(jwk, null, 4)}`
        // const decrypted2 = await decryptRSA(priv2, str2ab(atob(ciphertextB64)));
        // say `decrypted = ${decrypted2}`

    } catch (e) {
        say `${e}`
    }
}

async function importPrivateKey(pkcs8Pem) {     
    return await crypto.subtle.importKey(
        "pkcs8",
        getPkcs8Der(pkcs8Pem),
        {
            name: 'RSA-OAEP',
            hash: 'SHA-256',
        },
        true,
        ['decrypt']
    );
}

async function decryptRSA(key, ciphertext) {
    let decrypted = await crypto.subtle.decrypt(
        {
            name: "RSA-OAEP"
        },
        key,
        ciphertext
    );
    return new TextDecoder().decode(decrypted);
}

function getPkcs8Der(pkcs8Pem){
    const pemHeader = "-----BEGIN PRIVATE KEY-----";
    const pemFooter = "-----END PRIVATE KEY-----";
    var pemContents = pkcs8Pem.substring(pemHeader.length, pkcs8Pem.length - pemFooter.length);
    var binaryDerString = window.atob(pemContents);
    return str2ab(binaryDerString); 
}

//
// Helper
//
    
// https://stackoverflow.com/a/11058858
function str2ab(str) {
    const buf = new ArrayBuffer(str.length);
    const bufView = new Uint8Array(buf);
    for (let i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}
    
function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

Solution

  • Using the documentation of rsa.PrivateKey(), the five arguments key[0] through key[4] can be identified: They are, in this order, modulus (n), public exponent (e), private exponent (d), and the two primes p and q with n = p * q.

    WebCrypto does not support importing the private key in this way. But, as you already realized, the private key can be imported as JWK, which is quite close to the import in the Python code.
    A JWK contains the parameters n, e, d, p and q in Base64url encoded form. In addition, the parameters dp = d mod p-1, dq = d mod q-1 and q_inv = q^-1 mod p are required.

    Note that WebCrypto does not provide support for Base64url nor for modular arithmetic (such as the modular inverse), so this must be implemented yourself or additional libraries must be used.
    In the following implementation, BigInt is applied for large integers as required for RSA. For the determination of the modular inverse, functionalities of bigint-mod-arith are used, for the conversion from BigInt to Uint8Array and further for Base64url encoding, functionalities from s1r-J/browser-base64url-arraybuffer.js and from this post are applied.

    With these implementations, a JWK can be created, imported, and used for signing as follows:

    (async () => {
    
    // Sample data for n, e, d, p, q imported from decimal integers
    const n = BigInt("23536280960117078781071277796506388364632844470760889287043499897812352121486284917799618205473774110082986255111078948929716171007803508886970676224127450295642559080964190798895956419173396140605893662673557325803620649459356106588185328074303624698999369184931437392043701807154016276826947703489139066577610914955532141229640472629981914163258711276860837496743796618919012922407451955239609188448498501143346583070612890214114610395155257827751505466411422773826133218559917162211680737700993678145296589807650002517691621420203575296591550000452984882555949046114026781590107745032317312675291585975313969848859")
    const e = BigInt("65537")
    const d = BigInt("9382262539985942035117370835310273525276302879268630432030935728372487284645760310626287831576092110196042173349053779403830431169856213584267038716378986511034405846929055108124522963378015078098310449629166503450258471811735024255250342492655785209291827821785156504984080896469149873080916257284492090183134232671709250785165983314728570454111274522098371781165874751498087056242271462925111175964110913088917016069904769646771252836449225104991887781851938529428988142021234387304783760289871945413583159049359570528147608301067614991660860541652310473617313526348376112227247717281579081381208446321849438764473")
    const p = BigInt("155848818230016115749752423910909717296251325660942142725149379121248574362967471982268814952228835353898234610957136620749647839486274159495856726744103519159395949128597219453194423432726936416070232282723469181220002341912380459799343589855993917183257748160693240120150007893839052471718988588887870321263")
    const q = BigInt("151019951433831574850603517176599781805882023744929175175855523475079892352628220454545688743736195507788846749671107149711466014334137134060305215869022779903575705272635233063037001421670083870087620888835502764099951892566093068046644025208762390847987888334442622157531568813775934596672771679438667247893")
    
    // Calculate the missing dp, dq and q_inv
    const dp = d % (p-1n)
    const dq = d % (q-1n)
    const qi = modInv(q, p) //  q^-1 mod p
    
    // Create the JWK: convert BigInt to Uint8Array and Base64url encode    
    const jwk = {
        "kty":"RSA",
        "n":ab2base64url(bnToBuf(n)),
        "e":ab2base64url(bnToBuf(e)),
        "d":ab2base64url(bnToBuf(d)),
        "p":ab2base64url(bnToBuf(p)),
        "q":ab2base64url(bnToBuf(q)),
        "dp":ab2base64url(bnToBuf(dp)),
        "dq":ab2base64url(bnToBuf(dq)),
        "qi":ab2base64url(bnToBuf(qi))
    }
    
    // Import key        
    const privateKey = await window.crypto.subtle.importKey(
        "jwk",
        jwk,
        {
            name: "RSASSA-PKCS1-v1_5",
            hash: "SHA-256",
        },
        true,
        ["sign"],
    );
    
    // Sign data
    const plaintext = new TextEncoder().encode("The quick brown fox jumps over the lazy dog");
    const signature = await window.crypto.subtle.sign(
        "RSASSA-PKCS1-v1_5",
        privateKey,
        plaintext,
    );
    console.log("signature (hex):", ab2hex(signature));
      
    //
    // Helper
    //
      
    //https://github.com/juanelas/bigint-mod-arith
    function modInv (a, n) {
        const egcd = eGcd(toZn(a, n), n);
        if (egcd.g !== 1n) throw new RangeError(`${a.toString()} does not have inverse modulo ${n.toString()}`); // modular inverse does not exist
        else return toZn(egcd.x, n);
    }
    
    function eGcd(a, b) {
        let x = 0n, y = 1n, u = 1n, v = 0n;
        while (a !== 0n) {
            const q = b / a, r = b % a, m = x - (u * q), n = y - (v * q);
            b = a, a = r, x = u, y = v, u = m, v = n;
        }
        return {g: b, x, y}
    }
    
    function toZn (a, n) {
        if (n <= 0n) throw new RangeError('n must be > 0');
        const aZn = a % n;
        return (aZn < 0n) ? aZn + n : aZn;
    }
    
    // https://coolaj86.com/articles/convert-js-bigints-to-typedarrays/ 
    function bnToBuf(bn) {
        var hex = BigInt(bn).toString(16);
        if (hex.length % 2) hex = '0' + hex;
        var len = hex.length / 2;
        var u8 = new Uint8Array(len);
        var i = 0, j = 0;
        while (i < len) {
            u8[i] = parseInt(hex.slice(j, j+2), 16);
            i += 1, j += 2;
        }
        return u8;
    }
    
    // https://gist.github.com/s1r-J/fdc368b818be78ca58878085c89bd82b
    function ab2base64url(ab) {
        const str = String.fromCharCode.apply(null, new Uint8Array(ab))
        return base642base64url(window.btoa(str));  
    }
    
    function base642base64url(base64) {
        return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/g, '')
    }
    
    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/exportKey#pkcs_8_export
    function ab2str(buf) {
        return String.fromCharCode.apply(null, new Uint8Array(buf));
    }
    
    // from https://stackoverflow.com/a/40031979
    function ab2hex(ab) { 
        return Array.prototype.map.call(new Uint8Array(ab), x => ('00' + x.toString(16)).slice(-2)).join('');
    }
    
    })();

    The signature is valid, as can be verified e.g. here with CyberChef.


    Note that BigInt is a vulnerability in the context of cryptography in general, since the implementation is not constant-time and thus timing attacks are possible. For this example here, BigInt is only used to determine q_inv and generate the JWK.
    If the vulnerability implied by this is not acceptable, a third-party library must be applied that meets your requirements in the context of cryptography. The same care should be taken with the implementations of the other libraries.


    Alternative to a JWK, using PEM/DER encoded keys in PKCS#1 and PKCS#8 format1 and third party libraries:

    Python-RSA supports exporting the key in (DER or PEM encoded) PKCS#1 format. WebCrypto unfortunately does not support this format (nor the PEM encoding), but the DER encoded PKCS#8 format is supported. A conversion of the (DER or PEM encoded) PKCS#1 format to the DER encoded PKCS#8 format is possible e.g. with OpenSSL.

    Example:

    • Step 1: Export as PEM encoded PKCS#1 key:

      import rsa
      
      key = [23536280960117078781071277796506388364632844470760889287043499897812352121486284917799618205473774110082986255111078948929716171007803508886970676224127450295642559080964190798895956419173396140605893662673557325803620649459356106588185328074303624698999369184931437392043701807154016276826947703489139066577610914955532141229640472629981914163258711276860837496743796618919012922407451955239609188448498501143346583070612890214114610395155257827751505466411422773826133218559917162211680737700993678145296589807650002517691621420203575296591550000452984882555949046114026781590107745032317312675291585975313969848859, 65537, 9382262539985942035117370835310273525276302879268630432030935728372487284645760310626287831576092110196042173349053779403830431169856213584267038716378986511034405846929055108124522963378015078098310449629166503450258471811735024255250342492655785209291827821785156504984080896469149873080916257284492090183134232671709250785165983314728570454111274522098371781165874751498087056242271462925111175964110913088917016069904769646771252836449225104991887781851938529428988142021234387304783760289871945413583159049359570528147608301067614991660860541652310473617313526348376112227247717281579081381208446321849438764473, 155848818230016115749752423910909717296251325660942142725149379121248574362967471982268814952228835353898234610957136620749647839486274159495856726744103519159395949128597219453194423432726936416070232282723469181220002341912380459799343589855993917183257748160693240120150007893839052471718988588887870321263, 151019951433831574850603517176599781805882023744929175175855523475079892352628220454545688743736195507788846749671107149711466014334137134060305215869022779903575705272635233063037001421670083870087620888835502764099951892566093068046644025208762390847987888334442622157531568813775934596672771679438667247893]
      private_key = rsa.PrivateKey(key[0], key[1], key[2], key[3], key[4])
      private_key_pkcs1 = private_key.save_pkcs1().decode('utf-8') 
      print(private_key_pkcs1)
      
    • Step 2: Conversion of the PEM encoded PKCS#1 key to the DER encoded PKCS#8 key using OpenSSL:

      openssl pkcs8 -topk8 -inform PEM -outform DER -nocrypt -in pkcs1.pem -out pkcs8.der
      
    • Step 3: The DER encoded PKCS#8 key generated with OpenSSL is to be Base64 encoded and then imported with WebCrypto:

    (async () => {
    
    const pkcs8Der = b642ab("MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8swwj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqWrbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4OF2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtLzd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOWuL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEqkXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODwczdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfyqmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBXmNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyFEM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3obhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884JoRS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDsjRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCDJjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpMIvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltayaj3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFalCbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdHaj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIAX4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTGPr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLABviZm5AFCQWfke4LZo5mOS10");
    
    // Import key        
    const privateKey = await window.crypto.subtle.importKey(
        "pkcs8",
        pkcs8Der,
        {
            name: "RSASSA-PKCS1-v1_5",
            hash: "SHA-256",
        },
        true,
        ["sign"],
    );
    console.log(privateKey);
    
    function b642ab(base64_string){
        return Uint8Array.from(window.atob(base64_string), c => c.charCodeAt(0));
    }
    
    })();

    1The PKCS#1 and PKCS#8 format use ASN.1 as description language and typically DER encoding. ASN.1/DER encoded keys can be loaded in an ASN.1 parser like https://lapo.it/asn1js/. The PEM encoding is a representation of the ASN.1/DER encoding as text: The ASN.1/DER encoded key is Base64 encoded, with line breaks after each 64 characters. This data is surrounded with a format dependent header and footer.