Encrypting text fields in Mongoose is easy using Node's built-in crypto module. You might want to do this if you're using MongoDB as a service (see the recent MongoHQ security breach); or, if you're storing OAuth tokens that could, in the wrong hands, screw with somebody's account on a 3rd party service. (Of course, you should never encrypt passwords: those should be hashed.)
Imagine you have a Mongoose model like that shown below, which is modified only slighly from the example on the MongooseJS homepage.
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
var User = mongoose.model('User', {
name: String,
twitterOAuthToken: String
});
var kyle = new User({
name: 'Kyle',
twitterOAuthToken: 'c2721fee51e7ee571105e2d56c4919ae18fb7519'
});
kyle.save(function (err) {
console.log('woot');
});
If Kyle's twitterOAuthToken
fell into the wrong hands, it may be used to send spam, or worse. To decrease that risk, we can store the token encrypted, decrypting it only in the application (in memory) using MongooseJS getters and setters. See the same code, below, in which I'm using an environment variable SERVER_SECRET
as the key for AES-256 encryption in CBC mode.
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');
// Here's the required crypto code
var crypto = require('crypto');
function encrypt(text){
var cipher = crypto.createCipher('aes-256-cbc', process.env.SERVER_SECRET);
var crypted = cipher.update(text,'utf8','hex');
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
if (text === null || typeof text === 'undefined') {return text;};
var decipher = crypto.createDecipher('aes-256-cbc', process.env.SERVER_SECRET);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
var User = mongoose.model('User', {
name: String,
// Now add a getter and a setter
twitterOAuthToken: {type: String, get: decrypt, set: encrypt}
});
var kyle = new User({
name: 'Kyle',
twitterOAuthToken: 'c2721fee51e7ee571105e2d56c4919ae18fb7519'
});
kyle.save(function (err) {
console.log('woot');
});
Now, whenever we set the model's twitterOAuthToken
attribute, it is automatically encrypted, and when we access that attribute it is automatically decrypted. Only the encrypted value is sent to, and stored in, our MongoDB instance. That value is useless without the SERVER_SECRET
.
Very useful post. how do you suggest we handle migration of existing data and if we want to change the encrypt key or if key is comprimised ?