All Projects → wheresvic → mongoose-field-encryption

wheresvic / mongoose-field-encryption

Licence: MIT License
A simple symmetric encryption plugin for individual fields. Dependency free, only mongoose peer dependency.

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to mongoose-field-encryption

node-rest-api-scaffold
This project is an initial NodeJS Rest API scaffold for developers
Stars: ✭ 24 (-48.94%)
Mutual labels:  mongoose, node-js
Focus Budget Manager
Budget Manager application built with Vue.js, Node.js, Express.js and MongoDB
Stars: ✭ 189 (+302.13%)
Mutual labels:  mongoose, node-js
Nodejs Api Boilerplate
A boilerplate for kickstart your nodejs api project with JWT Auth and some new Techs :)
Stars: ✭ 364 (+674.47%)
Mutual labels:  mongoose, node-js
express-mvc-generator
Express' Model View Controller Application Generator.
Stars: ✭ 46 (-2.13%)
Mutual labels:  mongoose, node-js
Becoditive-API
The official API of beCoditive with many endpoints like memes, animals, image manipulation, url shortner, etc.
Stars: ✭ 14 (-70.21%)
Mutual labels:  mongoose, node-js
node-express-mongo-passport-jwt-typescript
A Node.js back end web application with REST API, user JWT authentication and MongoDB data storage using TypeScript
Stars: ✭ 51 (+8.51%)
Mutual labels:  mongoose, node-js
Node Typescript Mongodb
node js typescript mongodb express generator yo
Stars: ✭ 96 (+104.26%)
Mutual labels:  mongoose, node-js
react-full-stack-starter
🎈Full-stack React boilerplate using `create-react-app`, Babel, Node.js, and express
Stars: ✭ 22 (-53.19%)
Mutual labels:  mongoose, node-js
zuly
🤖 | Hi, I'm zuly, a brazilian bot! Focused on animes!
Stars: ✭ 45 (-4.26%)
Mutual labels:  mongoose, node-js
Registration-and-Login-using-MERN-stack
Simple Registration and Login component with MERN stack
Stars: ✭ 210 (+346.81%)
Mutual labels:  mongoose, node-js
Nodepress
😎 RESTful API service for Blog/CMS, powered by @nestjs
Stars: ✭ 829 (+1663.83%)
Mutual labels:  mongoose, node-js
Backend-NodeJS-Golang-Interview QA
A collection of Node JS and Golang Backend interview questions please feel free to fork and contribute to this repository
Stars: ✭ 122 (+159.57%)
Mutual labels:  mongoose, node-js
dhan-gaadi
A complete online bus reservation system (Node, React, Mongo, NextJS, ReactNative)
Stars: ✭ 207 (+340.43%)
Mutual labels:  mongoose, node-js
express-boilerplate
ExpressJS boilerplate with Socket.IO, Mongoose for scalable projects.
Stars: ✭ 83 (+76.6%)
Mutual labels:  mongoose, node-js
timeoff-server
TimeOff is an application that allows companies' employees to set vacations before they begin taking their time off. Implemented in modern tech stack i.e. Node, Express, MongoDB.
Stars: ✭ 33 (-29.79%)
Mutual labels:  mongoose, node-js
php-simple-encryption
The PHP Simple Encryption library is designed to simplify the process of encrypting and decrypting data while ensuring best practices are followed. By default is uses a secure encryption algorithm and generates a cryptologically strong initialization vector so developers do not need to becomes experts in encryption to securely store sensitive data.
Stars: ✭ 32 (-31.91%)
Mutual labels:  encryption
The-Ultimate-Guide-to-TypeScript-With-Mongoose
youtu.be/tbt7eo1fxui
Stars: ✭ 14 (-70.21%)
Mutual labels:  mongoose
generator-espress
an opinionated yeoman generator that scaffolds a mvc express webapp completely in es6
Stars: ✭ 20 (-57.45%)
Mutual labels:  mongoose
express-mvc
A light-weight mvc pattern for express framework with minimum dependencies
Stars: ✭ 23 (-51.06%)
Mutual labels:  mongoose
fookie
High-level reactive programming framework.
Stars: ✭ 14 (-70.21%)
Mutual labels:  mongoose

mongoose-field-encryption

