'Getting the same result from two packages (jsencrypt and node-rsa)
We were using jsencrypt on the client-side of an application to encrypt a string (a password) and pass it over to the backend that handles 3rd party API authorisation.
Like so:
const n = "_key"
export const encrypt2 = async(message: string): Promise < string > => {
const JSEncrypt = (await
import ('jsencrypt')).default;
const jsencrypt = new JSEncrypt({
default_key_size: '1024',
default_public_exponent: "010001",
log: false,
});
jsencrypt.setPublicKey(n);
return jsencrypt.encrypt(message) || message;
};
Due to product changes, we now want the encryption on the backend (nodejs). After research we found node-rsa that can have the same result. Used the code below:
export const encrypt = async(message: string): Promise < string > => {
return new NodeRSA().generateKeyPair(1024, '010001').importKey(n, 'public').encrypt(message).toString('base64')
};
given that n is still the same, our 3rd party auth service rejects this implementation.
Question & Goal:
How can I get a valid encrypted password from both of these implementations?
P.S, I was unable to use jsencrypt on the server side due to window usage
Solution 1:[1]
JSEncrypt only supports PKCS#1 v1.5 padding (see here), while Node-RSA supports PKCS#1 v1.5 padding and OAEP, where OAEP is the default (see here). The different paddings are the reason why both codes are not compatible.
In order for PKCS#1 v1.5 padding to be used in the Node-RSA code, this must be explicitly specified, e.g. in the 3rd parameter when importing the key:
const NodeRSA = require('node-rsa');
var x509 = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunF5aDa6HCfLMMI/MZLT
5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1E
bYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQw
KtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1x
H9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4
OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4i
GwIDAQAB
-----END PUBLIC KEY-----`;
var message = "The quick brown fox jumps over the lazy dog";
var privateKey = new NodeRSA(x509, 'public', { encryptionScheme: 'pkcs1' }); // specify PKCS#1 v1.5 padding
var ciphertext = privateKey.encrypt(message).toString('base64');
console.log("Ciphertext (Node-RSA): ", ciphertext);
A possible output is:
Ciphertext (Node-RSA): Lcfy7G0RYhDRCeWAvUXji4b4uFzrhpSsS9G158QcBiRy+Uae5UctQoA6MMgzZsjVWmf4Jlj50eZYTwxP94mGrTY0gMy9+FvFlMXDu0cMJ04qArOU4B+ZHnt3FElDjYvW0fnvbstYJFWd9OQjlLJc2mxSGPKEj0IQi3p8GQXK9qYfJx7MeONA+nfHdWYQgplicnz1crk+aiGf0zlJMAKdOoTodAtQuqVh9bNYyJcpPnzIt4eqUVW+wLEoXNqu14ibz1u5yMpCNX/+jBKug2fYaXv4ImsUnhqFEAok9sHDGVzZkihnYdDxnWaSocpG3db6UNhpGBoJSvwxdEuI86YgSQ==
Test:
Since both OAEP and PKCS#1v1.5 padding are not deterministic, testing by comparing the generated ciphertexts is not possible. Instead, it can be checked whether the Node-RSA ciphertext can be decrypted with that JSEncrypt code which can also decrypt the JSEncrypt ciphertext. If this is the case, the encryption codes are functionally identical:
var x509 = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunF5aDa6HCfLMMI/MZLT
5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1E
bYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQw
KtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1x
H9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4
OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4i
GwIDAQAB
-----END PUBLIC KEY-----`;
var pkcs8 = `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8sw
wj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqW
rbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4O
F2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtL
zd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOW
uL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEq
kXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1
c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODw
czdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfy
qmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBX
mNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyF
EM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3o
bhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884Jo
RS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDs
jRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCD
JjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9
/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3
qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpM
IvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltaya
j3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFal
CbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdH
aj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIA
X4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTG
Pr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6
ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLA
BviZm5AFCQWfke4LZo5mOS10
-----END PRIVATE KEY-----`;
var message = "The quick brown fox jumps over the lazy dog";
var publicKey = new JSEncrypt();
publicKey.setPublicKey(x509);
var ciphertext = publicKey.encrypt(message);
console.log("Ciphertext (JSEncrypt): ", ciphertext);
var privateKey = new JSEncrypt();
privateKey.setPrivateKey(pkcs8);
var decrypted = privateKey.decrypt(ciphertext);
console.log("Decrypted (JSEncrypt): ", decrypted);
var ciphertextFromNodeRSA = `Lcfy7G0RYhDRCeWAvUXji4b4uFzrhpSsS9G158QcBiRy+Uae5UctQoA6MMgzZsjVWmf4Jlj50eZYTwxP94mGrTY0gMy9+FvFlMXDu0cMJ04qArOU4B+ZHnt3FElDjYvW0fnvbstYJFWd9OQjlLJc2mxSGPKEj0IQi3p8GQXK9qYfJx7MeONA+nfHdWYQgplicnz1crk+aiGf0zlJMAKdOoTodAtQuqVh9bNYyJcpPnzIt4eqUVW+wLEoXNqu14ibz1u5yMpCNX/+jBKug2fYaXv4ImsUnhqFEAok9sHDGVzZkihnYdDxnWaSocpG3db6UNhpGBoJSvwxdEuI86YgSQ==`;
decrypted = privateKey.decrypt(ciphertextFromNodeRSA);
console.log("Decrypted (Node-RSA): ", decrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/3.2.1/jsencrypt.min.js"></script>
A possible output:
Ciphertext (JSEncrypt): WWcnfnlAB7Quf/jMQIyD9AgOpewJQdPgA/UQFqIu9PvoMhMUgjo+4LnxeKjnFGqhG9mgctqmVKW1EGjEsnJiThbgiC6VNIwufYloXgJcqTrT78p7UiTO9+k7z1H8iCy4HkgXtiOkoBpGI094zr/iMnTyiTMfMKjopygM969pgDPsaXqWeiE2MX4ZjxFcNP9QpYoZpdS7PGxDBj31kbXvttOB/1Oc57IqOepF2N8n0jp9KzIBnAf+CDjzUFbUh8t23feuktoL/o/MYqqySmY/hkvkkmtNd4LDLtTA0NjcU7Es99ue90xNcuYKCEybzaZ20pp769PQjZHu/lU+T7zlrg==
Decrypted (JSEncrypt): The quick brown fox jumps over the lazy dog
Decrypted (Node-RSA): The quick brown fox jumps over the lazy dog
proving that both encryption codes are functionally equivalent.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Topaco |
