57

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Clint
  • 2,871
  • 2
  • 25
  • 28

6 Answers6

154

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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jacob McKay
  • 2,776
  • 1
  • 19
  • 20
  • 9
    Worth mentioning that you can only encrypt small amounts (up to 245 bytes) of data with public key and for larger amounts you should use AES256 with the private key encrypted using a public-private pair. See this answer: https://security.stackexchange.com/questions/33434/rsa-maximum-bytes-to-encrypt-comparison-to-aes-in-terms-of-security – jmc May 17 '17 at 08:35
  • "crypto.publicEncrypt" doesn't work in node.js, same as a lot of functions from 'crypto' – croraf Oct 23 '17 at 17:29
  • 1
    Amazing :D Just one more thing, the key file should be in .pem format: ```openssl rsa -in ~/.ssh/id_rsa -outform pem > id_rsa.pem``` – Bodhi Hu Apr 26 '18 at 03:22
  • what if I have the key in a .der file? – Sourav Prem Jun 05 '18 at 14:09
  • 1
    And what if the privateKey is enocder and encrypted using a passphrase? I can't find the right method call of crypto.privateDecrypt() – maroodb Jan 07 '19 at 16:21
  • @maroodb generate your public and private keys using openssl keys tool generator ( https://sourceforge.net/p/gnuwin32/ ) and run these commands ( https://rietta.com/blog/2012/01/27/openssl-generating-rsa-key-from-command/ ) to create a pair of public.pem and private.pem files and pass these files to the crypto.privateDecrypt() function. – Prabhat Mishra Mar 03 '19 at 09:12
  • @Prabhat Mishra I am not asking about how to generate keys ;) – maroodb Mar 05 '19 at 08:14
  • Please any one will help if u want to achieve using crypto. I am able to encrypt using ```crypto.publicEncrypt(key, buffer)``` but for the decrypting the output using ```crypto.publicDecrypt(key, buffer)``` is not working. – Abhishek Kumar Sep 04 '19 at 12:01
  • @AbhishekKumar just looking at your code it looks like you could be using the same key for encryption and decryption. Check that you are encrypting with publicKey and decrypting with privateKey – Jacob McKay Sep 05 '19 at 16:06
  • Yes @jacobmckay you are right. I am using the same public key – Abhishek Kumar Sep 06 '19 at 17:55
  • Your example uses RSA, which [security experts argue against using anymore](https://blog.trailofbits.com/2019/07/08/fuck-rsa/). – Scott Arciszewski Oct 22 '19 at 05:12
  • @maroodb: You can call `privateDecrypt` like this: `var decrypted = crypto.privateDecrypt({key: privateKey.toString(), passphrase: 'top secret'}, buffer);` – Atul Aug 08 '22 at 18:23
34

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)
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BrunoLM
  • 97,872
  • 84
  • 296
  • 452
  • It took me days but I finally found an explaination why this would not work in Russian so I thought to add that here http://qaru.site/questions/16043509/nodejs-cryptopublicencrypt-error-error0906d06cpem-routinespemreadbiono-start-line it states publicEncrypt uses openssl and it's RSA only so NO using crypto.createECDH secp384r1 will cause an error of "unhandledRejection Error: error:0906D06C:PEM routines:PEM_read_bio:no start line" in publicEncrypt any advice how to use higher bit Elliptic Keys? also noted Chrome won't go 512 with EC either. This is as of nodejs 11.6 nodejs.org/api/crypto.html – Master James Jan 18 '19 at 08:32
  • Also when trying to get something encrypted to the browser from node the keys need to be generated in the browser with the public key being imported in node. – Master James Jan 28 '19 at 08:42
  • thanks for this answer, this taught me how to use JSON object to pass "passphrase" to the privateDecrypt function. – kaushalop Jul 28 '20 at 05:47
8

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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Louie Miranda
  • 1,071
  • 1
  • 20
  • 36
  • 4
    Ursa hasn't been maintained for quite a while. These newer implementations might help: https://github.com/tracker1/cryptico-js and https://github.com/rzcoder/node-rsa – Andrew Eddie Aug 20 '14 at 01:34
7

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • 1
    Maybe I need to get more familiar with RSA encryption. I read the docs on crypto a dozen times trying to see how to do what I needed, but I didn't find anything. You are saying createCipheriv() will do what I need, but I don't even know what "iv" is. I guess it is because it is more abstracted in PHP and other languages. I'll play around with that function and see if I can get it to work. – Clint Jan 06 '12 at 07:15
  • 1
    After reading more about createCipheriv it looks like it isn't asymmetric encryption (public/private key encryption). I don't think it will fill my needs. Crypto does have the ability to sign an encrypted string with a private key, which makes me wonder why I can't encrypt using a public key. Seems strange, or else I am missing something entirely. – Clint Jan 06 '12 at 07:25
  • iv is an initialization vectior. http://en.wikipedia.org/wiki/Initialization_vector – Peter Lyons Jan 06 '12 at 14:43
  • I didn't see that you edited your answer. That actually looks like it might do the job! I will test it and see. – Clint Jan 11 '12 at 18:31
5

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:

Not encryption libraries

This is literally all I could find.

B T
  • 57,525
  • 34
  • 189
  • 207
3

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Etienne
  • 16,249
  • 3
  • 26
  • 31