I know this should better be done with ECDH+AES, but I want to make things simplier as it is a hobby project.
I want to generate a key pair with Node and then give the public key to the client so that he can encrypt his login credentials. I want to be able to decrypt it using my private key in Node JS script.
I have the following client code:
const publicKeyHash = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx+6mliYZnAmJid8/u+SdiMdjB5BlNub4AsObrSG0v1AkmiiLL7N0WzN0+alClyXIOm12qQtYdHDaNkTr1xiO7ZYKH92y9S4g4PI5IIx6U7BNardEnvAS8YMh3HfuC/wMkkLiwuy/QvMPXL+Dp3eNzESJ69rMKkaEf5N2qZ39DbzA3//5PU7UlmhdjmMAr7h30cr5433mMR/NnuMJ+ZYWbZnlaeJKZFjWO2EkuzN2cheaIVxgi46pfdLnZfdxQE8TARgenw/5KdkVzaIk0y+eShEs+YA7hLYTI3iohthY0hgMv1+dd3TYzp/Nc3yqsyngXeQxa2pi4xTlmkXEiLtBeQIDAQAB';
const str2ab = str => {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
const generateKey = async () => await window.crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: { name: "SHA-256" }
},
true,
["encrypt", "decrypt"]
);
const importKey = async (pem, opt) => {
const binaryDerString = window.atob(pem);
const binaryDer = str2ab(binaryDerString);
return await window.crypto.subtle.importKey(
"spki",
binaryDer,
{
name: "RSA-OAEP",
hash: "SHA-256"
},
true,
opt
);
}
const encryptMessage = async (key, msg) => {
return await window.crypto.subtle.encrypt(
{
name: "RSA-OAEP"
},
key,
str2ab(msg)
)
}
const decryptMessage = async (key, msg) => {
return await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP"
},
key,
msg
);
}
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)
}
const test = async _ => {
try{
let publicKey = await importKey(publicKeyHash, ["encrypt"]);
let msg = "Admin:Password";
console.log("исходный текст для шифрования:", msg);
let msgEncrypted = await encryptMessage(publicKey, msg);
console.log(arrayBufferToBase64String(msgEncrypted));
}
catch(e){
console.log(e)
}
}
test()
and the NodeJS code:
const crypto = require("crypto");
/*
crypto.generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
}, (err, publicKey, privateKey) => {
console.log(publicKey);
console.log(privateKey);
});
*/
let privateKey = crypto.createPrivateKey('-----BEGIN PRIVATE KEY-----\
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDH7qaWJhmcCYmJ3z+75J2Ix2MHkGU25vgCw5utIbS/UCSaKIsvs3RbM3T5qUKXJcg6bXapC1h0cNo2ROvXGI7tlgof3bL1LiDg8jkgjHpTsE1qt0Se8BLxgyHcd+4L/AySQuLC7L9C8w9cv4Ond43MRInr2swqRoR/k3apnf0NvMDf//k9TtSWaF2OYwCvuHfRyvnjfeYxH82e4wn5lhZtmeVp4kpkWNY7YSS7M3ZyF5ohXGCLjql90udl93FATxMBGB6fD/kp2RXNoiTTL55KESz5gDuEthMjeKiG2FjSGAy/X513dNjOn81zfKqzKeBd5DFramLjFOWaRcSIu0F5AgMBAAECggEBAIARQuxTI3xsL4n9U1oMK0z+S1UBDZSJhrxeiE3ylVEnCQsmhWHc1d/FqlxyK5GeRhAHJkdKWTLdRyoUI+34cTWHMp0O6h9bmPv+rNFIquMIv85n7rDQn5HMqdgLipRqs7LM3Wx3Mly0TVbf5nlTf6UlEiPeV4GNAjqmPxCPfxVYe4jVncxYtGEpbid5gs5VU1kat411vmYNUZW7RjH1GYdI1nkuxjiNdMK3QvWGFj1ABkHGXpAyCG97w9zIdeCytBLPZ7HIF+0wIE+6DAu9KdG3uKbAf5td2FQiVsk9gBp+oG+0vfs/jo9f7Xq4TopMPTWOQuRFWGNu8O+dZfKS4DECgYEA/Z7pHL9CVkL9NcsXwHgnSEYMr5bwwFdeQ0T2zzEg9nAFZNOnAkLMmhC1jWr9ozT9esC4cpDmYRamd9HuEATIQi/ETvrbeERUYZAq55cEVxvZJwvGtOlKaW3bH7k+O+kdl07jvuGWsnqH3QSg/ISttwyc99RT9XjbrYKEex2E518CgYEAyc7NmCgltgCxPUWHJSRrDMWKhnM0pwju1vMgk5a/8Kqnwv7X+7aWnkXFMWu3/D8jIF3bFEKDpC7ZlAq0WwFfBtaGs/9WRtvbzP/cM5CPWeu9XOW9x7Yx2rn3IocU0uDvwcYedcVHDRa05nC5t9+6lcGC++kLkZWY+2mzJ2ptPicCgYEA1NYE3uEKdIWnJPuYpSawIJDYmIpc46zuKBm53cpm1SjQ/fo4j0crmKcpFNKSo+IWTmto3owHKbbuYGNGGx9IR3L6lSUkBNuizHVF7C/prohqRcA2MyAMGEnet9KnDXPmJ1JHAasi4gi995ao2wElHxZwq/H9u2R/Ri7fqsns/JUCgYA4RWPqg3dQcoz5SsPORYNcAlEIAGO0F7eRVCXHQYevscYphynuVBFXfJjpmOGqgmhnBASsd4eBZlbhAsMbhRxcKcFRu2bxRyjY1pcuAacXKbaZiq4KW/E2zhftFsFls7bmzzl7GVEggy5Z+yCt9sJuM5E0cNz68T3BDM4ZtYFUjQKBgQCvFU70gdcWhM4uYmSe3L4VBxdS53SaVIdtGCVKNCK5ZaJPqj+g5vL9KMpq9cQ4rl/rgGqo45WKNACvS3Q42+bMLyjUmMPyU9lLScp49RasXmK3sw/2CmQAsky793Zcf9MYIp1f11rcJBTlKrz3ZvBkjYeYpVAtWraB6sejZmPJGg==\
-----END PRIVATE KEY-----');
let data = 'PjEhLCjoPLc3ZXFE1VNga00KHFif+wtNfjIGCck/4SeLn3woDBhHP4cJCRdyqPajLLtUVMWUDZpEXfAh4Ch5hAB3+qI6mo+6n9NDBHv6/jH+sF7rl5XQCcbZyVOSIWtmzPr//eIWNUheg0YXweX9mgC9KeUpXvtIAY7LT5tnvaZpYJx1zyX3YP/4eIisjVMJbhuM1K8sakvAXparOwWg/TpCDPiFN68OOuWTgBju4tjbSy8iLKy5RecxhvuI1M6/4i2Cx3FWxqcNPgIgcjxXTzAPjYz3cqqDqQ4jORsHoJ9yOovWtNpP8xF4QwNj5n4Ci5mRmfs2g2Zs4stGvx1CEg==';
const decryptedData = crypto.privateDecrypt(
privateKey,
Buffer.from(data, 'base64')
);
console.log(decryptedData.toString());
For some reason I have the following error:
Error: error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error at Object.privateDecrypt (node:internal/crypto/cipher:79:12) at Object.<anonymous> (/home/runner/Generate-Key-Pair/index.js:27:30) at Module._compile (node:internal/modules/cjs/loader:1101:14)
If I try to decrypt with no padding (I guess it is even theoretically wrong), it gives me no errors but the result is not correct:
const decryptedData = crypto.privateDecrypt({
key: privateKey,
passphrase: '',
padding: crypto.constants.RSA_NO_PADDING
},
Buffer.from(data, 'base64')
);
console.log(Buffer.from(decryptedData));
I'm sure that 'RSA-OAEP' should work in NodeJS. As my keys are generated with Node too, I guess the problem is with the encryption in browser. But this 'RSA-OAEP' mode is the only one supported for encrypt/decrypt (the second one is for sign/verify).
What am I doing wrong?
In the NodeJS code, SHA-1 is used for the OAEP digests by default. Since you apply SHA-256 in the WebCrypto code, you must explicitly specify SHA-256 in the NodeJS code using oaepHash
.
Modify your NodeJS code as follows:
...
const decryptedData = crypto.privateDecrypt(
{
key: privateKey,
oaepHash: 'SHA256'
},
Buffer.from(data, 'base64')
);
...
With this change decryption with the NodeJS code works.