简体   繁体   中英

Decrypting OAEP RSA In JavaScript (Like PHP's openssl_private_decrypt)

I'm currently encrypting data in PHP as follows:

//$sMessage is the input to encrypt.
//$sPublicKey is the public key in PEM format.
openssl_public_encrypt($sMessage, $sEncrypted, $PublicKey, OPENSSL_PKCS1_OAEP_PADDING);
//$sEncrypted will now store the resulting text.

Here's an example of the code used to generate the key in PHP:

  $aConfig = array(
    "digest_alg" => "sha256",
    "private_key_bits" => 4096,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
  );
  $res = openssl_pkey_new($aConfig);
  //$sPassPhrase is the pass phrase.
  openssl_pkey_export($res, $sPrivateKey, $sPassPhrase);
  //$sPrivateKey is the key (in PEM format).

And my decryption is using a private key that's protected by a pass phrase.

//$sPrivateKeyWithPassPhrase is the protected private key in PEM format.
//$sPassPhrase is the pass phrase to protect the key.
$newRes = openssl_pkey_get_private($sPrivateKeyWithPassPhrase, $sPassPhrase);
//$sEncrypted is the encrypted text (ciphertext).
openssl_private_decrypt($sEncrypted, $sDecrypted, $newRes, OPENSSL_PKCS1_OAEP_PADDING);
//$sDecrypted is the decrypted text (plaintext)

I've also found out how to decrypt with Python using the cryptography library ( which you can learn about here ):

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

#passphrase is the pass phrase
passphrase_bytes = bytes(passphrase, 'utf-8')
#privatekey is the encrypted private key
private_key = serialization.load_pem_private_key(
    privatekey,
    password=passphrase_bytes,
)
#ciphertext is the text to be decrypted
plaintext = private_key.decrypt(
    ciphertext,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA1()),
        algorithm=hashes.SHA1(),
        label=None
    )
)
#plaintext will be the decrypted result.

However, I've been struggling to find a comparable function in JavaScript.

The best answer I found so far was this one: RSA Encryption Javascript

However, it doesn't seem like keys are in PEM format and when I go to the pidCrypt homepage to get more documentation, it's a 404 error.

I found this one as well: PHP's openssl_sign Equivalent in Node JS

The NodeJS library in general seems to have the right functions but when I went through the NodeJS documentation the Quick Start Guide seems to suggest that in order to use NodeJS I have to first install software and set up a web server. According to how to run node.js client on browser , it's not possible "Node.js is not browser javascript. There are many parts of it that use OS features not available in a browser context." (I'm hoping for something I can run in a native browser on the client-side.)

Someone suggested in a comment that I could use WebCrypto. After some struggle to figure out where to download it, I found it's actually native to the browsers (which was a pleasant surprise). However, I haven't been able to figure out how to import a PEM key from the documentation, and when I went to generate a key then export it, the only available formats were "jwk" (some sort of JavaScript object), "spki" (some kind of array buffer), and "pkcs8" (which I only got DOMException: key is not extractable). Nothing looks remotely like the OpenSSL PEM format. (I believe this is PKCS1.)

