@@ -71,6 +71,24 @@ struct ParsedWikiLink {
7171 WikiLinkParts parts;
7272};
7373
74+ struct NoteEncryptionEnvelope {
75+ bool isV2 = false ;
76+ QString cipherText;
77+ QString kdf;
78+ QString cipher;
79+ QString salt;
80+ QString nonce;
81+ QString mac;
82+ size_t iterations = 0 ;
83+ };
84+
85+ constexpr int NoteEncryptionVersion = 2 ;
86+ constexpr int NoteEncryptionSaltBytes = 32 ;
87+ constexpr int NoteEncryptionNonceBytes = 16 ;
88+ constexpr size_t NoteEncryptionKdfIterations = 300000 ;
89+ const auto NoteEncryptionKdf = QStringLiteral(" PBKDF2-HMAC-SHA1" );
90+ const auto NoteEncryptionCipher = QStringLiteral(" AES-256-CBC-PKCS7-HMAC-SHA1" );
91+
7492static const QRegularExpression &wikiLinkRegex () {
7593 static const QRegularExpression regex (QStringLiteral (R"( \[\[([^\[\]]+?)\]\])" ));
7694 return regex;
@@ -317,6 +335,79 @@ static QString postProcessWikiLinksHtml(QString html, int currentNoteSubFolderId
317335
318336 return html;
319337}
338+
339+ static NoteEncryptionEnvelope parseNoteEncryptionEnvelope (const QString &payload) {
340+ NoteEncryptionEnvelope envelope;
341+ envelope.cipherText = payload.trimmed ();
342+
343+ const QStringList lines = payload.trimmed ().split (QChar (' \n ' ));
344+ if (lines.isEmpty () || lines.constFirst ().trimmed () != QStringLiteral (" qon-crypto: 2" )) {
345+ return envelope;
346+ }
347+
348+ QHash<QString, QString> metadata;
349+ int cipherTextLine = -1 ;
350+ for (int i = 0 ; i < lines.count (); ++i) {
351+ const QString line = lines.at (i).trimmed ();
352+ if (line.isEmpty ()) {
353+ cipherTextLine = i + 1 ;
354+ break ;
355+ }
356+
357+ const int separatorIndex = line.indexOf (QChar (' :' ));
358+ if (separatorIndex <= 0 ) {
359+ return envelope;
360+ }
361+
362+ metadata.insert (line.left (separatorIndex).trimmed (),
363+ line.mid (separatorIndex + 1 ).trimmed ());
364+ }
365+
366+ if (cipherTextLine < 0 || cipherTextLine >= lines.count ()) {
367+ return envelope;
368+ }
369+
370+ bool iterationsOk = false ;
371+ const size_t iterations =
372+ metadata.value (QStringLiteral (" kdf-iterations" )).toULongLong (&iterationsOk);
373+ const QString cipherText = lines.mid (cipherTextLine).join (QChar (' \n ' )).trimmed ();
374+
375+ if (!iterationsOk ||
376+ metadata.value (QStringLiteral (" qon-crypto" )) != QString::number (NoteEncryptionVersion) ||
377+ metadata.value (QStringLiteral (" kdf" )) != NoteEncryptionKdf ||
378+ metadata.value (QStringLiteral (" cipher" )) != NoteEncryptionCipher ||
379+ metadata.value (QStringLiteral (" salt" )).isEmpty () ||
380+ metadata.value (QStringLiteral (" nonce" )).isEmpty () ||
381+ metadata.value (QStringLiteral (" mac" )).isEmpty () || cipherText.isEmpty ()) {
382+ return envelope;
383+ }
384+
385+ envelope.isV2 = true ;
386+ envelope.cipherText = cipherText;
387+ envelope.kdf = metadata.value (QStringLiteral (" kdf" ));
388+ envelope.cipher = metadata.value (QStringLiteral (" cipher" ));
389+ envelope.salt = metadata.value (QStringLiteral (" salt" ));
390+ envelope.nonce = metadata.value (QStringLiteral (" nonce" ));
391+ envelope.mac = metadata.value (QStringLiteral (" mac" ));
392+ envelope.iterations = iterations;
393+ return envelope;
394+ }
395+
396+ static QString buildNoteEncryptionEnvelope (const QString &cipherText, const QString &salt,
397+ const QString &nonce, const QString &mac) {
398+ return QStringLiteral (
399+ " qon-crypto: %1\n "
400+ " kdf: %2\n "
401+ " kdf-iterations: %3\n "
402+ " salt: %4\n "
403+ " cipher: %5\n "
404+ " nonce: %6\n "
405+ " mac: %7\n\n "
406+ " %8" )
407+ .arg (QString::number (NoteEncryptionVersion), NoteEncryptionKdf,
408+ QString::number (NoteEncryptionKdfIterations), salt, NoteEncryptionCipher, nonce, mac,
409+ cipherText);
410+ }
320411} // namespace
321412
322413int Note::getId () const { return this ->_id ; }
@@ -4211,8 +4302,14 @@ QString Note::encryptNoteText() {
42114302 // encrypt the text
42124303 BotanWrapper botanWrapper;
42134304 botanWrapper.setPassword (_cryptoPassword);
4214- botanWrapper.setSalt (QStringLiteral (BOTAN_SALT ));
4215- encryptedText = botanWrapper.Encrypt (text);
4305+ const QString salt = BotanWrapper::randomBytesBase64 (NoteEncryptionSaltBytes);
4306+ const QString nonce = BotanWrapper::randomBytesBase64 (NoteEncryptionNonceBytes);
4307+ QString mac;
4308+ encryptedText =
4309+ botanWrapper.EncryptV2 (text, salt, nonce, NoteEncryptionKdfIterations, &mac);
4310+ if (!encryptedText.isEmpty ()) {
4311+ encryptedText = buildNoteEncryptionEnvelope (encryptedText, salt, nonce, mac);
4312+ }
42164313
42174314 // SimpleCrypt *crypto = new
42184315 // SimpleCrypt(static_cast<quint64>(cryptoKey)); QString
@@ -4292,27 +4389,7 @@ bool Note::canDecryptNoteText() const {
42924389 return false ;
42934390 }
42944391
4295- // check if we have an external decryption method
4296- QString decryptedNoteText =
4297- ScriptingService::instance ()->callEncryptionHook (encryptedNoteText, _cryptoPassword, true );
4298-
4299- // check if a hook changed the text
4300- if (decryptedNoteText.isEmpty ()) {
4301- // decrypt the note text with Botan
4302- BotanWrapper botanWrapper;
4303- botanWrapper.setPassword (_cryptoPassword);
4304- botanWrapper.setSalt (QStringLiteral (BOTAN_SALT ));
4305- decryptedNoteText = botanWrapper.Decrypt (encryptedNoteText);
4306-
4307- // fallback to SimpleCrypt
4308- if (decryptedNoteText.isEmpty ()) {
4309- auto *crypto = new SimpleCrypt (static_cast <quint64>(_cryptoKey));
4310- decryptedNoteText = crypto->decryptToString (encryptedNoteText);
4311- delete crypto;
4312- }
4313- }
4314-
4315- return !decryptedNoteText.isEmpty ();
4392+ return !decryptEncryptedNoteText (encryptedNoteText).isEmpty ();
43164393}
43174394
43184395/* *
@@ -4354,24 +4431,7 @@ QString Note::getDecryptedNoteText(
43544431 const QString &encryptedNoteText) const { // check if we have an external decryption method
43554432 QString noteText = getNoteText ();
43564433
4357- QString decryptedNoteText =
4358- ScriptingService::instance ()->callEncryptionHook (encryptedNoteText, _cryptoPassword, true );
4359-
4360- // Check if a hook changed the text
4361- if (decryptedNoteText.isEmpty ()) {
4362- // Decrypt the note text
4363- BotanWrapper botanWrapper;
4364- botanWrapper.setPassword (_cryptoPassword);
4365- botanWrapper.setSalt (QStringLiteral (BOTAN_SALT ));
4366- decryptedNoteText = botanWrapper.Decrypt (encryptedNoteText);
4367-
4368- // Fallback to SimpleCrypt
4369- if (decryptedNoteText.isEmpty ()) {
4370- auto *crypto = new SimpleCrypt (static_cast <quint64>(_cryptoKey));
4371- decryptedNoteText = crypto->decryptToString (encryptedNoteText);
4372- delete crypto;
4373- }
4374- }
4434+ const QString decryptedNoteText = decryptEncryptedNoteText (encryptedNoteText);
43754435
43764436 if (decryptedNoteText.isEmpty ()) {
43774437 return noteText;
@@ -4385,6 +4445,37 @@ QString Note::getDecryptedNoteText(
43854445 return noteText;
43864446}
43874447
4448+ QString Note::decryptEncryptedNoteText (const QString &encryptedNoteText) const {
4449+ QString decryptedNoteText =
4450+ ScriptingService::instance ()->callEncryptionHook (encryptedNoteText, _cryptoPassword, true );
4451+
4452+ if (!decryptedNoteText.isEmpty ()) {
4453+ return decryptedNoteText;
4454+ }
4455+
4456+ const NoteEncryptionEnvelope envelope = parseNoteEncryptionEnvelope (encryptedNoteText);
4457+ BotanWrapper botanWrapper;
4458+ botanWrapper.setPassword (_cryptoPassword);
4459+
4460+ if (envelope.isV2 ) {
4461+ return botanWrapper.DecryptV2 (envelope.cipherText , envelope.salt , envelope.nonce ,
4462+ envelope.mac , envelope.iterations );
4463+ }
4464+
4465+ botanWrapper.setSalt (QStringLiteral (BOTAN_SALT ));
4466+ decryptedNoteText = botanWrapper.Decrypt (encryptedNoteText);
4467+
4468+ if (!decryptedNoteText.isEmpty ()) {
4469+ return decryptedNoteText;
4470+ }
4471+
4472+ auto *crypto = new SimpleCrypt (static_cast <quint64>(_cryptoKey));
4473+ decryptedNoteText = crypto->decryptToString (encryptedNoteText);
4474+ delete crypto;
4475+
4476+ return decryptedNoteText;
4477+ }
4478+
43884479/* *
43894480 * Expire crypto keys in the database after 10min
43904481 */
0 commit comments