Build Status Coverage Status

A zero dependency simple symmetric encryption plugin for individual fields. The goal of this plugin is to encrypt data but still allow searching over fields with string values. This plugin relies on the Node crypto module. Encryption and decryption happen transparently during save and find.

While this plugin works on individual fields of any type, note that for non-string fields, the original value is set to undefined after encryption. This is because if the schema has defined a field as an array, it would not be possible to replace it with a string value.

As of the stable 2.3.0 release, this plugin requires provision of a custom salt generation function (which would always provide a constant salt given the secret) in order to retain symmetric decryption capability.

Also consider mongoose-encryption if you are looking to encrypt the entire document.

How it works

Encryption is performed using AES-256-CBC. To encrypt, the relevant fields are encrypted with the provided secret + random salt (or a custom salt via the provided saltGenerator function). The generated salt and the resulting encrypted value is concatenated together using a : character and the final string is put in place of the actual value for string values. An extra boolean field with the prefix __enc_ is added to the document which indicates if the provided field is encrypted or not.

Fields which are either objects or of a different type are converted to strings using JSON.stringify and the value stored in an extra marker field of type string with a naming scheme of __enc_ as prefix and _d as suffix on the original field name. The original field is then set to undefined. Please note that this might break any custom validation and application of this plugin on non-string fields needs to be done with care.

Requirements

  • Node >=6 (Use 2.3.4 for Node >=4.4.7 && <=6.x.x)
  • MongoDB >=2.6.10
  • Mongoose >=4.0.0

Installation

npm install mongoose-field-encryption --save-exact

Security Notes

  • Always store your keys and secrets outside of version control and separate from your database. An environment variable on your application server works well for this.
  • Additionally, store your encryption key offline somewhere safe. If you lose it, there is no way to retrieve your encrypted data.
  • Encrypting passwords is no substitute for appropriately hashing them. bcrypt is one great option. You can also encrypt the password afer hashing it although it is not necessary.
  • If an attacker gains access to your application server, they likely have access to both the database and the key. At that point, neither encryption nor authentication do you any good.

Usage

Basic

For example, given a schema as follows:

const mongoose = require("mongoose");
const mongooseFieldEncryption = require("mongoose-field-encryption").fieldEncryption;
const Schema = mongoose.Schema;

const PostSchema = new Schema({
  title: String,
  message: String,
  references: {
    author: String,
    date: Date,
  },
});

PostSchema.plugin(mongooseFieldEncryption, { 
  fields: ["message", "references"], 
  secret: "some secret key",
  saltGenerator: function (secret) {
    return "1234567890123456"; 
    // should ideally use the secret to return a string of length 16, 
    // default = `const defaultSaltGenerator = secret => crypto.randomBytes(16);`, 
    // see options for more details
  },
});

const Post = mongoose.model("Post", PostSchema);

const post = new Post({ title: "some text", message: "hello all" });

post.save(function (err) {
  console.log(post.title); // some text (only the message field was set to be encrypted via options)
  console.log(post.message); // a9ad74603a91a2e97a803a367ab4e04d:93c64bf4c279d282deeaf738fabebe89
  console.log(post.__enc_message); // true
});

The resulting documents will have the following format:

{
  _id: ObjectId,
  title: String,
  message: String, // encrypted salt and hex value as string, e.g. 9d6a0ca4ac2c80fc84df0a06de36b548:cee57185fed78c055ed31ca6a8be9bf20d303283200a280d0f4fc8a92902e0c1
  __enc_message: true, // boolean marking if the field is encrypted or not
  references: undefined, // encrypted object set to undefined
  __enc_references: true, // boolean marking if the field is encrypted or not
  __enc_references_d: String // encrypted salt and hex object value as string, e.g. 6df2171f25fd1d32adc4a4059f867a82:5909152856cf9cdb7dc32c6af321c8fe69390c359c6b19d967eaa6e7a0a97216
}

find works transparently and you can make new documents as normal, but you should not use the lean option on a find if you want the fields of the document to be decrypted. findOne, findById and save also all work as normal. update works only for string fields and you would also need to manually set the __enc_ field value to false if you're updating an encrypted field.

