====== WebCrypto ====== 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. ===== RSA-PSS pour signature ===== [[https://artnum.ch/code/demos/webcrypto/sign.html|Démonstration]] ==== Conversion de clés ==== ==== Clé publique de PEM vers DER ==== $ openssl pkey -pubin -in rsapub.pem -out rsapub.der -outform DER === OpenSSH === Pour convertir une clé SSH vers RSA (pour ensuite être utilisée avec OpenSSL) $ ssh-keygen -p -m PEM -f ssh-rsa.key Ensuite on extrait sa clé publique et on la converti en PKCS#8 $ openssl rsa -in ssh-rsa.key -pubout > ssh-rsa.pub $ openssl pkcs8 -topk8 -nocrypt -in ssh-rsa.key -out ssh-rsa.pkcs8 === OpenSSL === 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 === JavaScript === 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-----` } ==== Génération de clés ==== === OpenSSL === 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 === SSH === OpenSSH génère des clé au format RFC4716. $ ssh-keygen -t rsa -b 2048 -f ssh-rsa.key Avec l'option "-m PEM" on peut avoir directement une clé PKCS#1 === JavaScript === 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)]) }) }) }) } ==== Création de signature ==== === OpenSSL === echo -n "Texte à signer" | openssl dgst -sha256 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:0 -sign fromjs.key -out - - | base64 === JavaScript === 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)) }) }) } }) } === PHP/phpseclib 2 === Signature avec [[http://phpseclib.sourceforge.net/|phpseclib]] loadKey(file_get_contents('fromjs.key')); $rsa->setHash('sha256'); $rsa->setMGFHash('sha256'); $rsa->setSaltLength(0); $rsa->setSignatureMode(RSA::SIGNATURE_PSS); $text = 'Texte à signer'; $signature = $rsa->sign($text); echo base64_encode($signature); ?> === C/OpenSSL EVP === /* Pour lire la clé : * * FILE * fp = fopen("pkey.pem", "r"); * sign(PEM_read_PrivateKey(fp, NULL, NULL, NULL), ...); */ void sign (EVP_PKEY * pkey, const char * text, unsigned char ** stext, size_t * slen) { size_t siglen = 0; EVP_PKEY_CTX * kctx = NULL; EVP_MD_CTX * mctx = NULL; mctx = EVP_MD_CTX_new(); if (!mctx) { return; } kctx = EVP_PKEY_CTX_new(pkey, NULL); if (kctx) { /* ordre important ici */ EVP_MD_CTX_set_pkey_ctx(mctx, kctx); EVP_DigestSignInit(mctx, NULL, EVP_sha256(), NULL, NULL); EVP_PKEY_CTX_set_rsa_padding(kctx, RSA_PKCS1_PSS_PADDING); EVP_PKEY_CTX_set_signature_md(kctx, EVP_sha256()); EVP_PKEY_CTX_set_rsa_mgf1_md(kctx, EVP_sha256()); EVP_PKEY_CTX_set_rsa_pss_saltlen(kctx, 0); EVP_DigestSignUpdate(mctx, text, strlen(text)); EVP_DigestSignFinal(mctx, NULL, &siglen); *stext = calloc(siglen, sizeof(**stext)); if (*stext == NULL) { EVP_MD_CTX_free(mctx); return; } EVP_DigestSignFinal(mctx, *stext, &siglen); *slen = siglen; } EVP_MD_CTX_free(mctx); return; } ==== Vérification de signature ==== === OpenSSL === $ 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 - === JavaScript === 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) }) }) } }) } === PHP/phpseclib 2 === L'ordre des opérations (setHash, setMGFHash, loadKey, ...) n'est pas déterminant. loadKey(file_get_contents('fromjs.pem')); $rsa->setHash('sha256'); $rsa->setMGFHash('sha256'); $rsa->setSaltLength(0); $rsa->setSignatureMode(RSA::SIGNATURE_PSS); $text = 'Texte à signer'; echo $rsa->verify($text, base64_decode(file_get_contents('fromjs.sig'))) ? 'Valide' : 'Invalide'; ?>