Ceci est une ancienne révision du document !
Comme il est parfois difficile de faire le lien entre les formats, les algorithmes et les divers paramètres, cette page à pour but de montrer le lien qu'il existe entre des opérations avec l'API WebCrypto et d'autres outils et langages. Elle ne reflète en aucun cas des méthodes devant être appliquée mais bien des exemples comme générer un clé privée avec OpenSSL et l'utiliser avec WebCrypto.
Pour importer une clé en JavaScript, le format PKCS#8 est préféré
$ openssl pkcs8 -topk8 -nocrypt -in rsakey.pem -out rsakey.pkcs8 # converti pkcs8
Convertir une clé PKCS#8 format PEM vers un ArrayBuffer utilisable avec crypto.subtle.importKey
function b64Decode (text) { let s = atob(text.trim().replace(/\r?\n|\r/g, '')) const buffer = new ArrayBuffer(s.length) const bview = new Uint8Array(buffer) for (let i = 0; i < s.length; i++) { bview[i] = s.charCodeAt(i) } return buffer } function readPEM (text) { const pemRegex = /^-----BEGIN (?:PRIVATE|PUBLIC) KEY-----$([a-zA-Z0-9\/\+=\r\n]*)^-----END (?:PRIVATE|PUBLIC) KEY-----$/mg let m = pemRegex.exec(text) if (m && m.length > 1) { return b64Decode(m[1]) } return undefined }
Convertir un ArrayBuffer obtenu avec crypto.subtle.exportKey (avec une mise en forme similaire à OpenSSL)
function b64Encode (buffer) { let txt = '' let b64 = btoa(String.fromCharCode.apply(null, new Uint8Array(buffer))) for (let i = 0; i < (Math.ceil(b64.length / 64)); i++) { txt += b64.substring(i * 64, (i + 1) * 64) + "\n" } return txt } function writePEM (buffer, priv = false) { let txt = `-----BEGIN ${priv ? 'PRIVATE' : 'PUBLIC'} KEY-----` + "\n" txt += b64Encode(buffer) return txt + `-----END ${priv ? 'PRIVATE' : 'PUBLIC'} KEY-----` }
OpenSSL génère, avec genrsa, une clé au format pkcs#1 codé avec PEM
$ openssl genrsa -F4 2048 > rsakey.pem # clé privée 2048bits pkcs1 $ openssl rsa -in rsakey.pem -pubout > rsapub.pem # clé publique
function genKeyPair() { return new Promise((resolve, reject) => { /* same as openssl genrsa -F4 2048 */ crypto.subtle.generateKey( { name: 'RSA-PSS', hash: 'SHA-256', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]) }, true, ['sign', 'verify']).then((key) => { Promise.all([ crypto.subtle.exportKey('pkcs8', key.privateKey), crypto.subtle.exportKey('spki', key.publicKey) ]).then((keys) => { resolve([writePEM(keys[0], true), writePEM(keys[1], false)]) }) }) }) }
echo -n "Texte à signer" | openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:0 -sign fromjs.key -out - - | base64
function signFromPKCS8 (text, privkey) { return new Promise((resolve, reject) => { const key = readPEM(privkey) if (key) { crypto.subtle.importKey( 'pkcs8', key, { name: 'RSA-PSS', hash: { name: 'SHA-256' } }, false, [ 'sign' ] ).then((key) => { crypto.subtle.sign( { name: 'RSA-PSS', saltLength: 0 }, key, (new TextEncoder).encode(text) ).then((signed) => { resolve(b64Encode(signed)) }) }) } }) }
$ base64 -d < fromjs.sig - > fromjs.bin.sig $ echo -n "Texte à signer" | openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:0 -verify fromjs.pem -signature fromjs.bin.sig -
function verifyFromPKCS8 (text, signature, pubkey) { return new Promise((resolve, reject) => { const cert = readPEM(pubkey) if (cert) { crypto.subtle.importKey( 'spki', cert, { name: 'RSA-PSS', hash: { name: 'SHA-256' } }, false, [ 'verify' ] ).then((key) => { crypto.subtle.verify( { name: 'RSA-PSS', saltLength: 0 }, key, b64Decode(signature), (new TextEncoder).encode(text) ).then((verified) => { resolve(verified) }) }) } }) }