From the mongoose package documentation: Note that findAndUpdate/Remove do not execute any hooks or validation before making the change in the database. If you need hooks and validation, first query for the document and then save it.

Note that as of 1.2.0 release, support for findOneAndUpdate has also been added. Note that you would need to specifically set the encryption field marker for it to be encrypted. For example:

Post.findOneAndUpdate({ _id: postId }, { $set: { message: "snoop", __enc_message: false } });

The above also works for non-string fields. See changelog for more details.

Also note that if you manually set the value __enc_ prefix field to true then the encryption is not run on the corresponding field and this may result in the plain value being stored in the db.

Search over encrypted fields

Note that in order to use this option a fixed salt generator must be provided. See example as follows:

const messageSchema = new Schema({
  title: String,
  message: String,
  name: String,
});

messageSchema.plugin(mongooseFieldEncryption, {
  fields: ["message", "name"],
  secret: "some secret key",
  saltGenerator: function (secret) {
    return "1234567890123456";
    // should ideally use the secret to return a string of length 16, 
    // default = `const defaultSaltGenerator = secret => crypto.randomBytes(16);`, 
    // see options for more details
  },
});

const title = "some text";
const name = "victor";
const message = "hello all";

const Message = mongoose.model("Message", messageSchema);

const messageToSave = new Message({ title, message, name });
await messageToSave.save();

// note that we are only providing the field we would like to search with
const messageToSearchWith = new Message({ name });
messageToSearchWith.encryptFieldsSync();

// `messageToSearchWith.name` contains the encrypted string text
const results = await Message.find({ name: messageToSearchWith.name });

// results is an array of length 1 (assuming that there is only 1 message with the name "victor" in the collection)
// and the message in the results array corresponds to the one saved previously

Options

  • fields (required): an array list of the required fields
  • secret (required): a string cipher (or a synchronous factory function which returns a string cipher) which is used to encrypt the data (don't lose this!)
  • useAes256Ctr (optional, default false): a boolean indicating whether the older aes-256-ctr algorithm should be used. Note that this is strictly a backwards compatibility feature and for new installations it is recommended to leave this at default.
  • saltGenerator (optional, default const defaultSaltGenerator = secret => crypto.randomBytes(16);): a function that should return either a utf-8 encoded string that is 16 characters in length or a Buffer of length 16. This function is also passed the secret as shown in the default function example.

Static methods

For performance reasons, once the document has been encrypted, it remains so. The following methods are thus added to the schema:

  • encryptFieldsSync(): synchronous call that encrypts all fields as given by the plugin options
  • decryptFieldsSync(): synchronous call that decrypts encrypted fields as given by the plugin options
  • stripEncryptionFieldMarkers(): synchronous call that removes the encryption field markers (useful for returning documents without letting the user know that something was encrypted)

Multiple calls to the above methods have no effect, i.e. once a field is encrypted and the __enc_ marker field value is set to true then the ecrypt operation is ignored. Same for the decrypt operation. Of course if the field markers have been removed via the stripEncryptionFieldMarkers() call, then the encryption will be executed if invoked.

Searching

To enable searching over the encrypted fields the encrypt and decrypt methods have also been exposed (see test/test-manual-encryption for detailed usage).

const fieldEncryption = require('mongoose-field-encryption');

const defaultSaltGenerator = (secret) => crypto.randomBytes(16);
const _hash = (secret) => crypto.createHash("sha256").update(secret).digest("hex").substring(0, 32);
const encrypted = fieldEncryption.encrypt('some text', _hash('secret')), defaultSaltGenerator);
const decrypted = fieldEncryption.decrypt(encrypted, _hash('secret'))); // decrypted = 'some text'

encryption of nested fields

Note that while this plugin is designed to encrypt only top level fields, nested fields can be easily encrypted by creating a mongoose schema for the nested objects and adding the plugin to them.

See comment for discussion: #34 (comment).

Please also note that this example is provided as a best-effort basis and this plugin does not take responsibility for what quirks mongoose might bring if you use this feature.

See relevant test in test/test-db.js:

// subdocument encryption

const UserExtraSchema = new mongoose.Schema({
  city: { type: String },
  country: { type: String },
  address: { type: String },
  postalCode: { type: String },
});

