66
votes

I have to generate two keys (private and public) to encrypt a text with the public and let the user with the private key decrypt the text.

Is it possible with the module Crypto?

9
I'm not sure how you're going to give the users their private keys securely. I would be better if they generated their key-pairs locally and gave you their public keys.Bruno
@Bruno yes, no problem about it... my goal is to understand how to create a pair of keys(private,public) with Node.js, is this possible?Dail

9 Answers

69
votes

nodejs v10.12 now supports this natively with crypto.generateKeyPair

const { generateKeyPair } = require('crypto');
generateKeyPair('rsa', {
  modulusLength: 4096,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase: 'top secret'
  }
}, (err, publicKey, privateKey) => {
  // Handle errors and use the generated key pair.
});
25
votes

Use the crypto module from npm to generate KeyPair.

var crypto = require('crypto');

var prime_length = 60;
var diffHell = crypto.createDiffieHellman(prime_length);

diffHell.generateKeys('base64');
console.log("Public Key : " ,diffHell.getPublicKey('base64'));
console.log("Private Key : " ,diffHell.getPrivateKey('base64'));

console.log("Public Key : " ,diffHell.getPublicKey('hex'));
console.log("Private Key : " ,diffHell.getPrivateKey('hex'));

Above is a example snippet. To know more checkout documentation http://nodejs.org/api/crypto.html

17
votes

The following code works, but I'm not a professional cryptographer, so some comments here would be useful.

I've used the ursa RSA module, instead of crypto.

I am concerned that if similar data were encrypted directly, without a pass of AES or similar, then it might be trivial to break this. Comments please...

var ursa = require('ursa');
var fs = require('fs');

// create a pair of keys (a private key contains both keys...)
var keys = ursa.generatePrivateKey();
console.log('keys:', keys);

// reconstitute the private key from a base64 encoding
var privPem = keys.toPrivatePem('base64');
console.log('privPem:', privPem);

var priv = ursa.createPrivateKey(privPem, '', 'base64');

// make a public key, to be used for encryption
var pubPem = keys.toPublicPem('base64');
console.log('pubPem:', pubPem);

var pub = ursa.createPublicKey(pubPem, 'base64');

// encrypt, with the public key, then decrypt with the private
var data = new Buffer('hello world');
console.log('data:', data);

var enc = pub.encrypt(data);
console.log('enc:', enc);

var unenc = priv.decrypt(enc);
console.log('unenc:', unenc);

After some further investigation http://en.wikipedia.org/w/index.php?title=RSA_%28cryptosystem%29&section=12#Attacks_against_plain_RSA it looks like ursa already does padding.

12
votes

If you know how to get what you want from OpenSSL, I think it's perfectly reasonable to run OpenSSL using Node's child_process.

var cp = require('child_process')
  , assert = require('assert')
  ;

var privateKey, publicKey;
publicKey = '';
cp.exec('openssl genrsa 2048', function(err, stdout, stderr) {
  assert.ok(!err);
  privateKey = stdout;
  console.log(privateKey);
  makepub = cp.spawn('openssl', ['rsa', '-pubout']);
  makepub.on('exit', function(code) {
    assert.equal(code, 0); 
    console.log(publicKey);
  });
  makepub.stdout.on('data', function(data) {
    publicKey += data;
  });
  makepub.stdout.setEncoding('ascii');
  makepub.stdin.write(privateKey);
  makepub.stdin.end();  
});
9
votes
const crypto = require('crypto');

  const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem'
    }
  }); 
5
votes

I dont know if this helps but I also was looking to do something along these lines. Here is what I came up with :

As mentioned in the answer by Nelson Owalo you can use the crypto library, as follows :

//import the methods
const { generateKeyPair, createSign, createVerify } = require("crypto");
//generate the key pair
generateKeyPair(
  "rsa",
  {
    modulusLength: 2048, // It holds a number. It is the key size in bits and is applicable for RSA, and DSA algorithm only.
    publicKeyEncoding: {
      type: "pkcs1", //Note the type is pkcs1 not spki
      format: "pem",
    },
    privateKeyEncoding: {
      type: "pkcs1", //Note again the type is set to pkcs1
      format: "pem",
      //cipher: "aes-256-cbc", //Optional
      //passphrase: "", //Optional
    },
  },
  (err, publicKey, privateKey) => {
    // Handle errors and use the generated key pair.
    if (err) console.log("Error!", err);
    console.log({
      publicKey,
      privateKey,
    });//Print the keys to the console or save them to a file.
    /*
    * At this point you will have to pem files, 
    * the public key which will start with 
    * '-----BEGIN RSA PUBLIC KEY-----\n' +
    * and the private key which will start with
    * '-----BEGIN RSA PRIVATE KEY-----\n' +
    */
    //Verify it works by signing some data and verifying it.
    //Create some sample data that we want to sign
    const verifiableData = "this need to be verified";

    // The signature method takes the data we want to sign, the
    // hashing algorithm, and the padding scheme, and generates
    // a signature in the form of bytes
    const signature = require("crypto").sign("sha256", Buffer.from(verifiableData), 
    {
      key: privateKey,
      padding: require("crypto").constants.RSA_PKCS1_PSS_PADDING,
    });
    //Convert the signature to base64 for storage.
    console.log(signature.toString("base64"));

    // To verify the data, we provide the same hashing algorithm and
    // padding scheme we provided to generate the signature, along
    // with the signature itself, the data that we want to
    // verify against the signature, and the public key
    const isVerified = require("crypto").verify(
      "sha256",
      Buffer.from(verifiableData),
      {
        key: publicKey,
        padding: require("crypto").constants.RSA_PKCS1_PSS_PADDING,
      },
      Buffer.from(signature.toString("base64"), "base64")
    );

    // isVerified should be `true` if the signature is valid
    console.log("signature verified: ", isVerified);
  }
);

I think the key points are which algorithm is used, as older versions of the pem use pkcs1 not pkcs8. The beginning of the key helps identify the version of the key and also includes information on wither it is encrypted or not. Hope this helps!

0
votes

I have not used it, but this may be useful:

http://ox.no/posts/diffie-hellman-support-in-node-js

Documentation is severely lacking on this (no examples that I could find).

0
votes

You can use this rsa-json module. It just spawns a openssl process, so it is pretty dependent on the OS (it does not work by default on windows).

0
votes

child_process route is a terrible and non-scalable solution imo. Stay away.

I chose to go with keypair instead.