From 2e19af5032513f3041e1c8936d629b52a8737304 Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 20:20:57 +0800 Subject: [PATCH] Pull out KDFs into their own class hierarchy In preparation for multiple KDFs in KDBX 4 --- src/CMakeLists.txt | 4 +- src/core/Database.cpp | 103 ++++++++--------- src/core/Database.h | 22 ++-- src/crypto/kdf/AesKdf.cpp | 171 +++++++++++++++++++++++++++++ src/crypto/kdf/AesKdf.h | 62 +++++++++++ src/crypto/kdf/Kdf.cpp | 86 +++++++++++++++ src/crypto/kdf/Kdf.h | 69 ++++++++++++ src/crypto/kdf/Kdf_p.h | 43 ++++++++ src/format/KeePass1Reader.cpp | 18 +-- src/format/KeePass2Reader.cpp | 32 ++++-- src/format/KeePass2Reader.h | 1 - src/format/KeePass2Writer.cpp | 9 +- src/gui/DatabaseSettingsWidget.cpp | 10 +- src/keys/CompositeKey.cpp | 112 +------------------ src/keys/CompositeKey.h | 9 +- src/keys/CompositeKey_p.h | 39 ------- tests/TestKeePass1Reader.cpp | 3 +- tests/TestKeys.cpp | 27 +++-- 18 files changed, 555 insertions(+), 265 deletions(-) create mode 100644 src/crypto/kdf/AesKdf.cpp create mode 100644 src/crypto/kdf/AesKdf.h create mode 100644 src/crypto/kdf/Kdf.cpp create mode 100644 src/crypto/kdf/Kdf.h create mode 100644 src/crypto/kdf/Kdf_p.h delete mode 100644 src/keys/CompositeKey_p.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2dca9e606..5ee71a28a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,9 @@ set(keepassx_SOURCES crypto/SymmetricCipher.cpp crypto/SymmetricCipherBackend.h crypto/SymmetricCipherGcrypt.cpp + crypto/kdf/Kdf.cpp + crypto/kdf/Kdf_p.h + crypto/kdf/AesKdf.cpp format/CsvExporter.cpp format/KeePass1.h format/KeePass1Reader.cpp @@ -139,7 +142,6 @@ set(keepassx_SOURCES gui/group/GroupModel.cpp gui/group/GroupView.cpp keys/CompositeKey.cpp - keys/CompositeKey_p.h keys/drivers/YubiKey.h keys/FileKey.cpp keys/Key.h diff --git a/src/core/Database.cpp b/src/core/Database.cpp index cb28ee211..45f2b479f 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -27,7 +27,7 @@ #include "cli/Utils.h" #include "core/Group.h" #include "core/Metadata.h" -#include "crypto/Random.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -45,7 +45,8 @@ Database::Database() { m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; - m_data.transformRounds = 100000; + m_data.kdf = new AesKdf(); + m_data.kdf->randomizeTransformSalt(); m_data.hasKey = false; setRootGroup(new Group()); @@ -225,16 +226,6 @@ Database::CompressionAlgorithm Database::compressionAlgo() const return m_data.compressionAlgo; } -QByteArray Database::transformSeed() const -{ - return m_data.transformSeed; -} - -quint64 Database::transformRounds() const -{ - return m_data.transformRounds; -} - QByteArray Database::transformedMasterKey() const { return m_data.transformedMasterKey; @@ -265,36 +256,18 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) m_data.compressionAlgo = algo; } -bool Database::setTransformRounds(quint64 rounds) +bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt) { - if (m_data.transformRounds != rounds) { - quint64 oldRounds = m_data.transformRounds; - - m_data.transformRounds = rounds; - - if (m_data.hasKey) { - if (!setKey(m_data.key)) { - m_data.transformRounds = oldRounds; - return false; - } - } + if (updateTransformSalt) { + m_data.kdf->randomizeTransformSalt(); } - return true; -} - -bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime) -{ - bool ok; - QString errorString; - - QByteArray transformedMasterKey = key.transform(transformSeed, transformRounds(), &ok, &errorString); - if (!ok) { + QByteArray transformedMasterKey; + if (!key.transform(*m_data.kdf, transformedMasterKey)) { return false; } m_data.key = key; - m_data.transformSeed = transformSeed; m_data.transformedMasterKey = transformedMasterKey; m_data.hasKey = true; if (updateChangedTime) { @@ -305,35 +278,11 @@ bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, return true; } -bool Database::setKey(const CompositeKey& key) -{ - return setKey(key, randomGen()->randomArray(32)); -} - bool Database::hasKey() const { return m_data.hasKey; } -bool Database::transformKeyWithSeed(const QByteArray& transformSeed) -{ - Q_ASSERT(hasKey()); - - bool ok; - QString errorString; - - QByteArray transformedMasterKey = - m_data.key.transform(transformSeed, transformRounds(), &ok, &errorString); - if (!ok) { - return false; - } - - m_data.transformSeed = transformSeed; - m_data.transformedMasterKey = transformedMasterKey; - - return true; -} - bool Database::verifyKey(const CompositeKey& key) const { Q_ASSERT(hasKey()); @@ -429,6 +378,7 @@ void Database::setEmitModified(bool value) void Database::copyAttributesFrom(const Database* other) { m_data = other->m_data; + m_data.kdf = m_data.kdf->clone(); m_metadata->copyAttributesFrom(other->m_metadata); } @@ -534,3 +484,38 @@ QString Database::saveToFile(QString filePath) return saveFile.errorString(); } } + +Kdf* Database::kdf() { + return m_data.kdf; +} + +void Database::setKdf(Kdf* kdf) { + if (m_data.kdf == kdf) { + return; + } + + if (m_data.kdf != nullptr) { + delete m_data.kdf; + } + + m_data.kdf = kdf; +} + +bool Database::changeKdf(Kdf* kdf) { + kdf->randomizeTransformSalt(); + QByteArray transformedMasterKey; + if (!m_data.key.transform(*kdf, transformedMasterKey)) { + return false; + } + + setKdf(kdf); + m_data.transformedMasterKey = transformedMasterKey; + emit modifiedImmediate(); + + return true; +} + +Database::DatabaseData::~DatabaseData() +{ + delete kdf; +} diff --git a/src/core/Database.h b/src/core/Database.h index b20f897fe..dbb48718e 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -23,6 +23,7 @@ #include #include +#include "crypto/kdf/Kdf.h" #include "core/Uuid.h" #include "keys/CompositeKey.h" @@ -56,13 +57,14 @@ public: { Uuid cipher; CompressionAlgorithm compressionAlgo; - QByteArray transformSeed; - quint64 transformRounds; QByteArray transformedMasterKey; + Kdf* kdf; CompositeKey key; bool hasKey; QByteArray masterSeed; QByteArray challengeResponseKey; + + ~DatabaseData(); }; Database(); @@ -90,8 +92,7 @@ public: Uuid cipher() const; Database::CompressionAlgorithm compressionAlgo() const; - QByteArray transformSeed() const; - quint64 transformRounds() const; + Kdf* kdf(); QByteArray transformedMasterKey() const; const CompositeKey& key() const; QByteArray challengeResponseKey() const; @@ -99,16 +100,10 @@ public: void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); - bool setTransformRounds(quint64 rounds); - bool setKey(const CompositeKey& key, const QByteArray& transformSeed, - bool updateChangedTime = true); - - /** - * Sets the database key and generates a random transform seed. - */ - bool setKey(const CompositeKey& key); + void setKdf(Kdf* kdf); + bool setKey(const CompositeKey& key, bool updateChangedTime = true, + bool updateTransformSalt = false); bool hasKey() const; - bool transformKeyWithSeed(const QByteArray& transformSeed); bool verifyKey(const CompositeKey& key) const; void recycleEntry(Entry* entry); void recycleGroup(Group* group); @@ -122,6 +117,7 @@ public: * Returns a unique id that is only valid as long as the Database exists. */ Uuid uuid(); + bool changeKdf(Kdf* kdf); static Database* databaseByUuid(const Uuid& uuid); static Database* openDatabaseFile(QString fileName, CompositeKey key); diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp new file mode 100644 index 000000000..4380a782e --- /dev/null +++ b/src/crypto/kdf/AesKdf.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "format/KeePass2.h" +#include "crypto/SymmetricCipher.h" +#include "crypto/CryptoHash.h" +#include "crypto/Random.h" +#include "AesKdf.h" + +const QList AesKdf::FIELDS = AesKdf::initFields(); + +QList AesKdf::initFields() +{ + return QList { + Kdf::Field(static_cast(Fields::ROUNDS), "Transform rounds", 1, UINT64_MAX, true), + }; +} + +bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const +{ + QByteArray resultLeft; + QByteArray resultRight; + + QFuture future = QtConcurrent::run(transformKeyRaw, raw.left(16), m_seed, m_rounds, &resultLeft); + + bool rightResult = transformKeyRaw(raw.right(16), m_seed, m_rounds, &resultRight); + bool leftResult = future.result(); + + if (!rightResult || !leftResult) { + return false; + } + + QByteArray transformed; + transformed.append(resultLeft); + transformed.append(resultRight); + + result = CryptoHash::hash(transformed, CryptoHash::Sha256); + return true; +} + +bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) +{ + QByteArray iv(16, 0); + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, + SymmetricCipher::Encrypt); + if (!cipher.init(seed, iv)) { + qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::init: %s", cipher.errorString().toUtf8().data()); + return false; + } + + *result = key; + + if (!cipher.processInPlace(*result, rounds)) { + qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s", cipher.errorString().toUtf8().data()); + return false; + } + + return true; +} + +AesKdf::AesKdf() + : m_rounds(100000ull) + , m_seed(QByteArray(32, 0)) +{ +} + +quint64 AesKdf::rounds() const +{ + return m_rounds; +} + +QByteArray AesKdf::seed() const +{ + return m_seed; +} + +bool AesKdf::setRounds(quint64 rounds) +{ + m_rounds = rounds; + return true; +} + +bool AesKdf::setSeed(const QByteArray& seed) +{ + if (seed.size() != 32) { + return false; + } + + m_seed = seed; + return true; +} + +void AesKdf::randomizeTransformSalt() +{ + setSeed(randomGen()->randomArray(32)); +} + +Kdf::Type AesKdf::type() const +{ + return Kdf::Type::AES; +} + +Kdf* AesKdf::clone() const +{ + return static_cast(new AesKdf(*this)); +} + +const QList AesKdf::fields() const +{ + return FIELDS; +} + +quint64 AesKdf::field(quint32 id) const +{ + switch (static_cast(id)) { + case Fields::ROUNDS: + return m_rounds; + default: + return 0; + } +} + +bool AesKdf::setField(quint32 id, quint64 val) +{ + switch (static_cast(id)) { + case Fields::ROUNDS: + return setRounds(val); + default: + return false; + } +} + +int AesKdf::benchmarkImpl(int msec) const +{ + QByteArray key = QByteArray(16, '\x7E'); + QByteArray seed = QByteArray(32, '\x4B'); + QByteArray iv(16, 0); + + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, + SymmetricCipher::Encrypt); + cipher.init(seed, iv); + + int rounds = 0; + QElapsedTimer t; + t.start(); + do { + if (!cipher.processInPlace(key, 10000)) { + rounds = -1; + break; + } + rounds += 10000; + } while (!t.hasExpired(msec)); + + return rounds; +} \ No newline at end of file diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h new file mode 100644 index 000000000..3aebc8d50 --- /dev/null +++ b/src/crypto/kdf/AesKdf.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_AESKDF_H +#define KEEPASSX_AESKDF_H + +#include "Kdf.h" + +class AesKdf : public Kdf +{ +public: + AesKdf(); + + bool transform(const QByteArray& raw, QByteArray& result) const override; + void randomizeTransformSalt() override; + Kdf::Type type() const override; + Kdf* clone() const override; + + const QList fields() const override; + quint64 field(quint32 id) const override; + bool setField(quint32 id, quint64 val) override; + + quint64 rounds() const; + QByteArray seed() const; + + bool setRounds(quint64 rounds); + bool setSeed(const QByteArray& seed); + + enum class Fields : quint32 + { + ROUNDS, + SEED + }; + + static const QList FIELDS; + +protected: + int benchmarkImpl(int msec) const override; + +private: + quint64 m_rounds; + QByteArray m_seed; + + static bool transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) Q_REQUIRED_RESULT; + static QList initFields(); +}; + +#endif // KEEPASSX_AESKDF_H diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp new file mode 100644 index 000000000..99f12645c --- /dev/null +++ b/src/crypto/kdf/Kdf.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdf.h" +#include "Kdf_p.h" + +#include +#include +#include + +Kdf::Field::Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark) + : m_id(id) + , m_name(name) + , m_min(min) + , m_max(max) + , m_benchmark(benchmark) +{ +} + +quint32 Kdf::Field::id() const +{ + return m_id; +} + +QString Kdf::Field::name() const +{ + return m_name; +} + +quint64 Kdf::Field::min() const +{ + return m_min; +} + +quint64 Kdf::Field::max() const +{ + return m_max; +} + +bool Kdf::Field::benchmarked() const +{ + return m_benchmark; +} + +int Kdf::benchmark(int msec) const +{ + BenchmarkThread thread1(msec, this); + BenchmarkThread thread2(msec, this); + + thread1.start(); + thread2.start(); + + thread1.wait(); + thread2.wait(); + + return qMin(thread1.rounds(), thread2.rounds()); +} + +Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) + : m_msec(msec) + , m_kdf(kdf) +{ +} + +int Kdf::BenchmarkThread::rounds() +{ + return m_rounds; +} + +void Kdf::BenchmarkThread::run() { + m_rounds = m_kdf->benchmarkImpl(m_msec); +} diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h new file mode 100644 index 000000000..c032a030c --- /dev/null +++ b/src/crypto/kdf/Kdf.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDF_H +#define KEEPASSX_KDF_H + +#include + +#include "core/Uuid.h" + +class Kdf +{ +public: + enum class Type { + AES + }; + + class Field { + public: + Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark = false); + + quint32 id() const; + QString name() const; + quint64 min() const; + quint64 max() const; + bool benchmarked() const; + + private: + quint32 m_id; + QString m_name; + quint64 m_min; + quint64 m_max; + bool m_benchmark; + }; + + virtual ~Kdf() {} + virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; + virtual void randomizeTransformSalt() = 0; + virtual Type type() const = 0; + virtual Kdf* clone() const = 0; + + virtual const QList fields() const = 0; + virtual quint64 field(quint32 id) const = 0; + virtual bool setField(quint32 id, quint64 val) = 0; + virtual int benchmark(int msec) const; + +protected: + virtual int benchmarkImpl(int msec) const = 0; + +private: + class BenchmarkThread; + +}; + +#endif // KEEPASSX_KDF_H diff --git a/src/crypto/kdf/Kdf_p.h b/src/crypto/kdf/Kdf_p.h new file mode 100644 index 000000000..c74837522 --- /dev/null +++ b/src/crypto/kdf/Kdf_p.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdf.h" + +#include + +#ifndef KEEPASSXC_KDF_P_H +#define KEEPASSXC_KDF_P_H + +class Kdf::BenchmarkThread : public QThread +{ + Q_OBJECT + +public: + explicit BenchmarkThread(int msec, const Kdf* kdf); + + int rounds(); + +protected: + void run(); + +private: + int m_rounds; + int m_msec; + const Kdf* m_kdf; +}; + +#endif // KEEPASSXC_KDF_P_H diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index 4747d7f87..512b0b58b 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -21,6 +21,7 @@ #include #include +#include "crypto/kdf/AesKdf.h" #include "core/Database.h" #include "core/Endian.h" #include "core/Entry.h" @@ -159,10 +160,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor raiseError("Invalid number of transform rounds"); return nullptr; } - if (!m_db->setTransformRounds(m_transformRounds)) { - raiseError(tr("Unable to calculate master key")); - return nullptr; - } + AesKdf* kdf = new AesKdf(); + kdf->setRounds(m_transformRounds); + kdf->setSeed(m_transformSeed); + db->setKdf(kdf); qint64 contentPos = m_device->pos(); @@ -397,12 +398,11 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key key.setPassword(password); key.setKeyfileData(keyfileData); - bool ok; - QString errorString; - QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString); + QByteArray transformedKey; + bool result = key.transform(*m_db->kdf(), transformedKey); - if (!ok) { - raiseError(errorString); + if (!result) { + raiseError("Key transformation failed"); return QByteArray(); } diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index bb737beda..60ebfdc82 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -24,6 +24,7 @@ #include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass1.h" #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" @@ -53,7 +54,6 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke m_headerEnd = false; m_xmlData.clear(); m_masterSeed.clear(); - m_transformSeed.clear(); m_encryptionIV.clear(); m_streamStartBytes.clear(); m_protectedStreamKey.clear(); @@ -101,14 +101,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke } // check if all required headers were present - if (m_masterSeed.isEmpty() || m_transformSeed.isEmpty() || m_encryptionIV.isEmpty() + if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() || m_db->cipher().isNull()) { raiseError("missing database headers"); return nullptr; } - if (!m_db->setKey(key, m_transformSeed, false)) { + if (!m_db->setKey(key, false)) { raiseError(tr("Unable to calculate master key")); return nullptr; } @@ -378,7 +378,15 @@ void KeePass2Reader::setTransformSeed(const QByteArray& data) raiseError("Invalid transform seed size"); } else { - m_transformSeed = data; + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setSeed(data); } } @@ -388,10 +396,18 @@ void KeePass2Reader::setTransformRounds(const QByteArray& data) raiseError("Invalid transform rounds size"); } else { - if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) { - raiseError(tr("Unable to calculate master key")); - } - } + quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER); + + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setRounds(rounds); + } } void KeePass2Reader::setEncryptionIV(const QByteArray& data) diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index f8b962535..f82b4464a 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -64,7 +64,6 @@ private: Database* m_db; QByteArray m_masterSeed; - QByteArray m_transformSeed; QByteArray m_encryptionIV; QByteArray m_streamStartBytes; QByteArray m_protectedStreamKey; diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index f8f60f11e..0d78dc9a8 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -24,6 +24,7 @@ #include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/Random.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2XmlWriter.h" @@ -45,7 +46,6 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) m_error = false; m_errorStr.clear(); - QByteArray transformSeed = randomGen()->randomArray(32); QByteArray masterSeed = randomGen()->randomArray(32); QByteArray encryptionIV = randomGen()->randomArray(16); QByteArray protectedStreamKey = randomGen()->randomArray(32); @@ -57,7 +57,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) return; } - if (!db->transformKeyWithSeed(transformSeed)) { + if (!db->setKey(db->key(), false, true)) { raiseError(tr("Unable to calculate master key")); return; } @@ -81,10 +81,11 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags, Endian::int32ToBytes(db->compressionAlgo(), KeePass2::BYTEORDER))); + AesKdf* kdf = static_cast(db->kdf()); CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed)); - CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed())); + CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds, - Endian::int64ToBytes(db->transformRounds(), + Endian::int64ToBytes(kdf->rounds(), KeePass2::BYTEORDER))); CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 7c51edfd4..dd18f3e0e 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -22,6 +22,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/SymmetricCipher.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2.h" #include "keys/CompositeKey.h" @@ -56,7 +57,7 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher())); - m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds()); + m_ui->transformRoundsSpinBox->setValue(static_cast(m_db->kdf())->rounds()); if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); m_ui->historyMaxItemsCheckBox->setChecked(true); @@ -88,9 +89,10 @@ void DatabaseSettingsWidget::save() m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast (m_ui->AlgorithmComboBox->currentIndex()))); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); - if (static_cast(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) { + AesKdf* kdf = static_cast(m_db->kdf()); + if (static_cast(m_ui->transformRoundsSpinBox->value()) != kdf->rounds()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_db->setTransformRounds(m_ui->transformRoundsSpinBox->value()); + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); QApplication::restoreOverrideCursor(); } @@ -135,7 +137,7 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - int rounds = CompositeKey::transformKeyBenchmark(1000); + int rounds = m_db->kdf()->benchmark(1000); if (rounds != -1) { m_ui->transformRoundsSpinBox->setValue(rounds); } diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 83a6725c9..88ac4cf6a 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -17,13 +17,14 @@ */ #include "CompositeKey.h" -#include "CompositeKey_p.h" #include "ChallengeResponseKey.h" #include #include #include +#include "crypto/kdf/Kdf.h" +#include "format/KeePass2.h" #include "core/Global.h" #include "crypto/CryptoHash.h" #include "crypto/SymmetricCipher.h" @@ -91,64 +92,9 @@ QByteArray CompositeKey::rawKey() const return cryptoHash.result(); } -QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds, - bool* ok, QString* errorString) const +bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const { - Q_ASSERT(seed.size() == 32); - Q_ASSERT(rounds > 0); - - bool okLeft; - QString errorStringLeft; - bool okRight; - QString errorStringRight; - - QByteArray key = rawKey(); - - QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, - &okLeft, &errorStringLeft); - QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight); - - QByteArray transformed; - transformed.append(future.result()); - transformed.append(result2); - - *ok = (okLeft && okRight); - - if (!okLeft) { - *errorString = errorStringLeft; - return QByteArray(); - } - - if (!okRight) { - *errorString = errorStringRight; - return QByteArray(); - } - - return CryptoHash::hash(transformed, CryptoHash::Sha256); -} - -QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds, bool* ok, QString* errorString) -{ - QByteArray iv(16, 0); - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt); - if (!cipher.init(seed, iv)) { - *ok = false; - *errorString = cipher.errorString(); - return QByteArray(); - } - - QByteArray result = key; - - if (!cipher.processInPlace(result, rounds)) { - *ok = false; - *errorString = cipher.errorString(); - return QByteArray(); - } - - *ok = true; - return result; + return kdf.transform(rawKey(), result); } bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const @@ -183,53 +129,3 @@ void CompositeKey::addChallengeResponseKey(QSharedPointer { m_challengeResponseKeys.append(key); } - - -int CompositeKey::transformKeyBenchmark(int msec) -{ - TransformKeyBenchmarkThread thread1(msec); - TransformKeyBenchmarkThread thread2(msec); - - thread1.start(); - thread2.start(); - - thread1.wait(); - thread2.wait(); - - return qMin(thread1.rounds(), thread2.rounds()); -} - - -TransformKeyBenchmarkThread::TransformKeyBenchmarkThread(int msec) - : m_msec(msec) - , m_rounds(0) -{ - Q_ASSERT(msec > 0); -} - -int TransformKeyBenchmarkThread::rounds() -{ - return m_rounds; -} - -void TransformKeyBenchmarkThread::run() -{ - QByteArray key = QByteArray(16, '\x7E'); - QByteArray seed = QByteArray(32, '\x4B'); - QByteArray iv(16, 0); - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt); - cipher.init(seed, iv); - - QElapsedTimer t; - t.start(); - - do { - if (!cipher.processInPlace(key, 10000)) { - m_rounds = -1; - return; - } - m_rounds += 10000; - } while (!t.hasExpired(m_msec)); -} diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index d9c4e3a9e..35bc309fa 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -23,6 +23,7 @@ #include #include +#include "crypto/kdf/Kdf.h" #include "keys/Key.h" #include "keys/ChallengeResponseKey.h" @@ -38,19 +39,13 @@ public: CompositeKey& operator=(const CompositeKey& key); QByteArray rawKey() const; - QByteArray transform(const QByteArray& seed, quint64 rounds, - bool* ok, QString* errorString) const; + bool transform(const Kdf& kdf, QByteArray& result) const Q_REQUIRED_RESULT; bool challenge(const QByteArray& seed, QByteArray &result) const; void addKey(const Key& key); void addChallengeResponseKey(QSharedPointer key); - static int transformKeyBenchmark(int msec); - private: - static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds, bool* ok, QString* errorString); - QList m_keys; QList> m_challengeResponseKeys; }; diff --git a/src/keys/CompositeKey_p.h b/src/keys/CompositeKey_p.h deleted file mode 100644 index 27fa7abc4..000000000 --- a/src/keys/CompositeKey_p.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (C) 2012 Felix Geyer -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 2 or (at your option) -* version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#ifndef KEEPASSX_COMPOSITEKEY_P_H -#define KEEPASSX_COMPOSITEKEY_P_H - -#include - -class TransformKeyBenchmarkThread : public QThread -{ - Q_OBJECT - -public: - explicit TransformKeyBenchmarkThread(int msec); - int rounds(); - -protected: - void run(); - -private: - int m_msec; - int m_rounds; -}; - -#endif // KEEPASSX_COMPOSITEKEY_P_H diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index f60846d26..7e015c2bc 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -26,6 +26,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass1Reader.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -110,7 +111,7 @@ void TestKeePass1Reader::testBasic() void TestKeePass1Reader::testMasterKey() { QVERIFY(m_db->hasKey()); - QCOMPARE(m_db->transformRounds(), static_cast(713)); + QCOMPARE(static_cast(m_db->kdf())->rounds(), static_cast(713)); } void TestKeePass1Reader::testCustomIcons() diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index 95a72a49f..990878e2d 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -27,6 +27,7 @@ #include "core/Metadata.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/CryptoHash.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -45,21 +46,23 @@ void TestKeys::testComposite() QScopedPointer compositeKey1(new CompositeKey()); QScopedPointer passwordKey1(new PasswordKey()); QScopedPointer passwordKey2(new PasswordKey("test")); - bool ok; - QString errorString; // make sure that addKey() creates a copy of the keys compositeKey1->addKey(*passwordKey1); compositeKey1->addKey(*passwordKey2); - QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString); - QVERIFY(ok); - QCOMPARE(transformed.size(), 32); + AesKdf kdf; + kdf.setRounds(1); + QByteArray transformed1; + QVERIFY(compositeKey1->transform(kdf, transformed1)); + QCOMPARE(transformed1.size(), 32); // make sure the subkeys are copied QScopedPointer compositeKey2(compositeKey1->clone()); - QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed); - QVERIFY(ok); + QByteArray transformed2; + QVERIFY(compositeKey2->transform(kdf, transformed2)); + QCOMPARE(transformed2.size(), 32); + QCOMPARE(transformed1, transformed2); QScopedPointer compositeKey3(new CompositeKey()); QScopedPointer compositeKey4(new CompositeKey()); @@ -208,10 +211,12 @@ void TestKeys::benchmarkTransformKey() QByteArray seed(32, '\x4B'); - bool ok; - QString errorString; + QByteArray result; + AesKdf kdf; + kdf.setSeed(seed); + kdf.setRounds(1e6); QBENCHMARK { - compositeKey.transform(seed, 1e6, &ok, &errorString); - } + Q_UNUSED(compositeKey.transform(kdf, result)); + }; }