UserExtraSchema.plugin(fieldEncryptionPlugin, {
  fields: ["address"],
  secret: "icanhazcheeseburger",
  saltGenerator: (secret) => secret.slice(0, 16),
});

const UserSchema = new mongoose.Schema(
  {
    name: { type: String, required: true },
    surname: { type: String, required: true },
    email: { type: String, required: true },
    extra: UserExtraSchema,
  },
  { collection: "users" }
);

UserSchema.plugin(fieldEncryptionPlugin, {
  fields: ["name", "surname"],
  secret: "icanhazcheeseburger",
  saltGenerator: (secret) => secret.slice(0, 16),
});

const UserModel = mongoose.model("User", UserSchema);

Development

As of version 3.0.5, one can setup a local development mongodb instance using docker:

  • copy development/docker-compose-dev.yml to development/docker-compose.yml
  • copy development/init-mongo-dev.js to development/init-mongo.js
  • run docker-compose up in the development folder

Feel free to make changes to the default docker configuration as required.

Testing

  1. Install dependencies with npm install and install mongo if you don't have it yet.
  2. Start mongo via docker-compose up under the development folder.
  3. Run tests with npm run test:auth. Additionally you can pass your own mongodb uri as an environment variable if you would like to test against your own database, for e.g. URI='mongodb://username:[email protected]:27017/mongoose-field-encryption-test' npm test

Publishing

release-it

release-it patch,minor,major

Manual

  • npm version patch,minor,major
  • npm publish

Changelog

5.0.1, 5.0.2

  • Update README
  • Add test for manual encryption
  • No functionality affected

5.0.0

  • BREAKING: support encrypting falsy values, e.g. an empty string field, a field with the 0 number value, a field with the false boolean value, etc. See relevant test: test/test-db.js#L631

    Note that previously falsy values were not encrypted and stored as is. On document retrieval the plugin checks for the existence of encrypted data so this should in theory not break existing documents. Existing documents would need to be re-encrypted to take advantage of falsy encryption however.

    As always, please test thoroughly before upgrading.

4.0.4, 4.0.5, 4.0.6, 4.0.7

  • Update README
  • Update development dependencies
  • No functionality affected

4.0.3

  • Add typescript types
  • Update development dependencies

4.0.2

  • Update documentation for subdocument encryption

4.0.1

  • Update documentation to add nested field encryption example
  • Switch from Travis to Github actions

4.0.0

  • FEATURE: Add support for an optional synchronous secret function instead of a fixed string. Note that while this change should be backwards compatible, care should be taken as an issues with the secret could lead to irrecoverable documents!
  • Add support for updateOne (https://mongoosejs.com/docs/api.html#query_Query-updateOne).

3.1.0

  • Do not use

3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6

  • Update development dependencies, fix unit tests, no functionality affected
  • Add development db via docker (3.0.5)

3.0.0

  • BREAKING: Drop Node 4 support

2.3.5

  • Update development dependencies, no functionality affected

2.3.2, 2.3.3, 2.3.4

  • Update documentation, no functionality affected

2.3.1

  • Update documentation, no functionality affected

2.3.0

  • FEATURE: Add provision for a custom salt generator, PR #27. Note that by using a custom salt, fixed search capability is now restored.

2.2.0

  • Update dependencies, no functionality affected

2.1.3

  • FIX: Fix bug where decryption fails when the field in question is not retrieved, PR #26.

2.1.1

  • FIX: Fix bug where data was not getting decrypted on a find(), #23.

2.0.0

  • BREAKING: Use cipheriv instead of plain cipher, #17.

    Note that this might break any fixed search capability as the encrypted values are now based on a random salt.

    Also note that while this version maintains backward compatibility, i.e. decryption will automatically fall back to using the aes-256-ctr algorithm, any further updates will lead to the value being encrypted with the salt. In order to fully maintain backwards compatibilty, an new option useAes256Ctr has been introduced (default false), which can be set to true to continue using the plugin as before. It is highly recommended to start using the newer algorithm however, see issue for more details.

1.2.0

  • FEATURE: Added support for findOneAndUpdate #20

1.1.0

  • FEATURE: Added support for mongoose 5 #16.
  • FIX: Removed mongoose dependency, moved to peerDependencies.
  • Formatted source code using prettier.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].