I've found an answer after some tests. In my case, I used JSEncrypt with PHP/openssl or phpseclib as a fallback.
With JSEncrypt, you can't choose the encryption algorithm. And this has an impact to the padding used when PHP deciphers the encrypted value. JSEncrypt uses:
- RSASSA-PKCS1-v1_5
 
- SHA-1 as a hash method
 
If you want to decipher a message, you have to use the default padding option:
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_PADDING);
But WebCrypto is not compatible with JSEncrypt (we cannot decrypt the message with PHP with the same options), because:
- WebCrypto can use SHA-1 as a hash method, even if it is not recommended.
 
- But WebCrypto forbids you to use RSASSA-PKCS1-v1_5 for encryption purpose (it is only allowed for signing purposes). You should use RSA-OAEP instead.
 
If you try to decode the encrypted value with the default options, you'll get this message:
RSA_EAY_PRIVATE_DECRYPT:padding check failed
So, you have to change the padding option as follow (in PHP):
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
Regarding my original question, yes you can import a key in PEM format if you follow the steps I've mentionned in the post
- Remove the PEM header
 
- Remove thePEM footer
 
- Remove CR/LF
 
- Trim string
 
- Decode the Base64 string
 
- Convert the result to an ArrayBuffer
 
Complete code:
var crypto = window.crypto || window.msCrypto;
var encryptAlgorithm = {
  name: "RSA-OAEP",
  hash: {
    name: "SHA-1"
  }
};
function arrayBufferToBase64String(arrayBuffer) {
  var byteArray = new Uint8Array(arrayBuffer)
  var byteString = '';
  for (var i=0; i<byteArray.byteLength; i++) {
    byteString += String.fromCharCode(byteArray[i]);
  }
  return btoa(byteString);
}
function base64StringToArrayBuffer(b64str) {
  var byteStr = atob(b64str);
  var bytes = new Uint8Array(byteStr.length);
  for (var i = 0; i < byteStr.length; i++) {
    bytes[i] = byteStr.charCodeAt(i);
  }
  return bytes.buffer;
}
function textToArrayBuffer(str) {
  var buf = unescape(encodeURIComponent(str)); // 2 bytes for each char
  var bufView = new Uint8Array(buf.length);
  for (var i=0; i < buf.length; i++) {
    bufView[i] = buf.charCodeAt(i);
  }
  return bufView;
}
function convertPemToBinary(pem) {
  var lines = pem.split('\n');
  var encoded = '';
  for(var i = 0;i < lines.length;i++){
    if (lines[i].trim().length > 0 &&
        lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 && 
        lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-END PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 &&
        lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) {
      encoded += lines[i].trim();
    }
  }
  return base64StringToArrayBuffer(encoded);
}
function importPublicKey(pemKey) {
  return new Promise(function(resolve) {
    var importer = crypto.subtle.importKey("spki", convertPemToBinary(pemKey), encryptAlgorithm, false, ["encrypt"]);
    importer.then(function(key) { 
      resolve(key);
    });
  });
}
if (crypto.subtle) {
      start = new Date().getTime();
      importPublicKey($('#pubkey').val()).then(function(key) {
        crypto.subtle.encrypt(encryptAlgorithm, key, textToArrayBuffer($('#txtClear').val())).then(function(cipheredData) {
            cipheredValue = arrayBufferToBase64String(cipheredData);
            console.log(cipheredValue);
        });
      });
}