diff --git a/migrations/0031__change_encryption_to_CBC.js b/migrations/0031__change_encryption_to_CBC.js index 3d77805c0..5765a1051 100644 --- a/migrations/0031__change_encryption_to_CBC.js +++ b/migrations/0031__change_encryption_to_CBC.js @@ -1,7 +1,6 @@ const sql = require('../services/sql'); const data_encryption = require('../services/data_encryption'); const password_encryption = require('../services/password_encryption'); -const my_scrypt = require('../services/my_scrypt'); const readline = require('readline'); const cl = readline.createInterface(process.stdin, process.stdout); @@ -46,11 +45,4 @@ module.exports = async () => { await sql.execute("UPDATE notes SET note_title = ?, note_text = ? WHERE note_id = ?", [noteHistory.note_title, noteHistory.note_text, noteHistory.note_history_id]); } - - const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); - - // trimming to 128bits (for AES-128) - const trimmedDataKey = dataKey.slice(0, 16); - - await password_encryption.encryptDataKey(passwordDerivedKey, trimmedDataKey); }; \ No newline at end of file diff --git a/migrations/0032__add_option_for_encrypted_data_key_iv.sql b/migrations/0032__add_option_for_encrypted_data_key_iv.sql new file mode 100644 index 000000000..e77d395da --- /dev/null +++ b/migrations/0032__add_option_for_encrypted_data_key_iv.sql @@ -0,0 +1 @@ +INSERT INTO options (opt_name, opt_value) VALUES ('encrypted_data_key_iv', '') \ No newline at end of file diff --git a/migrations/0033__change_data_key_encryption_to_cbc.js b/migrations/0033__change_data_key_encryption_to_cbc.js new file mode 100644 index 000000000..bba2f8239 --- /dev/null +++ b/migrations/0033__change_data_key_encryption_to_cbc.js @@ -0,0 +1,25 @@ +const password_encryption = require('../services/password_encryption'); +const readline = require('readline'); + +const cl = readline.createInterface(process.stdin, process.stdout); + +function question(q) { + return new Promise( (res, rej) => { + cl.question( q, answer => { + res(answer); + }) + }); +} + +module.exports = async () => { + const password = await question("Enter password: "); + let dataKey = await password_encryption.getDecryptedDataKey(password); + + console.log("Original data key: ", dataKey); + + dataKey = dataKey.slice(0, 16); + + console.log("Trimmed data key: ", dataKey); + + await password_encryption.setDataKeyCbc(password, dataKey); +}; \ No newline at end of file diff --git a/routes/api/login.js b/routes/api/login.js index 963e0f936..7cdf43785 100644 --- a/routes/api/login.js +++ b/routes/api/login.js @@ -57,7 +57,7 @@ router.post('/protected', auth.checkApiAuth, async (req, res, next) => { return; } - const decryptedDataKey = await password_encryption.getDecryptedDataKey(password); + const decryptedDataKey = await password_encryption.getDecryptedDataKeyCbc(password); const protectedSessionId = protected_session.setDataKey(req, decryptedDataKey); diff --git a/routes/api/note_history.js b/routes/api/note_history.js index fd4fc7cae..ecc875c45 100644 --- a/routes/api/note_history.js +++ b/routes/api/note_history.js @@ -15,8 +15,8 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { for (const hist of history) { if (hist.is_protected) { - hist.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_title); - hist.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_text); + hist.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_title); + hist.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_text); } } diff --git a/routes/api/notes.js b/routes/api/notes.js index aba03ade8..19c7ba666 100644 --- a/routes/api/notes.js +++ b/routes/api/notes.js @@ -21,8 +21,8 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { if (detail.is_protected) { const dataKey = protected_session.getDataKey(req); - detail.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(noteId), detail.note_title); - detail.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTextIv(noteId), detail.note_text); + detail.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(noteId), detail.note_title); + detail.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(noteId), detail.note_text); } res.send({ diff --git a/routes/api/tree.js b/routes/api/tree.js index dc23ac8df..6bcaa8e16 100644 --- a/routes/api/tree.js +++ b/routes/api/tree.js @@ -28,7 +28,7 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { for (const note of notes) { if (note.is_protected) { - note.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); + note.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); } note.children = []; diff --git a/services/change_password.js b/services/change_password.js index 7c565a10a..c7109df7f 100644 --- a/services/change_password.js +++ b/services/change_password.js @@ -18,12 +18,10 @@ async function changePassword(currentPassword, newPassword, req) { const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword)); const newPasswordDerivedKey = await my_scrypt.getPasswordDerivedKey(newPassword); - const decryptedDataKey = await password_encryption.getDecryptedDataKey(currentPassword); - - const newEncryptedDataKey = password_encryption.encryptDataKey(newPasswordDerivedKey, decryptedDataKey); + const decryptedDataKey = await password_encryption.getDecryptedDataKeyCbc(currentPassword); await sql.doInTransaction(async () => { - await options.setOption('encrypted_data_key', newEncryptedDataKey); + await password_encryption.setDataKey(newPasswordDerivedKey, decryptedDataKey); await options.setOption('password_verification_hash', newPasswordVerificationKey); diff --git a/services/data_encryption.js b/services/data_encryption.js index 6889fc24f..375d4579b 100644 --- a/services/data_encryption.js +++ b/services/data_encryption.js @@ -65,7 +65,7 @@ function encrypt(dataKey, plainText) { function shaArray(content) { // we use this as simple checksum and don't rely on its security so SHA-1 is good enough - return crypto.createHash('sha1').update(content).digest('base64'); + return crypto.createHash('sha1').update(content).digest(); } function sha256Array(content) { @@ -73,8 +73,12 @@ function sha256Array(content) { } function pad(data) { + console.log("Before padding: ", data); + let padded = Array.from(data); + console.log("After arraying: ", padded); + if (data.length >= 16) { padded = padded.slice(0, 16); } @@ -82,6 +86,8 @@ function pad(data) { padded = padded.concat(Array(16 - padded.length).fill(0)); } + console.log("Before buffering: ", padded); + return Buffer.from(padded); } @@ -90,15 +96,17 @@ function encryptCbc(dataKey, iv, plainText) { throw new Error("No data key!"); } + const plainTextBuffer = Buffer.from(plainText); + const cipher = crypto.createCipheriv('aes-128-cbc', pad(dataKey), pad(iv)); - const digest = shaArray(plainText).slice(0, 4); + const digest = shaArray(plainTextBuffer).slice(0, 4); - const digestWithPayload = digest + plainText; + const digestWithPayload = Buffer.concat([digest, plainTextBuffer]); - const encryptedData = cipher.update(digestWithPayload, 'utf8', 'base64') + cipher.final('base64'); + const encryptedData = Buffer.concat([cipher.update(digestWithPayload), cipher.final()]); - return encryptedData; + return encryptedData.toString('base64'); } function decryptCbc(dataKey, iv, cipherText) { @@ -106,14 +114,24 @@ function decryptCbc(dataKey, iv, cipherText) { return "[protected]"; } + console.log("Key: ", pad(dataKey)); + const decipher = crypto.createDecipheriv('aes-128-cbc', pad(dataKey), pad(iv)); - const decryptedBytes = decipher.update(cipherText, 'base64', 'utf-8') + decipher.final('utf-8'); + + const cipherTextBuffer = Buffer.from(cipherText, 'base64'); + const decryptedBytes = Buffer.concat([decipher.update(cipherTextBuffer), decipher.final()]); + + console.log("decrypted: ", decryptedBytes); const digest = decryptedBytes.slice(0, 4); const payload = decryptedBytes.slice(4); + console.log("payload: ", payload); + const computedDigest = shaArray(payload).slice(0, 4); + console.log("Hash arr: ", computedDigest); + if (!arraysIdentical(digest, computedDigest)) { return false; } @@ -121,6 +139,12 @@ function decryptCbc(dataKey, iv, cipherText) { return payload; } +function decryptCbcString(dataKey, iv, cipherText) { + const buffer = decryptCbc(dataKey, iv, cipherText); + + return buffer.toString('utf-8'); +} + function noteTitleIv(iv) { return "0" + iv; } @@ -135,6 +159,7 @@ module.exports = { encrypt, encryptCbc, decryptCbc, + decryptCbcString, noteTitleIv, noteTextIv }; \ No newline at end of file diff --git a/services/migration.js b/services/migration.js index da7d2eceb..a95120254 100644 --- a/services/migration.js +++ b/services/migration.js @@ -4,7 +4,7 @@ const options = require('./options'); const fs = require('fs-extra'); const log = require('./log'); -const APP_DB_VERSION = 31; +const APP_DB_VERSION = 33; const MIGRATIONS_DIR = "migrations"; async function migrate() { diff --git a/services/notes.js b/services/notes.js index 25316cd53..105666604 100644 --- a/services/notes.js +++ b/services/notes.js @@ -92,8 +92,8 @@ async function protectNote(note, dataKey, protect) { changed = true; } else if (!protect && note.is_protected) { - note.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); - note.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text); + note.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(note.note_id), note.note_title); + note.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text); note.is_protected = false; changed = true; @@ -121,8 +121,8 @@ async function protectNoteHistory(noteId, dataKey, protect) { history.is_protected = true; } else { - history.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(history.note_history_id), history.note_title); - history.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTextIv(history.note_history_id), history.note_text); + history.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(history.note_history_id), history.note_title); + history.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(history.note_history_id), history.note_text); history.is_protected = false; } diff --git a/services/password_encryption.js b/services/password_encryption.js index 8228048f1..bb2ed634a 100644 --- a/services/password_encryption.js +++ b/services/password_encryption.js @@ -3,6 +3,7 @@ const my_scrypt = require('./my_scrypt'); const utils = require('./utils'); const crypto = require('crypto'); const aesjs = require('./aes'); +const data_encryption = require('./data_encryption'); async function verifyPassword(password) { const givenPasswordHash = utils.toBase64(await my_scrypt.getVerificationHash(password)); @@ -31,6 +32,26 @@ function encryptDataKey(passwordDerivedKey, plainText) { return utils.toBase64(encryptedBytes); } +async function setDataKey(passwordDerivedKey, plainText) { + const newEncryptedDataKey = encryptDataKey(passwordDerivedKey, plainText); + + await options.setOption('encrypted_data_key', newEncryptedDataKey); +} + +async function setDataKeyCbc(password, plainText) { + const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); + + const encryptedDataKeyIv = utils.randomSecureToken(16).slice(0, 16); + + await options.setOption('encrypted_data_key_iv', encryptedDataKeyIv); + + const buffer = Buffer.from(plainText); + + const newEncryptedDataKey = data_encryption.encryptCbc(passwordDerivedKey, encryptedDataKeyIv, buffer); + + await options.setOption('encrypted_data_key', newEncryptedDataKey); +} + async function getDecryptedDataKey(password) { const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); @@ -41,13 +62,27 @@ async function getDecryptedDataKey(password) { return decryptedDataKey; } +async function getDecryptedDataKeyCbc(password) { + const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); + + const encryptedDataKeyIv = await options.getOption('encrypted_data_key_iv'); + const encryptedDataKey = await options.getOption('encrypted_data_key'); + + const decryptedDataKey = data_encryption.decryptCbc(passwordDerivedKey, encryptedDataKeyIv, encryptedDataKey); + + console.log("Decrypted data key: ", decryptedDataKey); + + return decryptedDataKey; +} + function getAes(key) { return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); } module.exports = { verifyPassword, - decryptDataKey, - encryptDataKey, - getDecryptedDataKey + getDecryptedDataKey, + getDecryptedDataKeyCbc, + setDataKey, + setDataKeyCbc }; \ No newline at end of file diff --git a/services/protected_session.js b/services/protected_session.js index 40b9bf8d0..d1db0cb42 100644 --- a/services/protected_session.js +++ b/services/protected_session.js @@ -3,7 +3,7 @@ const utils = require('./utils'); function setDataKey(req, decryptedDataKey) { - req.session.decryptedDataKey = decryptedDataKey; + req.session.decryptedDataKey = Array.from(decryptedDataKey); // can't store buffer in session req.session.protectedSessionId = utils.randomSecureToken(32); return req.session.protectedSessionId;