2

I want to make a Node.js daemon that runs on multiple computers and is able to exchange messages between the different daemons. Of course the communication should be encrypted, but I really don't know what kind of encryption I should use for server-to-server encryption. The protocol I'm using right now is TCP via net.createServer. How should I encrypt the communication assuming I have a already exchanged password on both devices? How do I make it secure to the most known attacks?

Edit: Is using RSA combined with an "authentication password" secure? This password would then be submitted with every request, the whole message (including the password) would be encrypted with the RSA public key (which can be downloaded without encryption).

Florian Wendelborn
  • 1,667
  • 1
  • 19
  • 29

3 Answers3

3

I think the right way to do this is to communicate via ssl, see here:

http://nodejs.org/docs/v0.4.2/api/tls.html

You could also do a quick and dirty encryption using the crypto module:

var crypto = require('crypto'); 
var algorithm = 'aes256'; // or any other algorithm supported by OpenSSL

exports.encryptString = function(text) {

var cipher = crypto.createCipher(algorithm, key);
return cipher.update(text, 'utf8', 'hex') + cipher.final('hex');

};

var key = "123456"; 

exports.decryptString = function(text) {

var decipher =  crypto.createDecipher(algorithm, key);
return decipher.update(text, 'hex', 'utf8') + decipher.final('utf8');

};

Both servers need the public key.

You'll probably want to use JSON stringify and parse functions on top of the above (I had those lying around). You could do it in middleware that deciphers incoming requests and ciphers outgoing ones.

Robert Moskal
  • 21,737
  • 8
  • 62
  • 86
  • 2
    The documentation link is quite old. Here's an updated link: http://nodejs.org/api/tls.html – Brad Mar 30 '14 at 01:01
  • How can I verify the other server without bought SSL certificates when using TLS/SSL? – Florian Wendelborn Mar 30 '14 at 14:59
  • It looks like it may reject self-signed certificates by deault, but that you can override this behavior like so: process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0" It seems there is plenty of discussion of this sort of thing on the internet. – Robert Moskal Mar 30 '14 at 15:14
  • But this would allow everybody to fake the certificate easily and therefore allow man-in-the-middle attacks. Also: Are you sure that the functions you provided output valid AES? I can't verify it by using http://aesencryption.net/ or similar sites while using the same key. – Florian Wendelborn Mar 30 '14 at 16:06
  • This is not my area of expertise, but it looks to me that by default, self signed certificates are not allowed, so you would be good there no? Not sure whether my little function produces valid AES, here it seems to say you want to use a slightly different function in the crypto module: http://stackoverflow.com/questions/15193913/mapping-openssl-command-line-aes-encryption-to-nodejs-crypto-api-equivalent – Robert Moskal Mar 30 '14 at 16:33
  • Please note that the call made to decipher requires typeof text === "string". When working with data from a TCP socket, you may need to convert it from Buffer to string. For example, socket.on("data", function (data) { var msg = decipherString(data+""); /* etc */ }); – fernando Jun 30 '15 at 10:17
1

I take a different approach to this by doing the work outside of my application. Generally speaking, you don't want to reinvent wheels and secure encryption is a tough thing to get right.

I have a situation where several slave servers need to communicate to a master server to run jobs from a queue. For the server-to-server connection I actually just use Socket.IO (using the socket.io client NPM package and all transports disabled except for web sockets). This gives me a solid RPC, which works well for my needs. (I have since discovered rpc-stream which can give you RPC over arbitrary streams. This would be a bit more lightweight for server-to-server communication where Socket.IO is overkill.)

Now, for the encryption part... I just use a VPN set up between my servers. I took the lazy approach and used Hamachi for this, but you can certainly use OpenVPN or any other.

A second method you can use is to tunnel your connections through SSH.

In short, don't do any work you don't have to. Opt for speed, simplicity, and security. Use something off-the-shelf for this.

Brad
  • 159,648
  • 54
  • 349
  • 530
  • 1
    For your particular situation it might be right, but unfortunately I can't use this approach, because "my" servers can't be configured to use vpn. The result of this app should be more like a peer-to-peer messaging system instead of predefined servers with preconfigured vpn connections to each other. – Florian Wendelborn Mar 30 '14 at 00:58
  • @FlorianW. Ah, that makes sense. In that case, using SSL directly might be more appropriate. I would still consider an SSH tunnel if possible. – Brad Mar 30 '14 at 01:01
  • This isn't possible too, because the servers are supposed to be on multiple operating systems. – Florian Wendelborn Mar 30 '14 at 01:03
  • @FlorianW. SSH works great on all OS. But yes, it is more self-contained if you keep this all inside your application. – Brad Mar 30 '14 at 01:04
1

One option which might be easier to implement is to encrypt and decrypt all messages sent over a normal socket connection (net.createServer and net.connect), via pre-shared gpg keys using node-gpg. This requires that you have gpg in your $PATH on both client and server with a password-less private gpg key 'Server' on the server and a corresponding 'Client' on the client, with the respective public keys installed on the other end.

server.js:

var socketServer = net.createServer(function (c) {
  // example of send to client
  var output = JSON.stringify({"msg": "Stuff to send to client."});
  encrypt(output, 'Client', function (error, cryptdata) {
    c.write(cryptdata.toString());
  });
  // receive data sent from client
  c.on('data', function (cryptdata) {
    decrypt(cryptdata.toString(), 'Server', function (error, data) {
      data = JSON.parse(data.toString());
      // handle incoming data
    });
  });
});
socketServer.listen(port, function() {
});

client.js:

var socketClient = net.connect({"port": port}, function () {
  // Send data to server
  var data = JSON.stringify({"msg": "Data to server"});
  encrypt(data, 'Server', function (error, cryptdata) {
    socketClient.write(cryptdata.toString());
  });
});
// Receive data from server
socketClient.on('data', function(cryptdata) {
  decrypt(cryptdata.toString(), 'Client', function (error, data) {
    data = JSON.parse(data.toString());
    // handle data
  });
});

And these were the functions I used in both server.js and client.js for encryption/decryption.

function encrypt(str, receiver, callback) {
  gpg.encrypt(str, ['-r ' + receiver, '-a'], callback);
}
function decrypt(str, receiver, callback) {
  gpg.decrypt(str, ['-u ' + receiver, '-a'], callback);
}

This eliminates any problem you may run into with self-signed SSL certificates and at least with my benchmarks it is a lot faster. Though, it might not be as secure.

Xyz
  • 5,955
  • 5
  • 40
  • 58