mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +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 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); | ||||
| }; | ||||
| @@ -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; | ||||
|     } | ||||
|  | ||||
|     const decryptedDataKey = await password_encryption.getDecryptedDataKey(password); | ||||
|     const decryptedDataKey = await password_encryption.getDecryptedDataKeyCbc(password); | ||||
|  | ||||
|     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) { | ||||
|         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); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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({ | ||||
|   | ||||
| @@ -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 = []; | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
| }; | ||||
| @@ -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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user