I'm trying to implement basic asymmetric encryption; one service has a public key and encrypts a value with that public key and then another service receives the encrypted message, decodes it using the private key, and does something with the decrypted data.
The problem that I'm having is that every time I use the in-built crypto.publicEncrypt method, I get a different encrypted value returned. As far as I can tell, I'm using the same inputs, so as I understand it I should be seeing the same output. Perhaps I have misunderstood this?
Here is my encryption utility;
import { createPublicKey, createPrivateKey, privateDecrypt, publicEncrypt, constants } from "crypto";
const privateKeyPem = process.env.ENCRYPTION_PRIVATE_KEY;
const privateKeyPemFixed = privateKeyPem.replace(/\\n/g, "\n");
const privateKey = createPrivateKey(privateKeyPemFixed);
const publicKey = createPublicKey(privateKey);
// const private1 = privateKey.export({
// type: 'pkcs1',
// format: 'pem',
// }).toString("base64");
// const public1 = publicKey.export({
// type: 'pkcs1',
// format: 'pem',
// }).toString("base64");
export const encrypt = (text: string): string => {
const buffer = Buffer.from(text);
const encrypted1 = publicEncrypt( {
key: publicKey,
oaepHash: 'sha256',
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, buffer);
const encrypted2 = publicEncrypt({
key: publicKey,
oaepHash: 'sha256',
padding: constants.RSA_PKCS1_OAEP_PADDING,
}, buffer);
console.log(encrypted1.toString("base64"));
console.log(encrypted2.toString("base64"));
return encrypted1.toString("base64");
}
export const decrypt = (cipher: string): string => {
const buffer = Buffer.from(cipher);
const decrypted = privateDecrypt(privateKey, buffer);
return decrypted.toString("utf8");
}
I have a jest test which looks like this;
import { encrypt } from "./encryption";
describe("encryption", () => {
const helloWorld = "Hello world";
const encryptedHelloWorld = "IIisobkVsZxKiR0e5nwyIHjsww/ebrKXI0hzDbdTdC8KMU2rc57IRX9krhVThVma2no7gZcMvbfwJsRjHz1s7NoBiT+BitgYlI/LE1jMpFd5Bmghy2S93F/wGFRWA4DMAqdw32I9s8CRKVvellxkh3ZlJ5NyzxWG8kVfc11CrEMD+1sqo2e9cFCcTdx5jEVYpCgITy7X2vDxUwOPQ7bK8K56kU5ivQhUfyoHjd9VclRUxfBaSzOwLJQqK6RJPbNwuUfILcCaR72GTf4zWMhQqIvs/zHhSu+S9QQYPVvmZ1SzqqJaCM9mM6Cvl8Gn2brwcMB003f0CFb8WFimOgM6lQ==";
it("should encrypt text", () => {
const received = encrypt(helloWorld);
expect(received).toEqual(encryptedHelloWorld);
});
});
However it constantly fails as the result always seems to be different.
I ran the encryption process twice in the encrypt
function, to demonstrate the problem; the two values which it logs out are completely different and I don't understand why.
console.log
aDWDWcE+Zs92/rp2DLJN8UTgwHPTg6TDqFPIrC3ODVIfZgo5uaQV0NTSESPPPAGHhHeKiWB8JFnVewJaEN7iz9StzRepaL3+DFpD/CvhA8L7o8CQ5CTeScqL9HedVkM7O4MziMHkTJy0Li7EjP/6xdp8Caw+m6EsqvQ9Yd3qN4OTwrsMWmItLIaAHmkB/4UPhMqVnddVnwBUVb7toJ5rvGc/uktZkZPuHdzJRI0XSW//ltHHFCi3zneoJ92v/myYZOtWTyBDTmrgUtzC5fHbsSVdnD9IyWTRf72fz1Hjf2z8xFdFsdugo/+0qzOwE77K4BkgukeIDwhAxmdIr5yo4w==
at encrypt (utils/encryption.ts:33:11)
console.log
LROC3KIjXJVoQVawJYZUYqT7rhXC8enb6O9ipY9VnOFMilFM00NHGiF3FHJQLWqac5zWFFZg2ofygANqT7Y5rQRtePcUEM5bLEUHvMaDdOAEXSdOK4PTbiCqZCAIPd79VVsW9gk2+vhKHbsq78AXhycCgUiOVjv25ooluDvqj3CQ+sTR+5cbatYO5kpXWwpu/BmPlRZYwsLUldpCuUPAYbkItKmQmiq/FWw1+z9Vx8mMKYhPtLuSTxnRrJ2Hn1eQm2EkuEeWQAEp+TJYaBsi93NalqmcWDo5swNe5HFPUH4hV7xtMtTZv82Wu9uNJ+ADUTD1B2mKDzKr0M0yNEYcGA==
at encrypt (utils/encryption.ts:34:11)
At first I wondered if there was a problem with my multiline private key in my .env
, but I can export my private and public keys (see commented out code) and when I log them out, they look as I would expect, which I think means the keyObjects
are being created successfully. If the keys were not created successfully, maybe it would create new keys each time and that would cause this failure? But as far as I can tell, they are created successfully.
I also read this answer which suggested that there might be a problem with the OpenSSL implementation on MacOS - I'm on MacOs Big Sur, Node 14.16.0 (LTS). So, I brew install openssl
and then linked it, and now I can see that I am using OpenSSL rather than LibreSSL by checking like so;
➜ website git:(master) ✗ openssl version
OpenSSL 1.1.1j 16 Feb 2021
However that doesn't seem to have made a difference.
So, what can I do to make the encrypt function reliably return the same output, given the same input?
EDIT
I've updated my encryption util to the following and accepted that the result of the encryption will be different because it is encrypted with a unique session key as well as the public key, however all the output values decrypt correctly with the private key.
import { createPublicKey, createPrivateKey, privateDecrypt, publicEncrypt } from "crypto";
const privateKeyPem = process.env.ENCRYPTION_PRIVATE_KEY;
const privateKeyPemFixed = privateKeyPem.replace(/\\n/g, "\n");
const privateKey = createPrivateKey(privateKeyPemFixed);
const publicKey = createPublicKey(privateKey);
export const encrypt = (text: string): string => {
const buffer = Buffer.from(text, "utf8");
const encrypted = publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
}
export const decrypt = (cipher: string): string => {
const buffer = Buffer.from(cipher, "base64");
const decrypted = privateDecrypt(privateKey, buffer);
return decrypted.toString("utf8");
}