58

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.

6 Answers 6

158

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.

Sign up to request clarification or add additional context in comments.

12 Comments

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: security.stackexchange.com/questions/33434/…
"crypto.publicEncrypt" doesn't work in node.js, same as a lot of functions from 'crypto'
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
what if I have the key in a .der file?
And what if the privateKey is enocder and encrypted using a passphrase? I can't find the right method call of crypto.privateDecrypt()
|
35

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)
}

3 Comments

It took me days but I finally found an explaination why this would not work in Russian so I thought to add that here qaru.site/questions/16043509/… 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
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.
thanks for this answer, this taught me how to use JSON object to pass "passphrase" to the privateDecrypt function.
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

1 Comment

Ursa hasn't been maintained for quite a while. These newer implementations might help: github.com/tracker1/cryptico-js and github.com/rzcoder/node-rsa
7

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

4 Comments

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.
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.
iv is an initialization vectior. en.wikipedia.org/wiki/Initialization_vector
I didn't see that you edited your answer. That actually looks like it might do the job! I will test it and see.
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.

1 Comment

node-rsa do not rely on node-waf anymore. It's browser compatible.
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

1 Comment

Time for an update (without "Update:", "Edit:". etc.)?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.