Skip to content

Commit 5400d25

Browse files
committed
#3617 encryption: encrypt notes asynchronously
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 5b1ef50 commit 5400d25

3 files changed

Lines changed: 61 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## 26.5.13
44

5+
- Improved autosaving encrypted notes by running the expensive Botan encryption
6+
step in a worker thread, keeping typing responsive while encrypted notes are
7+
saved and avoiding overwriting newer edits made during encryption
8+
(for [#3617](https://github.com/pbek/QOwnNotes/issues/3617))
59
- Fixed unchanged encrypted notes being marked dirty and rewritten on every
610
`Ctrl+S` while editing encrypted notes, which changed their file modification
711
date and repeatedly showed `Stored 1 note(s) to disk`; encrypted note files

src/entities/note.cpp

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <QDebug>
1313
#include <QDir>
1414
#include <QElapsedTimer>
15+
#include <QEventLoop>
16+
#include <QFutureWatcher>
1517
#include <QMessageBox>
1618
#include <QMimeDatabase>
1719
#include <QQueue>
@@ -21,7 +23,9 @@
2123
#include <QSqlError>
2224
#include <QSqlRecord>
2325
#include <QTemporaryFile>
26+
#include <QThread>
2427
#include <QUrl>
28+
#include <QtConcurrent/QtConcurrentRun>
2529
#include <utility>
2630

2731
#include "api/noteapi.h"
@@ -408,6 +412,37 @@ static QString buildNoteEncryptionEnvelope(const QString &cipherText, const QStr
408412
QString::number(NoteEncryptionKdfIterations), salt, NoteEncryptionCipher, nonce, mac,
409413
cipherText);
410414
}
415+
416+
static QString encryptNoteTextWithBotan(const QString &text, const QString &password) {
417+
BotanWrapper botanWrapper;
418+
botanWrapper.setPassword(password);
419+
const QString salt = BotanWrapper::randomBytesBase64(NoteEncryptionSaltBytes);
420+
const QString nonce = BotanWrapper::randomBytesBase64(NoteEncryptionNonceBytes);
421+
QString mac;
422+
const QString encryptedText =
423+
botanWrapper.EncryptV2(text, salt, nonce, NoteEncryptionKdfIterations, &mac);
424+
425+
return encryptedText.isEmpty() ? encryptedText
426+
: buildNoteEncryptionEnvelope(encryptedText, salt, nonce, mac);
427+
}
428+
429+
static QString encryptNoteTextWithBotanInBackground(const QString &text, const QString &password) {
430+
if (qApp == nullptr || QThread::currentThread() != qApp->thread()) {
431+
return encryptNoteTextWithBotan(text, password);
432+
}
433+
434+
QFutureWatcher<QString> watcher;
435+
QEventLoop loop;
436+
QObject::connect(&watcher, &QFutureWatcher<QString>::finished, &loop, &QEventLoop::quit);
437+
watcher.setFuture(
438+
QtConcurrent::run([text, password]() { return encryptNoteTextWithBotan(text, password); }));
439+
440+
if (!watcher.isFinished()) {
441+
loop.exec();
442+
}
443+
444+
return watcher.result();
445+
}
411446
} // namespace
412447

413448
int Note::getId() const { return this->_id; }
@@ -2245,8 +2280,22 @@ bool Note::storeNoteTextFileToDisk(bool &currentNoteTextChanged,
22452280

22462281
// if we find a decrypted text to encrypt, then we attempt to encrypt it
22472282
if (!_decryptedNoteText.isEmpty()) {
2283+
const QString decryptedNoteText = _decryptedNoteText;
22482284
_noteText = _decryptedNoteText;
2249-
encryptNoteText();
2285+
2286+
encryptNoteText(false);
2287+
2288+
if (_id > 0) {
2289+
const Note latestNote = Note::fetch(_id);
2290+
2291+
if (latestNote.isFetched() && latestNote._decryptedNoteText != decryptedNoteText) {
2292+
qDebug()
2293+
<< __func__
2294+
<< " - encrypted note changed while encryption was running, deferring save";
2295+
return false;
2296+
}
2297+
}
2298+
22502299
_decryptedNoteText = QLatin1String("");
22512300
}
22522301

@@ -4266,7 +4315,7 @@ void Note::resetChecksumStats() {
42664315
/**
42674316
* Encrypts the note text with the note's crypto key
42684317
*/
4269-
QString Note::encryptNoteText() {
4318+
QString Note::encryptNoteText(bool persist) {
42704319
if (_noteText.isEmpty()) {
42714320
return _noteText;
42724321
}
@@ -4314,16 +4363,7 @@ QString Note::encryptNoteText() {
43144363
if (encryptedText.isEmpty()) {
43154364
// fallback to Botan
43164365
// encrypt the text
4317-
BotanWrapper botanWrapper;
4318-
botanWrapper.setPassword(_cryptoPassword);
4319-
const QString salt = BotanWrapper::randomBytesBase64(NoteEncryptionSaltBytes);
4320-
const QString nonce = BotanWrapper::randomBytesBase64(NoteEncryptionNonceBytes);
4321-
QString mac;
4322-
encryptedText =
4323-
botanWrapper.EncryptV2(text, salt, nonce, NoteEncryptionKdfIterations, &mac);
4324-
if (!encryptedText.isEmpty()) {
4325-
encryptedText = buildNoteEncryptionEnvelope(encryptedText, salt, nonce, mac);
4326-
}
4366+
encryptedText = encryptNoteTextWithBotanInBackground(text, _cryptoPassword);
43274367

43284368
// SimpleCrypt *crypto = new
43294369
// SimpleCrypt(static_cast<quint64>(cryptoKey)); QString
@@ -4334,8 +4374,10 @@ QString Note::encryptNoteText() {
43344374
_noteText +=
43354375
encryptedText + QStringLiteral("\n") + QStringLiteral(NOTE_TEXT_ENCRYPTION_POST_STRING);
43364376

4337-
// store note
4338-
store();
4377+
if (persist) {
4378+
// store note
4379+
store();
4380+
}
43394381

43404382
return _noteText;
43414383
}

src/entities/note.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class Note {
189189

190190
static void resetChecksumStats();
191191

192-
QString encryptNoteText();
192+
QString encryptNoteText(bool persist = true);
193193

194194
QString fetchDecryptedNoteText() const;
195195

0 commit comments

Comments
 (0)