Q:

What should I use to encrypt data between a centralized server and multiple devices running NodeJS?

I have a centralized server and DB that each of my devices polls every ten seconds. The centralized server may respond with a list of "active jobs", which the devices will then run.
I need to ensure the data sent from the server to the devices is encrypted and only each device can decrypt that.
I was thinking I would need to generate a key pair on each device at installation and send the public key to be stored on the centralized server. The server then encrypts data out using the public key, and each device decrypts that data once ingested using the private key.
Does this make sense? Which encryption scheme should I be using? My server is running Laravel (PHP) and as mentioned each device is running a NodeJS daemon.

A:

This original answer was to use HTTP/SSL/TLS to encrypt the information between the PHP server and NodeJS client and to use basic authentication in the HTTPS request. 
However from the conversation in the comments to this answer the server and clients are already using HTTPS to secure the traffic between the server and the client.
Therefore as long as the NodeJS client trusts the SSL Certificate provided by the server, the communications between the client and server should be secure given all of the caveats of all of the answers to this Information Security question: How safe is SSL?
To be extra safe I found an example in NodeJS HTTPS API documentation that shows you how to have your HTTPS request validate that the server's SSL certificate has the correct SHA256 fingerprint and private key.
const tls = require('tls');
const https = require('https');
const crypto = require('crypto');

function sha256(s) {
  return crypto.createHash('sha256').update(s).digest('base64');
}
const options = {
  hostname: 'github.com',
  port: 443,
  path: '/',
  method: 'GET',
  checkServerIdentity: function(host, cert) {
    // Make sure the certificate is issued to the host we are connected to
    const err = tls.checkServerIdentity(host, cert);
    if (err) {
      return err;
    }

    // Pin the public key, similar to HPKP pin-sha25 pinning
    const pubkey256 = 'pL1+qb9HTMRZJmuC/bB/ZI9d302BYrrqiVuRyW+DGrU=';
    if (sha256(cert.pubkey) !== pubkey256) {
      const msg = 'Certificate verification error: ' +
        `The public key of '${cert.subject.CN}' ('${sha256(cert.pubkey)}') ` +
        `does not match our pinned public key ('${pubkey256}') `;
      return new Error(msg);
    }

    // Pin the exact certificate, rather then the pub key
    const cert256 = '25:FE:39:32:D9:63:8C:8A:FC:A1:9A:29:87:' +
      'D8:3E:4C:1D:98:DB:71:E4:1A:48:03:98:EA:22:6A:BD:8B:93:16';
    if (cert.fingerprint256 !== cert256) {
      const msg = 'Certificate verification error: ' +
      `The certificate of '${cert.subject.CN}' ('${cert.fingerprint256}') ` +
      `does not match our pinned fingerprint ('${cert256}')`;
      return new Error(msg);
    }

    // This loop is informational only.
    // Print the certificate and public key fingerprints of all certs in the
    // chain. Its common to pin the public key of the issuer on the public
    // internet, while pinning the public key of the service in sensitive
    // environments.
    do {
      console.log('Subject Common Name:', cert.subject.CN);
      console.log('  Certificate SHA256 fingerprint:', cert.fingerprint256);

      hash = crypto.createHash('sha256');
      console.log('  Public key ping-sha256:', sha256(cert.pubkey));

      lastprint256 = cert.fingerprint256;
      cert = cert.issuerCertificate;
    } while (cert.fingerprint256 !== lastprint256);

  },
};

options.agent = new https.Agent(options);
const req = https.request(options, (res) => {
  console.log('All OK. Server matched our pinned cert or public key');
  console.log('statusCode:', res.statusCode);
  // Print the HPKP values
  console.log('headers:', res.headers['public-key-pins']);

  res.on('data', (d) => {});
});

req.on('error', (e) => {
  console.error(e.message);
});
req.end();

Here's an example running this program.  The validations failed because the Github SSL certificate changed.  I added displaying the encountered values to the error messages to allow the script to be changed with the current values to validate testing.
pi@rpi1:~/tpf $ node check-cert.js 
Certificate verification error: The public key of 'github.com' ('o5oa5F4LbZEfeZ0kXDgmaU2K3sIPYtbQpT3EQLJZquM=') does not match our pinned private key ('pL1+qb9HTMRZJmuC/bB/ZI9d302BYrrqiVuRyW+DGrU=') 

Update script to use 'o5oa5F4LbZEfeZ0kXDgmaU2K3sIPYtbQpT3EQLJZquM=' for the private key.
pi@rpi1:~/tpf $ node check-cert.js 
Certificate verification error: The certificate of 'github.com' ('31:11:50:0C:4A:66:01:2C:DA:E3:33:EC:3F:CA:1C:9D:DE:45:C9:54:44:0E:7E:E4:13:71:6B:FF:36:63:C0:74') does not match our pinned fingerprint ('25:FE:39:32:D9:63:8C:8A:FC:A1:9A:29:87:D8:3E:4C:1D:98:DB:71:E4:1A:48:03:98:EA:22:6A:BD:8B:93:16')

Update script to use the displayed fingerprint which allows the script to continue and display all of the certificates in the chain provided.
pi@rpi1:~/tpf $ node check-cert.js 
Subject Common Name: github.com
  Certificate SHA256 fingerprint: 31:11:50:0C:4A:66:01:2C:DA:E3:33:EC:3F:CA:1C:9D:DE:45:C9:54:44:0E:7E:E4:13:71:6B:FF:36:63:C0:74
  Public key ping-sha256: o5oa5F4LbZEfeZ0kXDgmaU2K3sIPYtbQpT3EQLJZquM=
Subject Common Name: DigiCert SHA2 Extended Validation Server CA
  Certificate SHA256 fingerprint: 40:3E:06:2A:26:53:05:91:13:28:5B:AF:80:A0:D4:AE:42:2C:84:8C:9F:78:FA:D0:1F:C9:4B:C5:B8:7F:EF:1A
  Public key ping-sha256: RRM1dGqnDFsCJXBTHky16vi1obOlCgFFn/yOhI/y+ho=
Subject Common Name: DigiCert High Assurance EV Root CA
  Certificate SHA256 fingerprint: 74:31:E5:F4:C3:C1:CE:46:90:77:4F:0B:61:E0:54:40:88:3B:A9:A0:1E:D0:0B:A6:AB:D7:80:6E:D3:B1:18:CF
  Public key ping-sha256: WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=
All OK. Server matched our pinned cert or public key
statusCode: 200
headers: undefined

