mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	data key is not encrypted with aes-cbc as well
This commit is contained in:
		| @@ -1,7 +1,6 @@ | |||||||
| const sql = require('../services/sql'); | const sql = require('../services/sql'); | ||||||
| const data_encryption = require('../services/data_encryption'); | const data_encryption = require('../services/data_encryption'); | ||||||
| const password_encryption = require('../services/password_encryption'); | const password_encryption = require('../services/password_encryption'); | ||||||
| const my_scrypt = require('../services/my_scrypt'); |  | ||||||
| const readline = require('readline'); | const readline = require('readline'); | ||||||
|  |  | ||||||
| const cl = readline.createInterface(process.stdin, process.stdout); | 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]); |         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); |  | ||||||
| }; | }; | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | INSERT INTO options (opt_name, opt_value) VALUES ('encrypted_data_key_iv', '') | ||||||
							
								
								
									
										25
									
								
								migrations/0033__change_data_key_encryption_to_cbc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								migrations/0033__change_data_key_encryption_to_cbc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -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); | ||||||
|  | }; | ||||||
| @@ -57,7 +57,7 @@ router.post('/protected', auth.checkApiAuth, async (req, res, next) => { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const decryptedDataKey = await password_encryption.getDecryptedDataKey(password); |     const decryptedDataKey = await password_encryption.getDecryptedDataKeyCbc(password); | ||||||
|  |  | ||||||
|     const protectedSessionId = protected_session.setDataKey(req, decryptedDataKey); |     const protectedSessionId = protected_session.setDataKey(req, decryptedDataKey); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -15,8 +15,8 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | |||||||
|  |  | ||||||
|     for (const hist of history) { |     for (const hist of history) { | ||||||
|         if (hist.is_protected) { |         if (hist.is_protected) { | ||||||
|             hist.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_title); |             hist.note_title = data_encryption.decryptCbcString(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_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(hist.note_history_id), hist.note_text); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ router.get('/:noteId', auth.checkApiAuth, async (req, res, next) => { | |||||||
|     if (detail.is_protected) { |     if (detail.is_protected) { | ||||||
|         const dataKey = protected_session.getDataKey(req); |         const dataKey = protected_session.getDataKey(req); | ||||||
|  |  | ||||||
|         detail.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(noteId), detail.note_title); |         detail.note_title = data_encryption.decryptCbcString(dataKey, data_encryption.noteTitleIv(noteId), detail.note_title); | ||||||
|         detail.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTextIv(noteId), detail.note_text); |         detail.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(noteId), detail.note_text); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     res.send({ |     res.send({ | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ router.get('/', auth.checkApiAuth, async (req, res, next) => { | |||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|         if (note.is_protected) { |         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 = []; |         note.children = []; | ||||||
|   | |||||||
| @@ -18,12 +18,10 @@ async function changePassword(currentPassword, newPassword, req) { | |||||||
|     const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword)); |     const newPasswordVerificationKey = utils.toBase64(await my_scrypt.getVerificationHash(newPassword)); | ||||||
|     const newPasswordDerivedKey = await my_scrypt.getPasswordDerivedKey(newPassword); |     const newPasswordDerivedKey = await my_scrypt.getPasswordDerivedKey(newPassword); | ||||||
|  |  | ||||||
|     const decryptedDataKey = await password_encryption.getDecryptedDataKey(currentPassword); |     const decryptedDataKey = await password_encryption.getDecryptedDataKeyCbc(currentPassword); | ||||||
|  |  | ||||||
|     const newEncryptedDataKey = password_encryption.encryptDataKey(newPasswordDerivedKey, decryptedDataKey); |  | ||||||
|  |  | ||||||
|     await sql.doInTransaction(async () => { |     await sql.doInTransaction(async () => { | ||||||
|         await options.setOption('encrypted_data_key', newEncryptedDataKey); |         await password_encryption.setDataKey(newPasswordDerivedKey, decryptedDataKey); | ||||||
|  |  | ||||||
|         await options.setOption('password_verification_hash', newPasswordVerificationKey); |         await options.setOption('password_verification_hash', newPasswordVerificationKey); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ function encrypt(dataKey, plainText) { | |||||||
|  |  | ||||||
| function shaArray(content) { | function shaArray(content) { | ||||||
|     // we use this as simple checksum and don't rely on its security so SHA-1 is good enough |     // 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) { | function sha256Array(content) { | ||||||
| @@ -73,8 +73,12 @@ function sha256Array(content) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function pad(data) { | function pad(data) { | ||||||
|  |     console.log("Before padding: ", data); | ||||||
|  |  | ||||||
|     let padded = Array.from(data); |     let padded = Array.from(data); | ||||||
|  |  | ||||||
|  |     console.log("After arraying: ", padded); | ||||||
|  |  | ||||||
|     if (data.length >= 16) { |     if (data.length >= 16) { | ||||||
|         padded = padded.slice(0, 16); |         padded = padded.slice(0, 16); | ||||||
|     } |     } | ||||||
| @@ -82,6 +86,8 @@ function pad(data) { | |||||||
|         padded = padded.concat(Array(16 - padded.length).fill(0)); |         padded = padded.concat(Array(16 - padded.length).fill(0)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     console.log("Before buffering: ", padded); | ||||||
|  |  | ||||||
|     return Buffer.from(padded); |     return Buffer.from(padded); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -90,15 +96,17 @@ function encryptCbc(dataKey, iv, plainText) { | |||||||
|         throw new Error("No data key!"); |         throw new Error("No data key!"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     const plainTextBuffer = Buffer.from(plainText); | ||||||
|  |  | ||||||
|     const cipher = crypto.createCipheriv('aes-128-cbc', pad(dataKey), pad(iv)); |     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) { | function decryptCbc(dataKey, iv, cipherText) { | ||||||
| @@ -106,14 +114,24 @@ function decryptCbc(dataKey, iv, cipherText) { | |||||||
|         return "[protected]"; |         return "[protected]"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     console.log("Key: ", pad(dataKey)); | ||||||
|  |  | ||||||
|     const decipher = crypto.createDecipheriv('aes-128-cbc', pad(dataKey), pad(iv)); |     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 digest = decryptedBytes.slice(0, 4); | ||||||
|     const payload = decryptedBytes.slice(4); |     const payload = decryptedBytes.slice(4); | ||||||
|  |  | ||||||
|  |     console.log("payload: ", payload); | ||||||
|  |  | ||||||
|     const computedDigest = shaArray(payload).slice(0, 4); |     const computedDigest = shaArray(payload).slice(0, 4); | ||||||
|  |  | ||||||
|  |     console.log("Hash arr: ", computedDigest); | ||||||
|  |  | ||||||
|     if (!arraysIdentical(digest, computedDigest)) { |     if (!arraysIdentical(digest, computedDigest)) { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| @@ -121,6 +139,12 @@ function decryptCbc(dataKey, iv, cipherText) { | |||||||
|     return payload; |     return payload; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function decryptCbcString(dataKey, iv, cipherText) { | ||||||
|  |     const buffer = decryptCbc(dataKey, iv, cipherText); | ||||||
|  |  | ||||||
|  |     return buffer.toString('utf-8'); | ||||||
|  | } | ||||||
|  |  | ||||||
| function noteTitleIv(iv) { | function noteTitleIv(iv) { | ||||||
|     return "0" + iv; |     return "0" + iv; | ||||||
| } | } | ||||||
| @@ -135,6 +159,7 @@ module.exports = { | |||||||
|     encrypt, |     encrypt, | ||||||
|     encryptCbc, |     encryptCbc, | ||||||
|     decryptCbc, |     decryptCbc, | ||||||
|  |     decryptCbcString, | ||||||
|     noteTitleIv, |     noteTitleIv, | ||||||
|     noteTextIv |     noteTextIv | ||||||
| }; | }; | ||||||
| @@ -4,7 +4,7 @@ const options = require('./options'); | |||||||
| const fs = require('fs-extra'); | const fs = require('fs-extra'); | ||||||
| const log = require('./log'); | const log = require('./log'); | ||||||
|  |  | ||||||
| const APP_DB_VERSION = 31; | const APP_DB_VERSION = 33; | ||||||
| const MIGRATIONS_DIR = "migrations"; | const MIGRATIONS_DIR = "migrations"; | ||||||
|  |  | ||||||
| async function migrate() { | async function migrate() { | ||||||
|   | |||||||
| @@ -92,8 +92,8 @@ async function protectNote(note, dataKey, protect) { | |||||||
|         changed = true; |         changed = true; | ||||||
|     } |     } | ||||||
|     else if (!protect && note.is_protected) { |     else if (!protect && 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.note_text = data_encryption.decryptCbc(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text); |         note.note_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(note.note_id), note.note_text); | ||||||
|         note.is_protected = false; |         note.is_protected = false; | ||||||
|  |  | ||||||
|         changed = true; |         changed = true; | ||||||
| @@ -121,8 +121,8 @@ async function protectNoteHistory(noteId, dataKey, protect) { | |||||||
|             history.is_protected = true; |             history.is_protected = true; | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             history.note_title = data_encryption.decryptCbc(dataKey, data_encryption.noteTitleIv(history.note_history_id), history.note_title); |             history.note_title = data_encryption.decryptCbcString(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_text = data_encryption.decryptCbcString(dataKey, data_encryption.noteTextIv(history.note_history_id), history.note_text); | ||||||
|             history.is_protected = false; |             history.is_protected = false; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ const my_scrypt = require('./my_scrypt'); | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
| const crypto = require('crypto'); | const crypto = require('crypto'); | ||||||
| const aesjs = require('./aes'); | const aesjs = require('./aes'); | ||||||
|  | const data_encryption = require('./data_encryption'); | ||||||
|  |  | ||||||
| async function verifyPassword(password) { | async function verifyPassword(password) { | ||||||
|     const givenPasswordHash = utils.toBase64(await my_scrypt.getVerificationHash(password)); |     const givenPasswordHash = utils.toBase64(await my_scrypt.getVerificationHash(password)); | ||||||
| @@ -31,6 +32,26 @@ function encryptDataKey(passwordDerivedKey, plainText) { | |||||||
|     return utils.toBase64(encryptedBytes); |     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) { | async function getDecryptedDataKey(password) { | ||||||
|     const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); |     const passwordDerivedKey = await my_scrypt.getPasswordDerivedKey(password); | ||||||
|  |  | ||||||
| @@ -41,13 +62,27 @@ async function getDecryptedDataKey(password) { | |||||||
|     return decryptedDataKey; |     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) { | function getAes(key) { | ||||||
|     return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); |     return new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     verifyPassword, |     verifyPassword, | ||||||
|     decryptDataKey, |     getDecryptedDataKey, | ||||||
|     encryptDataKey, |     getDecryptedDataKeyCbc, | ||||||
|     getDecryptedDataKey |     setDataKey, | ||||||
|  |     setDataKeyCbc | ||||||
| }; | }; | ||||||
| @@ -3,7 +3,7 @@ | |||||||
| const utils = require('./utils'); | const utils = require('./utils'); | ||||||
|  |  | ||||||
| function setDataKey(req, decryptedDataKey) { | 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); |     req.session.protectedSessionId = utils.randomSecureToken(32); | ||||||
|  |  | ||||||
|     return req.session.protectedSessionId; |     return req.session.protectedSessionId; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user