I found the answer after some tests. In my case, I used JSEncrypt with PHP / openssl or phpseclib as a backup.
With JSEncrypt, you cannot select an encryption algorithm. And this affects the padding used when PHP decrypts the encrypted value. JSEncrypt uses:
- RSASSA-PKCS1-v1_5
- SHA-1 as a hash method
If you want to decrypt the message, you must use the default add-on option:
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_PADDING);
But WebCrypto is incompatible with JSEncrypt (we cannot decrypt the message with PHP with the same parameters), 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 purposes (this is only allowed for signature purposes). Instead, you should use RSA-OAEP.
If you try to decode the encrypted value with the default parameters, you will receive this message:
RSA_EAY_PRIVATE_DECRYPT:padding check failed
So, you need to change the fill parameter as it should (in PHP):
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
As for my initial question, yes, you can import the key in PEM format if you follow the steps that I mentioned in the message
- Delete PEM Header
- Remove the footer
- Remove CR / LF
- Trim line
- Decode Base64 string
- Convert result to ArrayBuffer
Full 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); }); }); }