mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-11-19 22:42:23 -05:00
Limit zxcvbn entropy estimation length
Limit the use of zxcvbn based password entropy estimation to 256 bytes. After this threshold, the average per-byte entropy from the zxcvbn calculation is added for each additional byte. In practice, this produces a slightly higher entropy calculation for purely randomized passwords than zxcvbn would normally calculate. However, the time to calculate is capped leading to a much better user experience and removing unnecessary calculations. Fixes #7712
This commit is contained in:
parent
dca70f809d
commit
6f28b5e2ba
6 changed files with 156 additions and 107 deletions
|
|
@ -21,10 +21,32 @@
|
|||
#include "PasswordHealth.h"
|
||||
#include "zxcvbn.h"
|
||||
|
||||
PasswordHealth::PasswordHealth(double entropy)
|
||||
: m_score(entropy)
|
||||
, m_entropy(entropy)
|
||||
namespace
|
||||
{
|
||||
const static int ZXCVBN_ESTIMATE_THRESHOLD = 256;
|
||||
} // namespace
|
||||
|
||||
PasswordHealth::PasswordHealth(double entropy)
|
||||
{
|
||||
init(entropy);
|
||||
}
|
||||
|
||||
PasswordHealth::PasswordHealth(const QString& pwd)
|
||||
{
|
||||
auto entropy = 0.0;
|
||||
entropy += ZxcvbnMatch(pwd.left(ZXCVBN_ESTIMATE_THRESHOLD).toUtf8(), nullptr, nullptr);
|
||||
if (pwd.length() > ZXCVBN_ESTIMATE_THRESHOLD) {
|
||||
// Add the average entropy per character for any characters above the estimate threshold
|
||||
auto average = entropy / ZXCVBN_ESTIMATE_THRESHOLD;
|
||||
entropy += average * (pwd.length() - ZXCVBN_ESTIMATE_THRESHOLD);
|
||||
}
|
||||
init(entropy);
|
||||
}
|
||||
|
||||
void PasswordHealth::init(double entropy)
|
||||
{
|
||||
m_score = m_entropy = entropy;
|
||||
|
||||
switch (quality()) {
|
||||
case Quality::Bad:
|
||||
case Quality::Poor:
|
||||
|
|
@ -43,11 +65,6 @@ PasswordHealth::PasswordHealth(double entropy)
|
|||
}
|
||||
}
|
||||
|
||||
PasswordHealth::PasswordHealth(const QString& pwd)
|
||||
: PasswordHealth(ZxcvbnMatch(pwd.toUtf8(), nullptr, nullptr))
|
||||
{
|
||||
}
|
||||
|
||||
void PasswordHealth::setScore(int score)
|
||||
{
|
||||
m_score = score;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ public:
|
|||
explicit PasswordHealth(double entropy);
|
||||
explicit PasswordHealth(const QString& pwd);
|
||||
|
||||
void init(double entropy);
|
||||
|
||||
/*
|
||||
* The password score is defined to be the greater the better
|
||||
* (more secure) the password is. It doesn't have a dimension,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
|
|||
connect(shortcut, &QShortcut::activated, this, [this] { applyPassword(); });
|
||||
|
||||
connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updateButtonsEnabled(QString)));
|
||||
connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength(QString)));
|
||||
connect(m_ui->editNewPassword, SIGNAL(textChanged(QString)), SLOT(updatePasswordStrength()));
|
||||
connect(m_ui->buttonAdvancedMode, SIGNAL(toggled(bool)), SLOT(setAdvancedMode(bool)));
|
||||
connect(m_ui->buttonAddHex, SIGNAL(clicked()), SLOT(excludeHexChars()));
|
||||
connect(m_ui->editAdditionalChars, SIGNAL(textChanged(QString)), SLOT(updateGenerator()));
|
||||
|
|
@ -115,9 +115,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
|
|||
loadSettings();
|
||||
}
|
||||
|
||||
PasswordGeneratorWidget::~PasswordGeneratorWidget()
|
||||
{
|
||||
}
|
||||
PasswordGeneratorWidget::~PasswordGeneratorWidget() = default;
|
||||
|
||||
void PasswordGeneratorWidget::closeEvent(QCloseEvent* event)
|
||||
{
|
||||
|
|
@ -253,15 +251,11 @@ void PasswordGeneratorWidget::regeneratePassword()
|
|||
{
|
||||
if (m_ui->tabWidget->currentIndex() == Password) {
|
||||
if (m_passwordGenerator->isValid()) {
|
||||
QString password = m_passwordGenerator->generatePassword();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
updatePasswordStrength(password);
|
||||
m_ui->editNewPassword->setText(m_passwordGenerator->generatePassword());
|
||||
}
|
||||
} else {
|
||||
if (m_dicewareGenerator->isValid()) {
|
||||
QString password = m_dicewareGenerator->generatePassphrase();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
updatePasswordStrength(password);
|
||||
m_ui->editNewPassword->setText(m_dicewareGenerator->generatePassphrase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -274,21 +268,52 @@ void PasswordGeneratorWidget::updateButtonsEnabled(const QString& password)
|
|||
m_ui->buttonCopy->setEnabled(!password.isEmpty());
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::updatePasswordStrength(const QString& password)
|
||||
void PasswordGeneratorWidget::updatePasswordStrength()
|
||||
{
|
||||
PasswordHealth health(password);
|
||||
// Calculate the password / passphrase health
|
||||
PasswordHealth passwordHealth(0);
|
||||
if (m_ui->tabWidget->currentIndex() == Diceware) {
|
||||
// Diceware estimates entropy differently
|
||||
health = PasswordHealth(m_dicewareGenerator->estimateEntropy());
|
||||
|
||||
m_ui->charactersInPassphraseLabel->setText(QString::number(password.length()));
|
||||
passwordHealth.init(m_dicewareGenerator->estimateEntropy());
|
||||
m_ui->charactersInPassphraseLabel->setText(QString::number(m_ui->editNewPassword->text().length()));
|
||||
} else {
|
||||
passwordHealth = PasswordHealth(m_ui->editNewPassword->text());
|
||||
}
|
||||
|
||||
m_ui->entropyLabel->setText(tr("Entropy: %1 bit").arg(QString::number(health.entropy(), 'f', 2)));
|
||||
// Update the entropy text labels
|
||||
m_ui->entropyLabel->setText(tr("Entropy: %1 bit").arg(QString::number(passwordHealth.entropy(), 'f', 2)));
|
||||
m_ui->entropyProgressBar->setValue(std::min(int(passwordHealth.entropy()), m_ui->entropyProgressBar->maximum()));
|
||||
|
||||
m_ui->entropyProgressBar->setValue(std::min(int(health.entropy()), m_ui->entropyProgressBar->maximum()));
|
||||
// Update the visual strength meter
|
||||
QString style = m_ui->entropyProgressBar->styleSheet();
|
||||
QRegularExpression re("(QProgressBar::chunk\\s*\\{.*?background-color:)[^;]+;",
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
|
||||
style.replace(re, "\\1 %1;");
|
||||
|
||||
colorStrengthIndicator(health);
|
||||
StateColorPalette statePalette;
|
||||
switch (passwordHealth.quality()) {
|
||||
case PasswordHealth::Quality::Bad:
|
||||
case PasswordHealth::Quality::Poor:
|
||||
m_ui->entropyProgressBar->setStyleSheet(
|
||||
style.arg(statePalette.color(StateColorPalette::HealthCritical).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Weak:
|
||||
m_ui->entropyProgressBar->setStyleSheet(style.arg(statePalette.color(StateColorPalette::HealthBad).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Good:
|
||||
m_ui->entropyProgressBar->setStyleSheet(style.arg(statePalette.color(StateColorPalette::HealthOk).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Excellent:
|
||||
m_ui->entropyProgressBar->setStyleSheet(
|
||||
style.arg(statePalette.color(StateColorPalette::HealthExcellent).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Excellent", "Password quality")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::applyPassword()
|
||||
|
|
@ -471,41 +496,6 @@ void PasswordGeneratorWidget::excludeHexChars()
|
|||
updateGenerator();
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::colorStrengthIndicator(const PasswordHealth& health)
|
||||
{
|
||||
// Take the existing stylesheet and convert the text and background color to arguments
|
||||
QString style = m_ui->entropyProgressBar->styleSheet();
|
||||
QRegularExpression re("(QProgressBar::chunk\\s*\\{.*?background-color:)[^;]+;",
|
||||
QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
|
||||
style.replace(re, "\\1 %1;");
|
||||
|
||||
StateColorPalette statePalette;
|
||||
switch (health.quality()) {
|
||||
case PasswordHealth::Quality::Bad:
|
||||
case PasswordHealth::Quality::Poor:
|
||||
m_ui->entropyProgressBar->setStyleSheet(
|
||||
style.arg(statePalette.color(StateColorPalette::HealthCritical).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Poor", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Weak:
|
||||
m_ui->entropyProgressBar->setStyleSheet(style.arg(statePalette.color(StateColorPalette::HealthBad).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Weak", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Good:
|
||||
m_ui->entropyProgressBar->setStyleSheet(style.arg(statePalette.color(StateColorPalette::HealthOk).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Good", "Password quality")));
|
||||
break;
|
||||
|
||||
case PasswordHealth::Quality::Excellent:
|
||||
m_ui->entropyProgressBar->setStyleSheet(
|
||||
style.arg(statePalette.color(StateColorPalette::HealthExcellent).name()));
|
||||
m_ui->strengthLabel->setText(tr("Password Quality: %1").arg(tr("Excellent", "Password quality")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PasswordGenerator::CharClasses PasswordGeneratorWidget::charClasses()
|
||||
{
|
||||
PasswordGenerator::CharClasses classes;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#define KEEPASSX_PASSWORDGENERATORWIDGET_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/PassphraseGenerator.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
|
|
@ -57,6 +58,10 @@ public:
|
|||
|
||||
static PasswordGeneratorWidget* popupGenerator(QWidget* parent = nullptr);
|
||||
|
||||
signals:
|
||||
void appliedPassword(const QString& password);
|
||||
void closed();
|
||||
|
||||
public slots:
|
||||
void regeneratePassword();
|
||||
void applyPassword();
|
||||
|
|
@ -65,19 +70,14 @@ public slots:
|
|||
void deleteWordList();
|
||||
void addWordList();
|
||||
|
||||
signals:
|
||||
void appliedPassword(const QString& password);
|
||||
void closed();
|
||||
|
||||
private slots:
|
||||
void updateButtonsEnabled(const QString& password);
|
||||
void updatePasswordStrength(const QString& password);
|
||||
void updatePasswordStrength();
|
||||
void setAdvancedMode(bool advanced);
|
||||
void excludeHexChars();
|
||||
|
||||
void passwordLengthChanged(int length);
|
||||
void passphraseLengthChanged(int length);
|
||||
void colorStrengthIndicator(const PasswordHealth& health);
|
||||
|
||||
void updateGenerator();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue