Outils pour utilisateurs

Outils du site


notes:webcrypto

Ceci est une ancienne révision du document !


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

Conversion de clés

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 phpseclib

<?PHP
require('phpseclib/autoload.php');
use phpseclib\Crypt\RSA;
 
$rsa = new RSA();
$rsa->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.

<?PHP
$rsa = new RSA();
$rsa->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';
?>
notes/webcrypto.1585319683.txt.gz · Dernière modification: 2020/03/27 15:34 de etienne