Encrypting data with a public key in Node.js - node.js

I need to encrypt a string using a public key (.pem file), and then sign it using a private key (also a .pem).
I am loading the .pem files fine:
publicCert = fs.readFileSync(publicCertFile).toString();
But after hours of scouring Google, I can't seem to find a way to encrypt data using the public key. In PHP I simply call openssl_public_encrypt(), but I don't see any corresponding function in Node.js or in any modules.

A library is not necessary. Enter crypto.
Here's a janky little module you could use to encrypt/decrypt strings with RSA keys:
var crypto = require("crypto");
var path = require("path");
var fs = require("fs");
var encryptStringWithRsaPublicKey = function(toEncrypt, relativeOrAbsolutePathToPublicKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey);
var publicKey = fs.readFileSync(absolutePath, "utf8");
var buffer = Buffer.from(toEncrypt);
var encrypted = crypto.publicEncrypt(publicKey, buffer);
return encrypted.toString("base64");
};
var decryptStringWithRsaPrivateKey = function(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
var absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey);
var privateKey = fs.readFileSync(absolutePath, "utf8");
var buffer = Buffer.from(toDecrypt, "base64");
var decrypted = crypto.privateDecrypt(privateKey, buffer);
return decrypted.toString("utf8");
};
module.exports = {
encryptStringWithRsaPublicKey: encryptStringWithRsaPublicKey,
decryptStringWithRsaPrivateKey: decryptStringWithRsaPrivateKey
}
I would recommend not using synchronous fs methods where possible, and you could use promises to make this better, but for simple use cases this is the approach that I have seen work and would take.

I tested this in Node.js 10, you can use encrypt/decrypt functions (small changes on Jacob's answer):
const crypto = require('crypto')
const path = require('path')
const fs = require('fs')
function encrypt(toEncrypt, relativeOrAbsolutePathToPublicKey) {
const absolutePath = path.resolve(relativeOrAbsolutePathToPublicKey)
const publicKey = fs.readFileSync(absolutePath, 'utf8')
const buffer = Buffer.from(toEncrypt, 'utf8')
const encrypted = crypto.publicEncrypt(publicKey, buffer)
return encrypted.toString('base64')
}
function decrypt(toDecrypt, relativeOrAbsolutePathtoPrivateKey) {
const absolutePath = path.resolve(relativeOrAbsolutePathtoPrivateKey)
const privateKey = fs.readFileSync(absolutePath, 'utf8')
const buffer = Buffer.from(toDecrypt, 'base64')
const decrypted = crypto.privateDecrypt(
{
key: privateKey.toString(),
passphrase: '',
},
buffer,
)
return decrypted.toString('utf8')
}
const enc = encrypt('hello', `public.pem`)
console.log('enc', enc)
const dec = decrypt(enc, `private.pem`)
console.log('dec', dec)
For the keys you can generate them with
const { writeFileSync } = require('fs')
const { generateKeyPairSync } = require('crypto')
function generateKeys() {
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: '',
},
})
writeFileSync('private.pem', privateKey)
writeFileSync('public.pem', publicKey)
}

The updated public/private decrypt and encryption module is URSA. The node-rsa module is outdated.
This Node module provides a fairly complete set of wrappers for the
RSA public/private key crypto functionality of OpenSSL.
npm install ursa

Use the node-rsa module. Here's a link to the test.js file that demonstrates usage.

TL;DR: URSA is your best bet. It's really funky that this doesn't come standard with Node.js' crypto.
Every other solutions I found either doesn't work in Windows or aren't actually encryption libraries. URSA, recommended by Louie, looks like the best bet. If you don't care about Windows, you're even more golden.
Note on Ursa: I had to install OpenSSL along with something called "Visual C++ 2008 Redistributables" in order to get the npm install to work. Get that junk here: http://slproweb.com/products/Win32OpenSSL.html
The breakdown:
Annoying additional manual installation steps for Windows
https://github.com/Obvious/ursa - probably the best of the lot
Not compatible with Windows
https://npmjs.org/package/rsautl - says BADPLATFORM
https://github.com/katyo/node-rsa - node-waf isn't available on Windows
https://github.com/paspao/simple_rsa_encrypt - unistd.h isn't on windows
https://npmjs.org/package/pripub - large amounts of linker errors, also not on GitHub
Not encryption libraries
https://github.com/substack/secure-peer
https://github.com/substack/rsa-json - just generates keys, but doesn't use them
https://github.com/substack/rsa-unpack - just unpacks PEM strings
This is literally all I could find.

This is not supported natively by Node.js version v0.11.13 or below, but it seems that next version of Node.js (a.k.a v0.12) will support this.
Here is the clue: https://github.com/joyent/node/blob/v0.12/lib/crypto.js#L358
See crypto.publicEncrypt and crypto.privateDecrypt
Here is the future documentation for this
https://github.com/joyent/node/blob/7c0419730b237dbfa0ec4e6fb33a99ff01825a8f/doc/api/crypto.markdown#cryptopublicencryptpublic_key-buffer

