Skip to content

Commit

Permalink
Estimate password strength if password length above threshold.
Browse files Browse the repository at this point in the history
Estimation procedure applies zxvcbn to 128 character chunks of the original password.
  • Loading branch information
libklein committed Apr 3, 2022
1 parent f002b43 commit 77531b8
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 40 deletions.
50 changes: 16 additions & 34 deletions src/gui/PasswordGeneratorWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<QString, std::optional<PasswordHealth>>{password, calculatePasswordHealth(password)};
},
this,
[this](const QPair<QString, std::optional<PasswordHealth>>& 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()));
Expand Down
3 changes: 1 addition & 2 deletions src/gui/PasswordGeneratorWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public slots:

private slots:
void updateButtonsEnabled(const QString& password);
void queuePasswordStrengthUpdate();
void setAdvancedMode(bool advanced);
void excludeHexChars();

Expand All @@ -90,7 +89,7 @@ private slots:
void closeEvent(QCloseEvent* event);
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<PasswordGenerator> m_passwordGenerator;
Expand Down
12 changes: 8 additions & 4 deletions tests/gui/TestGui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,10 @@ void TestGui::testPasswordEntryEntropy_data()
QTest::addColumn<QString>("expectedEntropyLabel");
QTest::addColumn<QString>("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";
Expand Down Expand Up @@ -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 "
Expand All @@ -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";
}

Expand Down

0 comments on commit 77531b8

Please sign in to comment.