🔐📱 An embedded and encrypted database
JavaScript Shell
Latest commit 19ba6d8 Dec 10, 2016 @BatikhSouri BatikhSouri Updated TODO

README.md

Lawncipher

Lawnchair and MongoDB-inspired libsodium-backed encrypted persistent document storage. Designed (and tested) for Cordova/Phonegap and Node.js

WARNING

Lawncipher v2.0 is still in beta stage. There are some problems with document validation (against an IndexModel) and some race conditions.

For stability, please use Lawncipher v1 instead, and install it simply with npm install lawncipher

If you want to use Lawncipher v2, install it with npm install lawncipher@beta

Goal

Building a rather versatile and persistent encrypted document storage.

Design

  • Lawncipher is a document store
  • The entirety of Lawncipher data is encrypted using either a password or a 256-bit key. (In case a password is used, it is transformed into a 256-bit root key using scrypt)
  • The security limits and precautions of Lawncipher are listed in the threat model.
  • Instead of tables containing rows, Lawncipher has collections containing documents
  • A document in Lawncipher has a unique ID and at least one of these two things:
    • A blob : could a JSON object, a string or arbitrary binary data (in a Uint8Array). It is stored encrypted and stored in a dedicated file, and decrypted when retrieved from the collection.
    • An indexData : An object, containing the query-able attributes of the document, stored in the collection's index.
  • Lawncipher is blob-first: when running a query, and the result list is being built, for a given result document, the result list will contain its blob. If the document doesn't have a blob, the indexData will take its place in the result list.
  • A schema, called "Index model", can be set for the indexData in a given collection. This schema gives the list and type of attributes that will be stored in the index. It can also determine whether a given attribute gives the IDs to the documents of the collection; as well as whether the value of a given attribute must be unique across the collection (without giving document IDs).
  • When inserting a document, if a JSON object is given, the indexData can be implicitly extracted from the document.
  • A document can be forced to expire, using TTLs (Time-to-live)

Getting started

In Node.js

npm install lawncipher

Then, we are good to go:

var Lawncipher = require('lawncipher');
Lawncipher.init();

var db = new Lawncipher.db('path/to/my/database');

db.openWithPassword('strongPasswordWow', function(err){
    if (err){
        if (err == 'INVALID_ROOTKEY'){
            //Invalid password
        }
        return;
    }

    //Do things with the database
});

In Cordova

Install the Cordova plugins:

Then install Lawncipher:

bower install lawncipher

Once we have installed Lawncipher (and the plugins mentioned above) and that we have imported Lawncipher into our application:

//Initialize the file system
window.plugins.nodefs.init(function(err){
    if (err){
        console.error('Error while initializing the file system: ' + err);
        return;
    }

    var fs = window.plugins.nodefs(window._fs);

    //Try to use MiniSodium, fallback to libsodium.js
    Lawncipher.init('?minisodium?');

    //Optional call. If you have installed cordova-plugin-scrypt. Redundant if MiniSodium is available
    Lawncipher.useCordovaPluginScrypt();

    var db = new Lawncipher.db('path/to/my/db', fs);

    db.openWithPassword('strongPasswordWow', function(err){
        if (err){
            if (err == 'INVALID_ROOTKEY'){
                //Invalid password
            }
            return;
        }

        //Do things with the database
    });
});

Example queries (and their SQL counterpart)

Lawncipher Retrieving a document by its ID (here, 'abc')

Collection.find('abc', callbackFunction)

Lawncipher

Collection.find({firstName: 'Steve', lastName: 'Jobs'}, callback)

SQL

SELECT * FROM tableName WHERE firstName = 'Steve' AND lastName = 'Jobs'

Lawncipher

Collection.find({firstName: 'Steve', $not: {lastName: 'Jobs'}}, callback)

SQL

SELECT * FROM tableName WHERE firstName = 'Steve' AND lastName <> 'Jobs'

Lawncipher

Collection.find({$or: [{firstName: 'Steve'}, {lastName: 'Jobs'}]}, callback)

SQL

SELECT * FROM tableName WHERE firstName = 'Steve' OR lastName = 'Jobs'  

Lawncipher

Collection.find({firstName: 'Steve', $or: [{lastName: 'Wozniak'}, {lastName: 'Jobs'}])

SQL

SELECT * FROM tableName WHERE firstName = 'Steve' AND (lastName = 'Wozniak' OR lastName = 'Jobs')

Lawncipher

Collection.find({firstName: 'Steve', $sort: {lastName: 'asc'}, $skip: 100}, callback, 100)

SQL

SELECT * FROM tableName WHERE firstName = 'Steve' ORDER BY lastName ASC LIMIT 100 OFFSET 100

(get the 101-200 guys who are called Steve, ordered alphabetically by lastName)

Testing

Here is how you can run unit tests in the compatible runtimes

Node.js

Go to the directory where the Lawncipher library is located, and run

npm test

Cordova/Phonegap

A small test app has been built for that purpose.

API

The Lawncipher API is documented here.

Internals and file formats

The Lawncipher interals and file formats are documented here.

License

Lawncipher is licensed under the terms of the MIT license.