Related

How to encrypt a String with a Firebase Cloud Function

I'm trying to encrypt a string inside my Firebase Cloud Function. I would love to use SHA-256 or AES-256 for this. However I didn't find the right approach yet.
exports.myfunction = functions.https.onCall((data, context) => {
const someString = "Hello World!"
const encryptedString = // How could I do this here?
return encryptedString
})
Therefore any help is appreciated! Thanks.
A good choice for this is probably the crypto module. It provides cryptographic functionality that includes a set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify functions.
You can use crypto.createHash(algorithm\[, options\]) to encrypt a string. Check the documentation on this function.
This is the final solution:
// Use `require('crypto')` to access this module.
const crypto = require('crypto');
exports.myfunction = functions.https.onCall((data, context) => {
const secret = 'Hello World!';
const hash = crypto.createHash('sha256')
.update(pwd)
.digest('base64'); // you can also use 'hex'
return hash
})
Also take a look at the official documentation.
Install crypto-js with this command.
npm install crypto-js
Then import it and encode your string.
const AES = require("crypto-js/aes");
const SHA256 = require("crypto-js/sha256");
exports.myfunction = functions.https.onCall((data, context) => {
const someString = "Hello World!";
const encryptedString = SHA256(someString);
// or
const encryptedString = AES(someString);
return encryptedString;
})
You may have a look at the Google Tink Library
A multi-language, cross-platform library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.

Decrypted string is not encoded properly in subsequent Node sessions

I have text of the form crypto.randomBytes(30).toString("hex") that I need encrypted.
Below is the encrypt and decrypt algorithms that I use.
import crypto from "crypto";
const ALGORITHM = "aes-256-ctr";
const IV_LENGTH = 16;
const ENCRYPTION_KEY = crypto.randomBytes(32);
export const encrypt = (text: string) => {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
const encryptedText = cipher.update(text, "utf8", "base64") + cipher.final("base64");
return `${iv.toString("hex")}:${encryptedText}`;
};
export const decrypt = (text: string) => {
const textParts = text.split(":");
const iv = Buffer.from(textParts.shift(), "hex");
const decipher = crypto.createDecipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
const encryptedText = Buffer.from(textParts.join(":"), "base64");
return decipher.update(encryptedText, "base64", "utf8") + decipher.final("utf8");
};
I run node in my terminal and am able to mess around with these functions in my repl-like environment.
When I am within that node session, I see the following:
const encryptedText = encrypt("0e1819ff39ce47ec80488896a16520bc6b8fcd7d55dc918c96c61ff8e426")
// Output: "9fa7486458345eae2b46687a81a9fcf5:LOrlVD06eotggmIPAq0z9yzP/EHoeQyZyK6IiBYKZMIWvWYLekmSe73OjlgXdWJVOrcTyWS/eP3UU2yv"
const decryptedText = decrypt(encryptedText);
// Output: "0e1819ff39ce47ec80488896a16520bc6b8fcd7d55dc918c96c61ff8e426"
Just like I want!
If I exit the node session, and open a new node session and copy and paste to decrypt the same string I get the following:
const decryptedText = decrypt(ENCRYPTED_TEXT_FROM_ABOVE)
// Output: "�Z<�\r����S78V��z|Z\u0013��\u001a}�����#ߩ����Ɣh���*����y\b�\u001d���l'�m�'�"
Why is this happening? What changed? Clearly it seems like the Node no longer knows how to display the characters or something. I don't know what encoding it is now.
I came across this because I store the encrypted data in Postgres and upon retrieving it, I sometimes need to decrypt it. For some reason, when I restart the node session it forgets how to read it.
The interesting thing is I can decrypt(encrypt("another string")) => "another string" in the new node terminal and it'll work, but the original string no longer does.
The decryption step is failing here since you are generating a new key for each session in the line:
const ENCRYPTION_KEY = crypto.randomBytes(32);
If you log the key like so:
console.log( { key: ENCRYPTION_KEY.toString("hex") });
You'll see the key is different for each run. So it makes sense that we fail to decrypt the encrypted data from a previous session!
If you change to using a fixed key:
const ENCRYPTION_KEY = Buffer.from("8b3d2068cf410479451eef41fe07d43e62ec80b962ae30cd99f7698499acfd61", "hex");
The output from each session should be decrypted in the next one.
Of course we won't want to leave keys in code, so it would be best to use an environment variable for this purpose.

Encrypt data on angular decrypt on Nodejs

What's the way of doing this? I tried using CryptoJS on angular and Crypto Module on node, without success I keep getting description error
Angular encrypt method :
_rsaEnc(p) {
var e = new JSEncrypt();
const key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/kaskaskKAS';
e.setPublicKey(key);
return e.encrypt(p);
}
Node decrypt method
privK = {
key: fs.readFileSync('./app/services/private.pem').toString(),
passphrase: 'xxxxxx'
};
var buf = Buffer.from(base64Data, 'base64');
origData = crypto.privateDecrypt(privK, buf);
return origData.toString('utf-8');
error:
Error: error:040A1079:rsa
routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error
Ended up changing the angular lib to jsencrypt, CryptoJS doesnt support RSA, and changed node lib to node-rsa to set the encryption scheme to pkcs1 with
myDecrypter.setOptions({encryptionScheme: 'pkcs1'});

