UNPKG

6.11 kBJavaScriptView Raw
1/**
2 * @file lokiCryptedFileAdapter.js
3 * @author Hans Klunder <Hans.Klunder@bigfoot.com>
4 */
5
6 /*
7 * The default Loki File adapter uses plain text JSON files. This adapter crypts the database string and wraps the result
8 * in a JSON including enough info to be able to decrypt it (except for the 'secret' of course !)
9 *
10 * The idea is that the 'secret' does not reside in your source code but is supplied by some other source (e.g. the user in node-webkit)
11 *
12 * The idea + encrypt/decrypt routines are borrowed from https://github.com/mmoulton/krypt/blob/develop/lib/krypt.js
13 * not using the krypt module to avoid third party dependencies
14 */
15
16
17/**
18 * require libs
19 * @ignore
20*/
21var fs = require('fs');
22var cryptoLib = require('crypto');
23var isError = require('util').isError;
24
25/*
26 * sensible defaults
27 */
28var CIPHER = 'aes-256-cbc',
29 KEY_DERIVATION = 'pbkdf2',
30 KEY_LENGTH = 256,
31 ITERATIONS = 64000;
32
33/**
34 * encrypt() - encrypt a string
35 * @private
36 * @param {string} input - the serialized JSON object to decrypt.
37 * @param {string} secret - the secret to use for encryption
38 */
39function encrypt(input, secret) {
40 if (!secret) {
41 return new Error('A \'secret\' is required to encrypt');
42 }
43
44
45 var salt = cryptoLib.randomBytes(KEY_LENGTH / 8),
46 iv = cryptoLib.randomBytes(16);
47
48 try {
49
50 var key = cryptoLib.pbkdf2Sync(secret, salt, ITERATIONS, KEY_LENGTH / 8, 'sha1'),
51 cipher = cryptoLib.createCipheriv(CIPHER, key, iv);
52
53 var encryptedValue = cipher.update(input, 'utf8', 'base64');
54 encryptedValue += cipher.final('base64');
55
56 var result = {
57 cipher: CIPHER,
58 keyDerivation: KEY_DERIVATION,
59 keyLength: KEY_LENGTH,
60 iterations: ITERATIONS,
61 iv: iv.toString('base64'),
62 salt: salt.toString('base64'),
63 value: encryptedValue
64 };
65 return result;
66
67 } catch (err) {
68 return new Error('Unable to encrypt value due to: ' + err);
69 }
70}
71
72/**
73 * decrypt() - Decrypt a serialized JSON object
74 * @private
75 * @param {string} input - the serialized JSON object to decrypt.
76 * @param {string} secret - the secret to use for decryption
77 */
78function decrypt(input, secret) {
79 // Ensure we have something to decrypt
80 if (!input) {
81 return new Error('You must provide a value to decrypt');
82 }
83 // Ensure we have the secret used to encrypt this value
84 if (!secret) {
85 return new Error('A \'secret\' is required to decrypt');
86 }
87
88 // turn string into an object
89 try {
90 input = JSON.parse(input);
91 } catch (err) {
92 return new Error('Unable to parse string input as JSON');
93 }
94
95 // Ensure our input is a valid object with 'iv', 'salt', and 'value'
96 if (!input.iv || !input.salt || !input.value) {
97 return new Error('Input must be a valid object with \'iv\', \'salt\', and \'value\' properties');
98 }
99
100 var salt = new Buffer(input.salt, 'base64'),
101 iv = new Buffer(input.iv, 'base64'),
102 keyLength = input.keyLength,
103 iterations = input.iterations;
104
105 try {
106
107 var key = cryptoLib.pbkdf2Sync(secret, salt, iterations, keyLength / 8, 'sha1'),
108 decipher = cryptoLib.createDecipheriv(CIPHER, key, iv);
109
110 var decryptedValue = decipher.update(input.value, 'base64', 'utf8');
111 decryptedValue += decipher.final('utf8');
112
113 return decryptedValue;
114
115 } catch (err) {
116 return new Error('Unable to decrypt value due to: ' + err);
117 }
118}
119
120/**
121 * The constructor is automatically called on `require` , see examples below
122 * @constructor
123 */
124function lokiCryptedFileAdapter() {}
125
126/**
127 * setSecret() - set the secret to be used during encryption and decryption
128 *
129 * @param {string} secret - the secret to be used
130 */
131lokiCryptedFileAdapter.prototype.setSecret = function setSecret(secret) {
132 this.secret = secret;
133};
134
135/**
136 * loadDatabase() - Retrieves a serialized db string from the catalog.
137 *
138 * @example
139 // LOAD
140 var cryptedFileAdapter = require('./lokiCryptedFileAdapter');
141 cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user
142 var db = new loki('test.crypted', { adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted'
143 db.loadDatabase(function(result) {
144 console.log('done');
145 });
146 *
147 * @param {string} dbname - the name of the database to retrieve.
148 * @param {function} callback - callback should accept string param containing serialized db string.
149 */
150lokiCryptedFileAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) {
151 var secret = this.secret;
152 var cFun = callback || console.log;
153
154 fs.readFile(dbname,'utf8',function(err,data){
155 var decrypted = err || decrypt(data, secret);
156 cFun(decrypted);
157 });
158};
159
160/**
161 *
162 @example
163 // SAVE : will save database in 'test.crypted'
164 var cryptedFileAdapter = require('./lokiCryptedFileAdapter');
165 cryptedFileAdapter.setSecret('mySecret'); // you should change 'mySecret' to something supplied by the user
166 var loki=require('lokijs');
167 var db = new loki('test.crypted',{ adapter: cryptedFileAdapter }); //you can use any name, not just '*.crypted'
168 var coll = db.addCollection('testColl');
169 coll.insert({test: 'val'});
170 db.saveDatabase(); // could pass callback if needed for async complete
171
172 @example
173 // if you have the krypt module installed you can use:
174 krypt --decrypt test.crypted --secret mySecret
175 to view the contents of the database
176
177 * saveDatabase() - Saves a serialized db to the catalog.
178 *
179 * @param {string} dbname - the name to give the serialized database within the catalog.
180 * @param {string} dbstring - the serialized db string to save.
181 * @param {function} callback - (Optional) callback passed obj.success with true or false
182 */
183lokiCryptedFileAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) {
184 var cFun = callback || function (){};
185 var encrypted = encrypt(dbstring, this.secret);
186 if (! isError(encrypted)){
187 fs.writeFile(dbname,
188 JSON.stringify(encrypted, null, ' '),
189 'utf8',cFun);
190 }
191 else { // Error !
192 cFun(encrypted);
193 }
194};
195
196module.exports = new lokiCryptedFileAdapter();
197exports.lokiCryptedFileAdapter = lokiCryptedFileAdapter;