Explicitly support AES-KDF in KDBX4 and don't convert KDBX4 files with AES-KDF back to KDBX3 when saving

This commit is contained in:
Janek Bevendorff 2018-01-10 21:24:53 +01:00 committed by Jonathan White
parent d327c16904
commit b04c8c2b6e
No known key found for this signature in database
GPG key ID: 440FC65F2E0C6E01
9 changed files with 59 additions and 26 deletions

View file

@ -44,7 +44,10 @@ Database::Database()
{ {
m_data.cipher = KeePass2::CIPHER_AES; m_data.cipher = KeePass2::CIPHER_AES;
m_data.compressionAlgo = CompressionGZip; m_data.compressionAlgo = CompressionGZip;
m_data.kdf = QSharedPointer<AesKdf>::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<AesKdf>::create(true);
m_data.kdf->randomizeSeed(); m_data.kdf->randomizeSeed();
m_data.hasKey = false; m_data.hasKey = false;

View file

@ -23,7 +23,15 @@
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
AesKdf::AesKdf() 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(); QByteArray seed = p.value(KeePass2::KDFPARAM_AES_SEED).toByteArray();
if (!setSeed(seed)) { return setSeed(seed);
return false;
}
return true;
} }
QVariantMap AesKdf::writeParameters() QVariantMap AesKdf::writeParameters()
{ {
QVariantMap p; 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_ROUNDS, rounds());
p.insert(KeePass2::KDFPARAM_AES_SEED, seed()); p.insert(KeePass2::KDFPARAM_AES_SEED, seed());
return p; return p;

View file

@ -24,6 +24,7 @@ class AesKdf: public Kdf
{ {
public: public:
AesKdf(); AesKdf();
explicit AesKdf(bool legacyKdbx3);
bool processParameters(const QVariantMap& p) override; bool processParameters(const QVariantMap& p) override;
QVariantMap writeParameters() override; QVariantMap writeParameters() override;

View file

@ -22,6 +22,7 @@
#include "core/Group.h" #include "core/Group.h"
#include "core/Endian.h" #include "core/Endian.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2RandomStream.h" #include "format/KeePass2RandomStream.h"
#include "format/KdbxXmlReader.h" #include "format/KdbxXmlReader.h"
#include "streams/HmacBlockStream.h" #include "streams/HmacBlockStream.h"

View file

@ -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_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c"));
const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); 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 Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C"));
const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); 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_SECRET("K");
const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A");
const QList<QPair<Uuid, QString>> KeePass2::CIPHERS { const QList<QPair<Uuid, QString>> KeePass2::CIPHERS{
qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")),
qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")),
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit"))
}; };
const QList<QPair<Uuid, QString>> KeePass2::KDFS {
qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")), const QList<QPair<Uuid, QString>> KeePass2::KDFS{
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")), 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) { QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey) {
@ -61,6 +64,12 @@ QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMaster
return hmacKeyHash.result(); return hmacKeyHash.result();
} }
/**
* Create KDF object from KDBX4+ KDF parameters.
*
* @param p variant map containing parameters
* @return initialized KDF
*/
QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap &p) QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap &p)
{ {
QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray();
@ -68,7 +77,12 @@ QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap &p)
return {}; return {};
} }
QSharedPointer<Kdf> 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> kdf = uuidToKdf(kdfUuid);
if (kdf.isNull()) { if (kdf.isNull()) {
return {}; return {};
} }
@ -87,7 +101,10 @@ QVariantMap KeePass2::kdfToParameters(QSharedPointer<Kdf> kdf)
QSharedPointer<Kdf> KeePass2::uuidToKdf(const Uuid& uuid) QSharedPointer<Kdf> KeePass2::uuidToKdf(const Uuid& uuid)
{ {
if (uuid == KDF_AES) { if (uuid == KDF_AES_KDBX3) {
return QSharedPointer<AesKdf>::create(true);
}
if (uuid == KDF_AES_KDBX4) {
return QSharedPointer<AesKdf>::create(); return QSharedPointer<AesKdf>::create();
} }
if (uuid == KDF_ARGON2) { if (uuid == KDF_ARGON2) {

View file

@ -46,7 +46,8 @@ namespace KeePass2
extern const Uuid CIPHER_TWOFISH; extern const Uuid CIPHER_TWOFISH;
extern const Uuid CIPHER_CHACHA20; 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 Uuid KDF_ARGON2;
extern const QByteArray INNER_STREAM_SALSA20_IV; extern const QByteArray INNER_STREAM_SALSA20_IV;

View file

@ -18,8 +18,9 @@
#include <QIODevice> #include <QIODevice>
#include <QFile> #include <QFile>
#include "format/KeePass2Writer.h"
#include "core/Database.h" #include "core/Database.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2Writer.h"
#include "format/Kdbx3Writer.h" #include "format/Kdbx3Writer.h"
#include "format/Kdbx4Writer.h" #include "format/Kdbx4Writer.h"
@ -52,12 +53,12 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
m_errorStr.clear(); m_errorStr.clear();
// determine KDBX3 vs KDBX4 // determine KDBX3 vs KDBX4
if (db->kdf()->uuid() != KeePass2::KDF_AES || db->publicCustomData().size() > 0) { if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) {
m_version = KeePass2::FILE_VERSION_4;
m_writer.reset(new Kdbx4Writer());
} else {
m_version = KeePass2::FILE_VERSION_3; m_version = KeePass2::FILE_VERSION_3;
m_writer.reset(new Kdbx3Writer()); m_writer.reset(new Kdbx3Writer());
} else {
m_version = KeePass2::FILE_VERSION_4;
m_writer.reset(new Kdbx4Writer());
} }
return m_writer->writeDatabase(device, db); return m_writer->writeDatabase(device, db);

View file

@ -151,7 +151,8 @@ void DatabaseSettingsWidget::save()
if (warning.clickedButton() != ok) { if (warning.clickedButton() != ok) {
return; 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; QMessageBox warning;
warning.setIcon(QMessageBox::Warning); warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too low")); warning.setWindowTitle(tr("Number of rounds too low"));

View file

@ -22,6 +22,7 @@
#include <format/KeePass2.h> #include <format/KeePass2.h>
#include "core/Global.h" #include "core/Global.h"
#include "crypto/kdf/AesKdf.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
CompositeKey::CompositeKey() CompositeKey::CompositeKey()
@ -130,7 +131,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed) const
*/ */
bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) 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 // legacy KDBX3 AES-KDF, challenge response is added later to the hash
return kdf.transform(rawKey(), result); return kdf.transform(rawKey(), result);
} }