NodeJS | Crypto Encryption is not producing correct results

I have a tricky problem to resolve. Not sure how to explain it correctly but will try my best. So here is what I am trying to do: I am trying to use a 3rd Party API, which wants me to encrypt a value and submits it. I successfully achieved it through C# code using the following block:
public string Encrypt(byte[] dataToEncrypt, byte[] keyBytes)
{
AesManaged tdes = new AesManaged();
tdes.KeySize = 256;
tdes.BlockSize = 128;
tdes.Key = keyBytes;
tdes.Mode = CipherMode.ECB;
tdes.Padding = PaddingMode.PKCS7;
ICryptoTransform crypt = tdes.CreateEncryptor();
byte[] cipher = crypt.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
tdes.Clear();
return Convert.ToBase64String(cipher, 0, cipher.Length);
}
Now, I am trying to achieve the same in Node. I wrote the following function.
encrypt(buffer){
var buffbytes = new Buffer('my app key goes here to be used as password','utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), buffbytes);
},
encrypt_key(buffer, keybytes){
var cipher = crypto.createCipher('aes-128-ecb',keybytes);
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
return crypted;
},
This encryption code works fine. It encrypts it fine, but it doesn't encrypt it similar to what c# code does. When I take the encrypted text from C# code, and inject the encrypted result into the API call, it passes through fine, but when I use my encrypted result into the API call, it fails mentioning that the format of my key is incorrect.
I would like to know if these code blocks are same or not. I assume it is same, because both code using 128 bit AES, ECB Cipher and default padding for Crypto Node module is PKCS5 which is same as PKCS7 for 128 bit encryption. Please Help!
Edit: 9/19/2017
Fixed as per #smarx solution:
encrypt(buffer){
var buffbytes = new Buffer(helper.Constants.AppKey,'utf8'); //converts the app key into buffer stream
return this.encrypt_key(new Buffer(buffer,'utf8'), helper.Constants.AppKey);
},
encrypt_key(buffer, key){
var cipher = crypto.createCipheriv('aes-256-ecb',key,new Buffer(0));
var crypted = cipher.update(buffer,'utf8','base64');
crypted = crypted+ cipher.final('base64');
console.log('printed: ', crypted);
return crypted;
},
In your Node.js code, you're using the wrong cipher algorithm. Use aes-256-ecb, since you're using a 256-bit key. Also, be sure to use createCipheriv, since createCipher expects a password from which it derives an encryption key.
One-line fix:
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
The below two programs produce identical output (Q9VZ73VKhW8ZvdcBzm05mw==).
C#:
var key = System.Text.Encoding.UTF8.GetBytes("abcdefghijklmnopqrstuvwxyz123456");
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var aes = new AesManaged {
Key = key,
Mode = CipherMode.ECB,
};
Console.WriteLine(Convert.ToBase64String(
aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length)));
Node.js:
const crypto = require('crypto');
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const data = 'Hello, World!';
const cipher = crypto.createCipheriv('aes-256-ecb', key, new Buffer(0));
console.log(cipher.update(data, 'utf-8', 'base64') + cipher.final('base64'));

Encrypting file in Node via Crypto and Stream

I want to read from a stream then encrypt it and finally write it to another file.
This is my code:
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.read(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
encrypt.end();
outfile.close();
});
But it returns an empty file, and encrypted is null. What is the problem? I don't want to use pipe .
You really want to use Cipher#update and Cipher#final instead of Stream#read, because the function signature is read([size]) and data is not a size.
var fs = require('fs');
var crypto = require('crypto');
var infile = fs.createReadStream('a.dmg');
var outfile = fs.createWriteStream('b.dmg');
var encrypt = crypto.createCipher('aes192', 'behdad');
var size = fs.statSync('a.dmg').size;
console.log(size);
infile.on('data',function(data) {
var percentage = parseInt(infile.bytesRead) / parseInt(size);
console.log(percentage * 100);
var encrypted = encrypt.update(data);
console.log(encrypted);
if(encrypted){
console.log(encrypted);
outfile.write(encrypted);
}
});
infile.on('close', function() {
outfile.write(encrypt.final());
outfile.close();
});
Since crypto.createCipher is deprecated now. You should use crypto.createCipheriv where you provide a key and IV. That means that you should stretch the password that you use with PBKDF2 or similar to get a key and generate a random IV to get semantic security. Since the salt for PBKDF2 and the IV are not supposed to be secret, they can be written in front of the ciphertext. Since they have always the same length (salt is usually 8-16 bytes and IV always 16 bytes for AES-CBC), you know how many bytes you have to read in order to get those values back. Keep in mind that the decryption code has to have proper error handling.

Resources