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.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.hasKey = false;

View File

@ -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;

View File

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

View File

@ -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"

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_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<QPair<Uuid, QString>> 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<QPair<Uuid, QString>> 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<QPair<Uuid, QString>> KeePass2::KDFS {
qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")),
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")),
const QList<QPair<Uuid, QString>> 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<Kdf> KeePass2::kdfFromParameters(const QVariantMap &p)
{
QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray();
@ -68,7 +77,12 @@ QSharedPointer<Kdf> KeePass2::kdfFromParameters(const QVariantMap &p)
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()) {
return {};
}
@ -87,7 +101,10 @@ QVariantMap KeePass2::kdfToParameters(QSharedPointer<Kdf> kdf)
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();
}
if (uuid == KDF_ARGON2) {

View File

@ -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;

View File

@ -18,8 +18,9 @@
#include <QIODevice>
#include <QFile>
#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);

View File

@ -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"));

View File

@ -22,6 +22,7 @@
#include <format/KeePass2.h>
#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);
}