diff --git a/src/core/Database.cpp b/src/core/Database.cpp index a579ed02d..75b91a5c5 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -44,7 +44,10 @@ Database::Database() { m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; - m_data.kdf = QSharedPointer::create(); + + // instantiate default AES-KDF with legacy KDBX3 flag set + // KDBX4+ will re-initialize the KDF using parameters read from the KDBX file + m_data.kdf = QSharedPointer::create(true); m_data.kdf->randomizeSeed(); m_data.hasKey = false; diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index d668652aa..df4392468 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -23,7 +23,15 @@ #include "crypto/CryptoHash.h" AesKdf::AesKdf() - : Kdf::Kdf(KeePass2::KDF_AES) + : Kdf::Kdf(KeePass2::KDF_AES_KDBX4) +{ +} + +/** + * @param legacyKdbx3 initialize as legacy KDBX3 KDF + */ +AesKdf::AesKdf(bool legacyKdbx3) + : Kdf::Kdf(legacyKdbx3 ? KeePass2::KDF_AES_KDBX3 : KeePass2::KDF_AES_KDBX4) { } @@ -36,17 +44,16 @@ bool AesKdf::processParameters(const QVariantMap &p) } QByteArray seed = p.value(KeePass2::KDFPARAM_AES_SEED).toByteArray(); - if (!setSeed(seed)) { - return false; - } - - return true; + return setSeed(seed); } QVariantMap AesKdf::writeParameters() { QVariantMap p; - p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES.toByteArray()); + + // always write old KDBX3 AES-KDF UUID for compatibility with other applications + p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES_KDBX3.toByteArray()); + p.insert(KeePass2::KDFPARAM_AES_ROUNDS, rounds()); p.insert(KeePass2::KDFPARAM_AES_SEED, seed()); return p; diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 69c15b8af..31ee1fa70 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -24,6 +24,7 @@ class AesKdf: public Kdf { public: AesKdf(); + explicit AesKdf(bool legacyKdbx3); bool processParameters(const QVariantMap& p) override; QVariantMap writeParameters() override; diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 1cde6d18c..b0545888f 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -22,6 +22,7 @@ #include "core/Group.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2RandomStream.h" #include "format/KdbxXmlReader.h" #include "streams/HmacBlockStream.h" diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index aa081e7b5..9f0832fb0 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -25,7 +25,8 @@ const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be58 const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); -const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); +const Uuid KeePass2::KDF_AES_KDBX3 = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); +const Uuid KeePass2::KDF_AES_KDBX4 = Uuid(QByteArray::fromHex("7C02BB8279A74AC0927D114A00648238")); const Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C")); const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); @@ -43,14 +44,16 @@ const QString KeePass2::KDFPARAM_ARGON2_VERSION("V"); const QString KeePass2::KDFPARAM_ARGON2_SECRET("K"); const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); -const QList> KeePass2::CIPHERS { - qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), - qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), - qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) +const QList> KeePass2::CIPHERS{ + qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), + qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), + qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) }; -const QList> KeePass2::KDFS { - qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")), - qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")), + +const QList> KeePass2::KDFS{ + qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (recommended)")), + qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")), + qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)")) }; QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey) { @@ -61,6 +64,12 @@ QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMaster return hmacKeyHash.result(); } +/** + * Create KDF object from KDBX4+ KDF parameters. + * + * @param p variant map containing parameters + * @return initialized KDF + */ QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) { QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); @@ -68,7 +77,12 @@ QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) return {}; } - QSharedPointer kdf(uuidToKdf(Uuid(uuidBytes))); + Uuid kdfUuid(uuidBytes); + if (kdfUuid == KDF_AES_KDBX3) { + // upgrade to non-legacy AES-KDF, since KDBX3 doesn't have any KDF parameters + kdfUuid = KDF_AES_KDBX4; + } + QSharedPointer kdf = uuidToKdf(kdfUuid); if (kdf.isNull()) { return {}; } @@ -87,7 +101,10 @@ QVariantMap KeePass2::kdfToParameters(QSharedPointer kdf) QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { - if (uuid == KDF_AES) { + if (uuid == KDF_AES_KDBX3) { + return QSharedPointer::create(true); + } + if (uuid == KDF_AES_KDBX4) { return QSharedPointer::create(); } if (uuid == KDF_ARGON2) { diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index f7fa0d397..cd53d7132 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -46,7 +46,8 @@ namespace KeePass2 extern const Uuid CIPHER_TWOFISH; extern const Uuid CIPHER_CHACHA20; - extern const Uuid KDF_AES; + extern const Uuid KDF_AES_KDBX3; + extern const Uuid KDF_AES_KDBX4; extern const Uuid KDF_ARGON2; extern const QByteArray INNER_STREAM_SALSA20_IV; diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 3e32eb96b..67aeec98f 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -18,8 +18,9 @@ #include #include -#include "format/KeePass2Writer.h" #include "core/Database.h" +#include "crypto/kdf/AesKdf.h" +#include "format/KeePass2Writer.h" #include "format/Kdbx3Writer.h" #include "format/Kdbx4Writer.h" @@ -52,12 +53,12 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { m_errorStr.clear(); // determine KDBX3 vs KDBX4 - if (db->kdf()->uuid() != KeePass2::KDF_AES || db->publicCustomData().size() > 0) { - m_version = KeePass2::FILE_VERSION_4; - m_writer.reset(new Kdbx4Writer()); - } else { + if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) { m_version = KeePass2::FILE_VERSION_3; m_writer.reset(new Kdbx3Writer()); + } else { + m_version = KeePass2::FILE_VERSION_4; + m_writer.reset(new Kdbx4Writer()); } return m_writer->writeDatabase(device, db); diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 68b0d4ae6..c0cc06296 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -151,7 +151,8 @@ void DatabaseSettingsWidget::save() if (warning.clickedButton() != ok) { return; } - } else if (kdf->uuid() == KeePass2::KDF_AES && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { + } else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4) + && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setWindowTitle(tr("Number of rounds too low")); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 7a9b2cbe0..e5e507c77 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -22,6 +22,7 @@ #include #include "core/Global.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/CryptoHash.h" CompositeKey::CompositeKey() @@ -130,7 +131,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed) const */ bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const { - if (kdf.uuid() == KeePass2::KDF_AES) { + if (kdf.uuid() == KeePass2::KDF_AES_KDBX3) { // legacy KDBX3 AES-KDF, challenge response is added later to the hash return kdf.transform(rawKey(), result); }