diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp index 5ccde5e86a..df2ec82a48 100644 --- a/src/gui/PasswordGeneratorWidget.cpp +++ b/src/gui/PasswordGeneratorWidget.cpp @@ -53,7 +53,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent) // Configure password health timer m_passwordHealthTimer.setSingleShot(true); - connect(&m_passwordHealthTimer, &QTimer::timeout, this, [this] { queuePasswordStrengthUpdate(); }); + connect(&m_passwordHealthTimer, &QTimer::timeout, this, [this] { updatePasswordStrength(getGeneratedPassword()); }); // Add two shortcuts to save the form CTRL+Enter and CTRL+S auto shortcut = new QShortcut(Qt::CTRL + Qt::Key_Return, this); @@ -278,49 +278,31 @@ void PasswordGeneratorWidget::updateButtonsEnabled(const QString& password) m_ui->buttonCopy->setEnabled(!password.isEmpty()); } -void PasswordGeneratorWidget::queuePasswordStrengthUpdate() -{ - const static int MAX_SYNC_PASSWORD_LENGTH = 100; - const static int MAX_WORD_LENGTH = 16; - - const auto& password = getGeneratedPassword(); - - if (m_ui->tabWidget->currentIndex() == Diceware || password.size() < MAX_SYNC_PASSWORD_LENGTH) { - updatePasswordStrength(password, calculatePasswordHealth(password)); - return; - } - - // TODO Get wordcount size - auto truncatedPassword = password.left(MAX_SYNC_PASSWORD_LENGTH - MAX_WORD_LENGTH); - - // TODO Display "Entropy > xxx"? - updatePasswordStrength(truncatedPassword, calculatePasswordHealth(truncatedPassword)); - - AsyncTask::runThenCallback( - [this, password]() { - // QFuture requires default constructible result type, which PasswordHealth is not. Wrap it in an optional. - return QPair>{password, calculatePasswordHealth(password)}; - }, - this, - [this](const QPair>& result) { - if (result.first == getGeneratedPassword()) { - updatePasswordStrength(result.first, *result.second); - } - }); -} - PasswordHealth PasswordGeneratorWidget::calculatePasswordHealth(const QString& password) const { + // Length at which accurate password entropy estimation becomes to expensive + const static int EXPENSIVE_PASSWORD_LENGTH_THRESHOLD = 128; + if (m_ui->tabWidget->currentIndex() == Diceware) { // Diceware estimates entropy differently return PasswordHealth(m_dicewareGenerator->estimateEntropy()); } + + if (password.size() > EXPENSIVE_PASSWORD_LENGTH_THRESHOLD) { + double entropy_estimate = 0.0; + for (int i = 0; i < password.size(); i += EXPENSIVE_PASSWORD_LENGTH_THRESHOLD) { + entropy_estimate += PasswordHealth(password.mid(i, EXPENSIVE_PASSWORD_LENGTH_THRESHOLD)).entropy(); + } + return PasswordHealth(entropy_estimate); + } + return PasswordHealth(password); } -// TODO Maybe make password a member of PasswordHealth? -void PasswordGeneratorWidget::updatePasswordStrength(const QString& password, const PasswordHealth& passwordHealth) +void PasswordGeneratorWidget::updatePasswordStrength(const QString& password) { + auto passwordHealth = calculatePasswordHealth(password); + if (m_ui->tabWidget->currentIndex() == Diceware) { // Additionally show password length m_ui->charactersInPassphraseLabel->setText(QString::number(password.length())); diff --git a/src/gui/PasswordGeneratorWidget.h b/src/gui/PasswordGeneratorWidget.h index bf46f9316e..f9617a5b88 100644 --- a/src/gui/PasswordGeneratorWidget.h +++ b/src/gui/PasswordGeneratorWidget.h @@ -72,7 +72,6 @@ public slots: private slots: void updateButtonsEnabled(const QString& password); - void queuePasswordStrengthUpdate(); void setAdvancedMode(bool advanced); void excludeHexChars(); @@ -90,7 +89,7 @@ private slots: void closeEvent(QCloseEvent* event) override; PasswordGenerator::CharClasses charClasses(); PasswordGenerator::GeneratorFlags generatorFlags(); - void updatePasswordStrength(const QString& password, const PasswordHealth& passwordHealth); + void updatePasswordStrength(const QString& password); PasswordHealth calculatePasswordHealth(const QString& password) const; const QScopedPointer m_passwordGenerator; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 8006149107..3b0e268670 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -654,6 +654,10 @@ void TestGui::testPasswordEntryEntropy_data() QTest::addColumn("expectedEntropyLabel"); QTest::addColumn("expectedStrengthLabel"); + QTest::newRow("Empty password") << "" + << "Entropy: 0.00 bit" + << "Password Quality: Poor"; + QTest::newRow("Well-known password") << "hello" << "Entropy: 6.38 bit" << "Password Quality: Poor"; @@ -686,15 +690,15 @@ void TestGui::testPasswordEntryEntropy_data() << "Entropy: 174.59 bit" << "Password Quality: Excellent"; - QTest::newRow("Very long random password, triggers async request") + QTest::newRow("Very long random password, above Zxcvbn threshold") << "quintet-tamper-kinswoman-humility-vengeful-haven-tastiness-aspire-widget-ipad-cussed-reaffirm-ladylike-" "ashamed-anatomy-daybed-jam-swear-strudel-neatness-stalemate-unbundle-flavored-relation-emergency-underrate-" "registry-getting-award-unveiled-unshaken-stagnate-cartridge-magnitude-ointment-hardener-enforced-scrubbed-" "radial-fiddling-envelope-unpaved-moisture-unused-crawlers-quartered-crushed-kangaroo-tiptop-doily" - << "Entropy: 1155.96 bit" + << "Entropy: 1157.54 bit" << "Password Quality: Excellent"; - QTest::newRow("Even longer random password, triggers async request") + QTest::newRow("Even longer random password, above Zxcvbn threshold") << "modulator &712&7123x/thinly &712&7123x/straggler &712&7123x/overripe &712&7123x/steering &712&7123x/from " "&712&7123x/grit &712&7123x/jovial &712&7123x/saddled &712&7123x/employee &712&7123x/mothproof " "&712&7123x/modular &712&7123x/stretch &712&7123x/marital &712&7123x/capitol &712&7123x/smock " @@ -715,7 +719,7 @@ void TestGui::testPasswordEntryEntropy_data() "&712&7123x/drum &712&7123x/heavily &712&7123x/antiviral &712&7123x/depict &712&7123x/walmart " "&712&7123x/epilepsy &712&7123x/botany &712&7123x/wince &712&7123x/mating &712&7123x/starlet " "&712&7123x/revise &712&7123x/helper &712&7123x/cycling &712&7123x/operable" - << "Entropy: 6714.15 bit" + << "Entropy: 6785.34 bit" << "Password Quality: Excellent"; }