0 Design: Encryption of credentials
Nick O'Leary edited this page 2018-01-15 22:29:01 +00:00

Target: 0.15

Currently credentials are passed to the storage API in the clear so unless the storage mechanism does anything specific, they get stored in the clear.

With a move to add version control backing to node-red, the very real prospect emerges of credentials being stored, in the clear, in version control. That is highly undesirable.

We cannot escape the fact that we need to store credential information in a retrievable way; hashing is not an option.

This feature is to enable encryption of credentials by default - a user has to explicitly disable encryption if they do not want it to apply.


The encryption scheme requires a key to encrypt/decrypt the content.

A user is able to provide their own key via the credentialSecret property in the settings file. But most users will not do that the first time they run node-red after upgrading to this release. In which case, the runtime will auto-generate a key and store it in runtime settings. The credentials will then get encrypted with that key the next time flows are deployed.

If a user then provides their own credentialSecret property in the settings.js file, the runtime will migrate from the generated key to the user provided key the next time flows are deployed.

If a user changes credentialSecret at any point, the runtime will no longer be able to decrypt the credentials and they will be lost.


The credentials passed over the Storage API will be the encrypted set. An unencrypted credential object looks like this:

{
    "nodeId": {
        "credentialA": "value",
        "credentialB": "value"
    }
}

After encryption, it looks like this:

{
    "$": "b89d8209b5158a3c313675561b1a5b5phN1gDBe81Z"
}

By keeping it a valid JSON object underlying storage implementations should not be affected by the change.


Encryption scheme

var encryptionKey = crypto.createHash('sha256').update(userKey).digest();
var initVector = crypto.randomBytes(16);
var cipher = crypto.createCipheriv("aes-256-ctr", encryptionKey, initVector);
var result = cipher.update(JSON.stringify(credentials), 'utf8', 'base64') + cipher.final('base64');
result = initVector.toString('hex') + result;

Decryption scheme

var encryptionKey = crypto.createHash('sha256').update(userKey).digest();
var initVector = new Buffer(encryptedCredentials.substring(0, 32),'hex');
encryptedCredentials = encryptedCredentials.substring(32);
var decipher = crypto.createDecipheriv(encryptionAlgorithm, encryptionKey, initVector);
var decrypted = decipher.update(encryptedCredentials, 'base64', 'utf8') + decipher.final('utf8');
var result = JSON.parse(decrypted);

FAQ

The credentials file (flows_cred.json) is encrypted by default to ensure its contents cannot be easily read.

Node-RED generates a random key for the encryption if you do not provide one in your settings file. If the second instance of Node-RED doesn't have the same encryption key, it won't be able to decrypt the file.

Here are the steps you need to resolve this.

  1. edit your settings.js file and add a credentialSecret property with a whatever string value you want. If you want to disable encryption, set its value to false.

     credentialSecret: "my-random-string"
    
  2. Restart Node-RED and deploy a change - this will trigger Node-RED to re-encrypt your credentials with your chosen key (or disabling encryption if set to false).

  3. You can then copy your flow/credential file to a second instance, just make sure you give it the same credentialSecret value in its settings file.

Note that once you set credentialSecret you cannot change its value.