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));
}
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.