When I did "crypto.subtle.importKey" it wanted 5 different parameters (format, keyData, algorithm, extractable, keyUsages). None of those accept the PEM string. After a bit of struggle I did find SubtleCrypto importKey from PEM which confirmed that it's possible if you do fancy manipulation to import a PEM public key, however that's for public keys in PEM format and I have no idea how to handle pass phrases (and really I'd rather not program that all by hand). Then I found How can I import an RSA private key in PEM format for use with WebCrypto? and Javascript, crypto.subtle: how to import RSA private key? however those basically told me it's not possible unless I go and convert the private key to PKCS8 using a command line OpenSSL (that also has to be installed separately and I remember was a huge pain to get on Windows). And once I get past that hurdle, I'm still not sure how I would go about getting an equivalent pass phrase protection in PKCS8. I'm hoping there's something similar that ideally supports PKCS1 to do the same as the PHP and Python code above, and I can run it 100% in the native browser. (Importing more JavaScript code is okay.)

I'd ideally like to find a simple working JavaScript example for OpenSSL RSA decryption, equivalent to the PHP or Python ones above. If I have the private key PEM string, the pass phrase, and the cipher text it can give back the plaintext.

Thank you very much! I appreciate all your help a ton and you can save me many hours!

As already noted in the comments, a JavaScript library is required that can import and export encrypted keys.

Since OAEP is used as padding, the library must also support OAEP and preferably allow arbitrary configuration of the associated parameters.

forge , a pure JavaScrpt library, fulfills these criteria.


The following forge example uses an encrypted key and a ciphertext, both generated with the posted PHP code. For the test a 1024 bits key is applied (note that in practice at least 2048 bits keys must be used for security reasons).

The example imports key and ciphertext and decrypts the ciphertext and thus demonstrates that forge can import and decrypt keys as well as ciphertexts both generated and encrypted with the PHP code.

 var encPkcs8Pem = `-----BEGIN ENCRYPTED PRIVATE KEY----- MIIC1DBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIOWo28ihesJwCAggA MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECLGX3aHMfd9KBIICgOKh/RiTC/3a z+LCg0/Y1pyZH43OS68N5nnK0SbW+qr5OJwlkP95f8IZb7G1kPEyXki7MOKCwyhr xPZMUzDJJv4YJZuElP0VRlzNz30i+GlIUNMLaqEynlQgLQErg/S1za9SxYTmuqe4 gcX35gtNdX09lms5dfGNsl+aWYSJsrplDP9LSW5Khd6H2r0FGoxbT5mC3BSJ3/ZZ g/v5hWdmzG576fMMTZ0eQ9OqpH5/+d1g87OF1IXYLk6JNPhFykE6HPCRNfR8xL5s uXIRk//Et/pSRG1A82/rdwPSetPNrnsCpeGCxwRkZXXgLQx2X+63eyNEWo4TXT2z chi52pKapHZdTTrB9GhCPK45aW7F6dWD9iJx9HOjquy8Y5MnNE5FqPwfLxqCvS5n HdiV+nvkvZm7VmxWANqtBPwzS3IeGU/UB2r88rYMFDrtn8t8MiQ/Jv2Ehp3ofVyR Bd2OKUTgZvNh5YWKdFBIEFiOrnXhf1XOZY0fc/0PVvQhTVOQHz43eHHIZSSzoIyD SMNtovFr9l1Wce6+avyXrPYPtIW3z6WJHbH3gyJ+0BdcVUXxkx89u4TyORZFFOBj nVDUPgfHaI8nHItCeYvW8mEdNja+m1IMt+RqBeY9goOlrLTzCdHaDEcRVWb0wYkY 7LKoGNp3hMf6aUUJXWKdKdGGsZnJMWOp/vsajr6mzbvUAJ2YAKLeR9OsY2IU+fZS ORUZGlLVeeQfFA3jZEq2r0ia/VNcOITButfuRVQnp3isq/Ub5x9p75NpvBW0ToAI 9J6dDepBnZkTCEMoAH7q696oBP379BB+7cuRDtZZ1mP4z82tp3Mve6qDqNFXCUNG II0YmXBD0NU= -----END ENCRYPTED PRIVATE KEY-----`; // Import an PKCS#8 encrypted key from PHP var encryptedPrivateKeyInfo = forge.pki.encryptedPrivateKeyFromPem(encPkcs8Pem); var privateKeyInfo = forge.pki.decryptPrivateKeyInfo(encryptedPrivateKeyInfo, 'mypassphrase'); var pkcs8Pem = forge.pki.privateKeyInfoToPem(privateKeyInfo); var privateKey = forge.pki.privateKeyFromPem(pkcs8Pem); // Decrypt ciphertext from PHP var ciphertext = forge.util.decode64("l8+TDQQoMbK8FiHCgAO9B/uo7OXuaGehEwfWyDM5jgFM/qRbcWFHlzLGGT5d0ZMWhH7SZc2DrLiZbLM9OEGqt6AeCgPhbaeP9YbPpUZbVeNspGi5jqEIMwjc7iH6+9JYXzRolbCwG37L8+q18LtgpWVFvJ/07dJ5H5jvQf6uj/c="); var decrypted = privateKey.decrypt(ciphertext, 'RSA-OAEP'); document.getElementById("pt").innerHTML = "Decrypted: " + decrypted; // The quick brown fox jumps over the lazy dog
 <script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script> <p style="font-family:'Courier New', monospace;" id="pt"></p>


The following forge example illustrates key generation, key export and encryption/decryption:

 // Key generation var keyPair = forge.pki.rsa.generateKeyPair({bits: 1024, e: 0x10001}); var rsaPrivateKey = forge.pki.privateKeyToAsn1(keyPair.privateKey); var privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey); var encryptedPrivateKeyInfo = forge.pki.encryptPrivateKeyInfo( privateKeyInfo, 'mypassphrase', { algorithm: 'aes256', }); // Key export: private encrypted PKCS#8 var encPkcs8Pem = forge.pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo); document.getElementById("priv").innerHTML = "PKCS#8 encrypted private key:<br>" + encPkcs8Pem.replaceAll("\r\n","<br>"); // public X.509/SPKI var x509Pem = forge.pki.publicKeyToPem(keyPair.publicKey); document.getElementById("pub").innerHTML = "X.509/SPKI public key:<br>" + x509Pem.replaceAll("\r\n","<br>"); // Encryption var encrypted = keyPair.publicKey.encrypt(forge.util.encodeUtf8("The quick brown fox jumps over the lazy dog"), 'RSA-OAEP'); document.getElementById("ct").innerHTML = "Ciphertext, base64: " + forge.util.encode64(encrypted); // Decryption var decrypted = keyPair.privateKey.decrypt(encrypted, 'RSA-OAEP', { md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); document.getElementById("pt").innerHTML = "Decrypted: " + decrypted;
 <script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script> <p style="font-family:'Courier New', monospace;" id="priv"></p> <p style="font-family:'Courier New', monospace;" id="pub"></p> <p style="font-family:'Courier New', monospace;" id="ct"></p> <p style="font-family:'Courier New', monospace;" id="pt"></p>
If a stored encrypted key is to be used for decryption, load that key from your storage device and proceed analogously to the 1st example.


Regarding OAEP: For both digests, OAEP and MGF1, SHA1 is applied (consistent with the Python and PHP code), which is also the forge default for both digests as a comparison of encryption and decryption in the last example shows.

PKCS#1 Versus PKCS#8

Note that while private keys generated without pass phrases in PHP are PKCS#1, the private key generated from PHP with a pass phrase is actually PKCS#8 (see this link for how to determine which standard applies to a private key).

Third Party Library Required

In order to work with PEM-format keys, a custom JavaScript library (or other custom code) is required. There are various libraries at this link which you can look at. Many are not maintained anymore.

This example uses the Forge JavaScript library, but you could also use a different library.

Installing/Importing Forge

While there are various ways to build the Forge environment , the easiest is to add the following HTML code prior to where you need to do encryption/decryption:

<script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script>

Performing RSA Decryption

The following JavaScript code will perform decryption using the Forge library with a private key that's passphrase-protected:

// Import an PKCS#8 encrypted key from PHP
//sEncPkcs8Pem = The encrypted PEM key in a string.
//sPassPhrase = A pass phrase string to use for decryption.
//bCipherText = The encrypted cipher text (string of bytes).
var encryptedPrivateKeyInfo = forge.pki.encryptedPrivateKeyFromPem(sEncPkcs8Pem);
var privateKeyInfo = forge.pki.decryptPrivateKeyInfo(encryptedPrivateKeyInfo, sPassPhrase);
var pkcs8Pem = forge.pki.privateKeyInfoToPem(privateKeyInfo);
var privateKey = forge.pki.privateKeyFromPem(pkcs8Pem);
var sDecrypted = privateKey.decrypt(bCipherText, 'RSA-OAEP');
//sDecrypted will store the result.

Full Working Example

The following is a full working example with a 4096-bit key. The JavaScript imports an encrypted private key (PKCS#8 format, PEM encoded) and successfully decrypts a ciphertext.
The encrypted key and ciphertext were both generated using the posted PHP code.
The JavaScript library applied is forge .

 <script src="https://cdn.jsdelivr.net/npm/node-forge@0.7.0/dist/forge.min.js"></script> <p style="font-family:'Courier New', monospace;" id="pt"></p> <script> var encPkcs8Pem = `-----BEGIN ENCRYPTED PRIVATE KEY----- MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIxjk2j/jhKPECAggA MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECMWHokN4f1pIBIIJSGsi3CbrV4Uf QL70dh+nKI0+PbSKlpx4CJZVARiTonO26+YathAgo/F6TKY+MZAfctuAg38EDo6u zyjPxkrz8Ha1rIhBioFg4Tyem4s/16F4dVw9D4+dy/ORCk7PvbCg++lCCt3lVAqF Q+zX8X9ygXza+PyHlJzAi6o2MQsoxC37cBvhLmPGSbVFPBAhSmfsWqct7StMp872 jIk/2x1xt0gZlSIayefLUEc/OP1sBXoBeFEFF3nxZrpMaCixElEyMKWHaQPDberm gkbuvwCmwC/rFCO4BG4GLcgWzQ8msYBIE0wS+QoLzBqE+RrAXPgTpFto2/S/SZ2U 2zRiyXtDG6AK0yBBj9IJcSUHbNO7JvBsbxg9LO8Y+HxkwPQkQSF4CABsuFXeZ75a eXs2AaptSmj5rjHHRoYKPB1Gsi7IkLa0q9otDCA2veDZRyh1OAhasZ382F3KQvGa z8JQ0sahKLDUWdb9R0KVnEVurCnz4gutthMwXyfBJrAO9W9q0f3R40OOQvWxhTzX cre04g8ihlYE39JdCWL09yctBXaDbGDMd3qIyo6+fHllVpRJVBzQB7mu2SAd1ZAQ LcIpq4a83/82IGWN7oOkwmtceqpZNKo53SPEzHmU8sz+ZZSEDk5blDPrZLpypStU MeS4vvRfartTtfgzD+nr5ulAUCPHs3EgentQAg92kHHs1znFW0oc5Iuyr57RAFvh qbAgeUFKPIq/8lcAJkrPLmPeS/HkA6oNr2HC/+IgdUNBVUPOdp8tg8R+S6c3jt1x fH9WWHT+xARK8Eo/5sQwdlb74ix+PkKEuf1EziYDZ/QFrsV72Fy2UZahKO2OmVHr ZF3GN08gCL7Oh5Uo3fip5sVctxz1sD1RpAR4ClvVAJc+NbReKw7Oi9LKqQ0+8n6l YwXTcRmkYv5ZU1mQedCQLv/CaelcDZ6o+7cTiNtpMP1kI05pux8nZISq9U3sL6HR b2cWEiVtUf/aLg5kBp3RiWDCsXazZ2JPovgp4IvhHzRJvRauwlfKmCNYh5aeoAAF NYtPhT1CY9WW1tsRvsr8KmaG4kB1wMHVaidGz3l2yVJmy+FVlylU1Uy+bZ5Km+77 ynREheQ3cwvG+n0IDJDTbmchnEB+B1eGuaoepjlVPLR291gRESTwMwUO2Rjc3Vu4 SD0FByqNeOQBfjTQJdXoKeWiSuvo9syC/1N6bsT88y/rOIqB8UDSBnrxSAvu3WGW 2MfJhuG8dQe056T5zkftifInFpwph+hHi589+LmuTq93iplKxQsJWEESe77OdSTl Bg9MCOeBMa+sR3FDVDjGZ2UlscCp51SRXkbVPDkwnrPayqG6PNsFO8Hb1CHmzISi K8ZnTrvgrIvLQ2QrpJZp5YvLG6V0rpBoII2cEwxDqoKcvAt46iIcTIj9UFs3iEPx QLvtGvgm0xQXxVkrv3VDykUwMM+STySWJkJpT/1559XQytigRct+Ha5MpRzAHyo8 d6mV0Zi6Zh5GTQ/rO3+vbNXQQPy79f9Zrpne10GG+YltOHNbCDHz4sTBbvsAJvdz nuMMG9Nt8sQ14XkH88VQVOk603MIHuoc7wp//6Z64sTd9ktu/Sm08vBtU/f7cslc bjnP/ZH1JTaAILHyEfgdnVBacf2y/SrlEp2X0Ql/fu49UmIoT7ykcwJnhQNaXZ3N M5lSqpNlB+lfshNbLHMaAp6sv8WARkK6etartmKveoZaJVhMicfvIrwlcMirO3Li h5j97ZE8jxNyeGUV4425XIH4uN5qnlqxBVkCT5lln8GcPK/9Vkm/lUn/nXdGkFV4 4LOpmM7637N3KVah1iBL5LjEh5qXXgmVUdeiEQwXlLwIq33WszJLSSmI/wDC6H6N uyG5u3Fx2gBZ9XCuJffoKJhH9UtIghIbilgNORc+8IE3w4beqFAHU9lbJeV85DMb f3PbgLwN5Wc8hr6wPsK1ygJZhf+zzMQ67apa/pa2OuIRIYmmiee2VGxEJDCKUmFW mIxqnZBsmDG1ajCFnIZ3EAwNhaYvVDrdoj5tYnwaflW1+WTAN7NqFGvmEZakJ2A+ tbRdbFIfxBrrbI3MNYMCx2w1hQHOCogLUsrc8vOM79Uv2z9LiPPmZocKnWv4NFR8 9rHPdFIhBOdsE6bfAG+UjpUcFJMblYwevVmu/te6p/TXEkosxcPuWJH8nXZ4uyx2 LO0FnvFMjQgAP3GShqjjrnDLgabC6Fx+I0kTJNdUYvFHUfyOK1Nszi1QODz7CP6Z NglJoeFWCvtfmMGxM55qQJUFq1iacwAekW+K913WlPNCssXS3lWyenPOzgj40klS e5RirohgDyhYONhPQVEsWRQsKcLIDC2q3l0jTbkfshWtaxwh3NGCdDTRoB/Aedy1 egq/HLJrQgGozQ+o130MIVLWhMuoOLIIl1XEFPtoT6ZLpwCh+HTL/tSdw7+QajV5 uZ+XxLc6lkcrJg4RvQjRm2aHsqi3HbBX+ydn2nAkuv6jPnw9EsAaAV4OWqYI7/R6 Zk6lncr6PmXABooFVpKg2WeTTp0j6Ye4M1QgxXosdtQjmuNRaNo2/QW2p3P0Sgvp FsSwmWU1mTw9zPItQs9Y+ij/r27v5Atxy9D6RHU7QysjcMceMgUf9zeu3S1IOt2I Rz2NYEhKqcVhne4yXMFO6dhdBZa3fyO0mWU1LyELykwjsZ/9KdFDlvksI9vFJ7zI PWIUpW7EOKdLTHunXZu6Dn59fQqJYLFVlf4FgJTT4/YshIS0Nu+MeS45HQY7G3Up QHXJ2TjHXVHV4amajkCLhSgaP7C59i8L9f+spWCu5v/8KKx6XLLbO6izI8TpWPW/ 7rAysCmd9uNTTzJtUQJoBVZ1r8p7xoQXtkegqyE5/zsldIzSeC8/yr+XaZ2CK7zo NIdwzqjFtpkcvsCQT3xLmGvLWJM9Jai1Hpfuhaggl9z7lo/i2rTsSIJRPa1BrSnm +ntlsNo3r+RSoI+Tmi1MKzz2p+X4rXlD0tfPGm/yzuBXx8mtbZVX5yBNEI2l+EwN EqPR5qHuobYfK8aZ7juB+AHoVlrGM26Q1PghHOFPc1dZVm3K+fThl19t/jdVve6Q 6NKAggLjuyEU4f+MELA8fwq7/66wqfgWIcBdGpn0npI23nx24jlfiKmmacBrKAHM ij9mm4GhutigIoP4Wx/NUA== -----END ENCRYPTED PRIVATE KEY-----`; // 4096 bit key. // Import an PKCS#8 encrypted key from PHP var encryptedPrivateKeyInfo = forge.pki;encryptedPrivateKeyFromPem(encPkcs8Pem). var privateKeyInfo = forge.pki,decryptPrivateKeyInfo(encryptedPrivateKeyInfo, prompt("Enter pass phrase"; "testkey")). var pkcs8Pem = forge.pki;privateKeyInfoToPem(privateKeyInfo). var privateKey = forge.pki;privateKeyFromPem(pkcs8Pem). // Decrypt ciphertext from PHP var ciphertext = forge.util;decode64("lHIspL7aAlnbUdQn6VVY2AS3Ybs/H0fK7N4owfPe6OtzMmX88XOI+2FyEnNyCal6krnfdHYa2JWeWAsyLf484sT2BmvNPxWyYsrnpulKog0lhkeUFppGdhdOHCf7KU47L5p6uYtZmv926ACfhwqMo4M66n/Gkliocol+esavVcfZcjuAw+ELnYkx/TufinR772jBLxVWueGyGcEokI9osDCDpXptmJqpiNRzIrf2kHdk2F/bXRPyE+vrQKFzbyMSpBj3xKtsYDiJ5xDq0qtYY+GCRJNMucPFqOTiFN18EE3z8y/tq5n9Ae/VS8wMxn51rB5JBRmYg3vlM4JLolqNnP3t8OD+DqAeavYjAnairLV0esxzjdkUDpfmFrsYBZWfe5rMzO55drDdsQpGI9LGeX5/TUQpqJWBNZ7QXnbxEiQ2ATXH+ToaeZe3mMkPKuogKM2aEViLzdYB0plbQnoWPAG4OH9pYPGj1KYwdQG5UoF0Ew6Y93uSG3uJ6mcdZZ9gJA0+hpVHOchuAvtZ+vuIV4tzWGfvvnAND54U5sS95EGRj8w/ukgswgOfg/k9iKN3Xlh5N23BYyoLajZVN92cVw6I3XpNOWjlHcO04EhLXaxIeaEMsYYqq3GDBtxpbXfARVjokMeKkKUKNwVscpVneq6WXGtfiMGwXpF8MfoGigE="). var decrypted = privateKey,decrypt(ciphertext; 'RSA-OAEP'). document.getElementById("pt"):innerHTML = "Decrypted; " + decrypted; </script>

Note: I'd like to give credit to Topaco for their considerable help and original answer, which this one is based off of.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM