Skip to content

Commit

Permalink
Add Argon2id KDF (backport of #5726)
Browse files Browse the repository at this point in the history
  • Loading branch information
phoerious committed Dec 3, 2020
1 parent e9b9582 commit 4727b92
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 57 deletions.
2 changes: 1 addition & 1 deletion src/cli/Import.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ int Import::execute(const QStringList& arguments)

QString errorMessage;
Database db;
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db.setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
db.setKey(key);

if (!db.import(xmlExportPath, &errorMessage)) {
Expand Down
32 changes: 25 additions & 7 deletions src/crypto/kdf/Argon2Kdf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
* a 256-bit salt is generated each time the database is saved, the tag length is 256 bits, no secret key
* or associated data. KeePass uses the latest version of Argon2, v1.3.
*/
Argon2Kdf::Argon2Kdf()
: Kdf::Kdf(KeePass2::KDF_ARGON2)
Argon2Kdf::Argon2Kdf(Type type)
: Kdf::Kdf(KeePass2::KDF_ARGON2D)
, m_type(type)
, m_version(0x13)
, m_memory(1 << 16)
, m_parallelism(static_cast<quint32>(QThread::idealThreadCount()))
Expand All @@ -54,6 +55,16 @@ bool Argon2Kdf::setVersion(quint32 version)
return false;
}

Argon2Kdf::Type Argon2Kdf::type() const
{
return m_type;
}

void Argon2Kdf::setType(Type type)
{
m_type = type;
}

quint64 Argon2Kdf::memory() const
{
return m_memory;
Expand Down Expand Up @@ -133,7 +144,11 @@ bool Argon2Kdf::processParameters(const QVariantMap& p)
QVariantMap Argon2Kdf::writeParameters()
{
QVariantMap p;
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2.toRfc4122());
if (type() == Type::Argon2d) {
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2D.toRfc4122());
} else {
p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2ID.toRfc4122());
}
p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version());
p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism());
p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024);
Expand All @@ -158,18 +173,20 @@ bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const
{
result.clear();
result.resize(32);
return transformKeyRaw(raw, seed(), version(), rounds(), memory(), parallelism(), result);
return transformKeyRaw(raw, seed(), version(), type(), rounds(), memory(), parallelism(), result);
}

bool Argon2Kdf::transformKeyRaw(const QByteArray& key,
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,
QByteArray& result)
{
// Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length

int rc = argon2_hash(rounds,
memory,
parallelism,
Expand All @@ -181,7 +198,7 @@ bool Argon2Kdf::transformKeyRaw(const QByteArray& key,
result.size(),
nullptr,
0,
Argon2_d,
type == Type::Argon2d ? Argon2_d : Argon2_id,
version);
if (rc != ARGON2_OK) {
qWarning("Argon2 error: %s", argon2_error_message(rc));
Expand All @@ -205,7 +222,7 @@ int Argon2Kdf::benchmarkImpl(int msec) const
timer.start();

int rounds = 4;
if (transformKeyRaw(key, seed, version(), rounds, memory(), parallelism(), key)) {
if (transformKeyRaw(key, seed, version(), type(), rounds, memory(), parallelism(), key)) {
return static_cast<int>(rounds * (static_cast<float>(msec) / timer.elapsed()));
}

Expand All @@ -214,5 +231,6 @@ int Argon2Kdf::benchmarkImpl(int msec) const

QString Argon2Kdf::toString() const
{
return QObject::tr("Argon2 (%1 rounds, %2 KB)").arg(QString::number(rounds()), QString::number(memory()));
return QObject::tr("Argon2%1 (%2 rounds, %3 KB)")
.arg(type() == Type::Argon2d ? "d" : "id", QString::number(rounds()), QString::number(memory()));
}
12 changes: 11 additions & 1 deletion src/crypto/kdf/Argon2Kdf.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@
class Argon2Kdf : public Kdf
{
public:
Argon2Kdf();
enum class Type
{
Argon2d,
Argon2id
};

Argon2Kdf(Type type);

bool processParameters(const QVariantMap& p) override;
QVariantMap writeParameters() override;
Expand All @@ -32,6 +38,8 @@ class Argon2Kdf : public Kdf

quint32 version() const;
bool setVersion(quint32 version);
Type type() const;
void setType(Type type);
quint64 memory() const;
bool setMemory(quint64 kibibytes);
quint32 parallelism() const;
Expand All @@ -41,6 +49,7 @@ class Argon2Kdf : public Kdf
protected:
int benchmarkImpl(int msec) const override;

Type m_type;
quint32 m_version;
quint64 m_memory;
quint32 m_parallelism;
Expand All @@ -49,6 +58,7 @@ class Argon2Kdf : public Kdf
Q_REQUIRED_RESULT static bool transformKeyRaw(const QByteArray& key,
const QByteArray& seed,
quint32 version,
Type type,
quint32 rounds,
quint64 memory,
quint32 parallelism,
Expand Down
13 changes: 9 additions & 4 deletions src/format/KeePass2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ const QUuid KeePass2::CIPHER_CHACHA20 = QUuid("d6038a2b-8b6f-4cb5-a524-339a31dbb

const QUuid KeePass2::KDF_AES_KDBX3 = QUuid("c9d9f39a-628a-4460-bf74-0d08c18a4fea");
const QUuid KeePass2::KDF_AES_KDBX4 = QUuid("7c02bb82-79a7-4ac0-927d-114a00648238");
const QUuid KeePass2::KDF_ARGON2 = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
const QUuid KeePass2::KDF_ARGON2D = QUuid("ef636ddf-8c29-444b-91f7-a9a403e30a0c");
const QUuid KeePass2::KDF_ARGON2ID = QUuid("9e298b19-56db-4773-b23d-fc3ec6f0a1e6");

const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xe8\x30\x09\x4b\x97\x20\x5d\x2a");

Expand All @@ -53,7 +54,8 @@ const QList<QPair<QUuid, QString>> KeePass2::CIPHERS{
qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20 256-bit"))};

const QList<QPair<QUuid, QString>> KeePass2::KDFS{
qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (KDBX 4 – recommended)")),
qMakePair(KeePass2::KDF_ARGON2D, QObject::tr("Argon2d (KDBX 4 – recommended)")),
qMakePair(KeePass2::KDF_ARGON2ID, QObject::tr("Argon2id (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))};

Expand Down Expand Up @@ -109,8 +111,11 @@ QSharedPointer<Kdf> KeePass2::uuidToKdf(const QUuid& uuid)
if (uuid == KDF_AES_KDBX4) {
return QSharedPointer<AesKdf>::create();
}
if (uuid == KDF_ARGON2) {
return QSharedPointer<Argon2Kdf>::create();
if (uuid == KDF_ARGON2D) {
return QSharedPointer<Argon2Kdf>::create(Argon2Kdf::Type::Argon2d);
}
if (uuid == KDF_ARGON2ID) {
return QSharedPointer<Argon2Kdf>::create(Argon2Kdf::Type::Argon2id);
}

return {};
Expand Down
3 changes: 2 additions & 1 deletion src/format/KeePass2.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ namespace KeePass2

extern const QUuid KDF_AES_KDBX3;
extern const QUuid KDF_AES_KDBX4;
extern const QUuid KDF_ARGON2;
extern const QUuid KDF_ARGON2D;
extern const QUuid KDF_ARGON2ID;

extern const QByteArray INNER_STREAM_SALSA20_IV;

Expand Down
2 changes: 1 addition & 1 deletion src/format/OpVaultReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Database* OpVaultReader::readDatabase(QDir& opdataDir, const QString& password)
key->addKey(QSharedPointer<PasswordKey>::create(password));

QScopedPointer<Database> db(new Database());
db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
db->setCipher(KeePass2::CIPHER_AES256);
db->setKey(key, true, false);
db->metadata()->setName(vaultName);
Expand Down
31 changes: 15 additions & 16 deletions src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int)));

m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray());
m_ui->decryptionTimeSlider->setMinimum(Kdf::MIN_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100);
Expand Down Expand Up @@ -75,6 +75,9 @@ DatabaseSettingsWidgetEncryption::~DatabaseSettingsWidgetEncryption()
{
}

#define IS_ARGON2(uuid) (uuid == KeePass2::KDF_ARGON2D || uuid == KeePass2::KDF_ARGON2ID)
#define IS_AES_KDF(uuid) (uuid == KeePass2::KDF_AES_KDBX3 || uuid == KeePass2::KDF_AES_KDBX4)

void DatabaseSettingsWidgetEncryption::initialize()
{
Q_ASSERT(m_db);
Expand All @@ -85,7 +88,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
bool isDirty = false;

if (!m_db->kdf()) {
m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
m_db->setKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2D));
isDirty = true;
}
if (!m_db->key()) {
Expand Down Expand Up @@ -175,7 +178,7 @@ void DatabaseSettingsWidgetEncryption::loadKdfParameters()
}

m_ui->transformRoundsSpinBox->setValue(kdf->rounds());
if (m_db->kdf()->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(m_db->kdf()->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
m_ui->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory()) / (1 << 10));
m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism());
Expand All @@ -188,13 +191,10 @@ void DatabaseSettingsWidgetEncryption::updateKdfFields()
{
QUuid id = m_db->kdf()->uuid();

bool memoryVisible = (id == KeePass2::KDF_ARGON2);
m_ui->memoryUsageLabel->setVisible(memoryVisible);
m_ui->memorySpinBox->setVisible(memoryVisible);

bool parallelismVisible = (id == KeePass2::KDF_ARGON2);
m_ui->parallelismLabel->setVisible(parallelismVisible);
m_ui->parallelismSpinBox->setVisible(parallelismVisible);
m_ui->memoryUsageLabel->setVisible(IS_ARGON2(id));
m_ui->memorySpinBox->setVisible(IS_ARGON2(id));
m_ui->parallelismLabel->setVisible(IS_ARGON2(id));
m_ui->parallelismSpinBox->setVisible(IS_ARGON2(id));
}

void DatabaseSettingsWidgetEncryption::activateChangeDecryptionTime()
Expand Down Expand Up @@ -253,7 +253,7 @@ bool DatabaseSettingsWidgetEncryption::save()
m_db->metadata()->customData()->remove(CD_DECRYPTION_TIME_PREFERENCE_KEY);

// first perform safety check for KDF rounds
if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_ui->transformRoundsSpinBox->value() > 10000) {
if (IS_ARGON2(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() > 10000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too high", "Key transformation rounds"));
Expand All @@ -266,8 +266,7 @@ bool DatabaseSettingsWidgetEncryption::save()
if (warning.clickedButton() != ok) {
return false;
}
} else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4)
&& m_ui->transformRoundsSpinBox->value() < 100000) {
} else if (IS_AES_KDF(kdf->uuid()) && m_ui->transformRoundsSpinBox->value() < 100000) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Number of rounds too low", "Key transformation rounds"));
Expand All @@ -286,7 +285,7 @@ bool DatabaseSettingsWidgetEncryption::save()

// Save kdf parameters
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
argon2Kdf->setMemory(static_cast<quint64>(m_ui->memorySpinBox->value()) * (1 << 10));
argon2Kdf->setParallelism(static_cast<quint32>(m_ui->parallelismSpinBox->value()));
Expand Down Expand Up @@ -317,7 +316,7 @@ void DatabaseSettingsWidgetEncryption::benchmarkTransformRounds(int millisecs)
// Create a new kdf with the current parameters
auto kdf = KeePass2::uuidToKdf(QUuid(m_ui->kdfComboBox->currentData().toByteArray()));
kdf->setRounds(m_ui->transformRoundsSpinBox->value());
if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
if (!argon2Kdf->setMemory(static_cast<quint64>(m_ui->memorySpinBox->value()) * (1 << 10))) {
m_ui->memorySpinBox->setValue(static_cast<int>(argon2Kdf->memory() / (1 << 10)));
Expand Down Expand Up @@ -402,7 +401,7 @@ void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool
auto kdf = KeePass2::uuidToKdf(kdfUuid);
m_db->setKdf(kdf);

if (kdf->uuid() == KeePass2::KDF_ARGON2) {
if (IS_ARGON2(kdf->uuid())) {
auto argon2Kdf = kdf.staticCast<Argon2Kdf>();
// Default to 64 MiB of memory and 2 threads
// these settings are safe for desktop and mobile devices
Expand Down
Loading

0 comments on commit 4727b92

Please sign in to comment.