Add word case option to passphrase generator (#3172)

* Closes #1933
* Adds word case options for lower, UPPER, and Title Case to passphrase generation
This commit is contained in:
shaneknysh 2019-05-24 16:23:19 -06:00 committed by Jonathan White
parent 2ee97ed191
commit 7ead8e7290
9 changed files with 192 additions and 42 deletions

View File

@ -510,7 +510,6 @@ QString BrowserSettings::generatePassword()
m_passwordGenerator.setFlags(passwordGeneratorFlags()); m_passwordGenerator.setFlags(passwordGeneratorFlags());
return m_passwordGenerator.generatePassword(); return m_passwordGenerator.generatePassword();
} else { } else {
m_passPhraseGenerator.setDefaultWordList();
m_passPhraseGenerator.setWordCount(passPhraseWordCount()); m_passPhraseGenerator.setWordCount(passPhraseWordCount());
m_passPhraseGenerator.setWordSeparator(passPhraseWordSeparator()); m_passPhraseGenerator.setWordSeparator(passPhraseWordSeparator());
return m_passPhraseGenerator.generatePassphrase(); return m_passPhraseGenerator.generatePassphrase();

View File

@ -73,8 +73,6 @@ int Diceware::execute(const QStringList& arguments)
if (!parser.value(wordlistFile).isEmpty()) { if (!parser.value(wordlistFile).isEmpty()) {
dicewareGenerator.setWordList(parser.value(wordlistFile)); dicewareGenerator.setWordList(parser.value(wordlistFile));
} else {
dicewareGenerator.setDefaultWordList();
} }
if (!dicewareGenerator.isValid()) { if (!dicewareGenerator.isValid()) {

View File

@ -28,9 +28,11 @@ const char* PassphraseGenerator::DefaultSeparator = " ";
const char* PassphraseGenerator::DefaultWordList = "eff_large.wordlist"; const char* PassphraseGenerator::DefaultWordList = "eff_large.wordlist";
PassphraseGenerator::PassphraseGenerator() PassphraseGenerator::PassphraseGenerator()
: m_wordCount(0) : m_wordCount(DefaultWordCount)
, m_separator(PassphraseGenerator::DefaultSeparator) , m_wordCase(LOWERCASE)
, m_separator(DefaultSeparator)
{ {
setDefaultWordList();
} }
double PassphraseGenerator::calculateEntropy(const QString& passphrase) double PassphraseGenerator::calculateEntropy(const QString& passphrase)
@ -46,12 +48,12 @@ double PassphraseGenerator::calculateEntropy(const QString& passphrase)
void PassphraseGenerator::setWordCount(int wordCount) void PassphraseGenerator::setWordCount(int wordCount)
{ {
if (wordCount > 0) { m_wordCount = qMax(1, wordCount);
m_wordCount = wordCount; }
} else {
// safe default if something goes wrong void PassphraseGenerator::setWordCase(PassphraseWordCase wordCase)
m_wordCount = DefaultWordCount; {
} m_wordCase = wordCase;
} }
void PassphraseGenerator::setWordList(const QString& path) void PassphraseGenerator::setWordList(const QString& path)
@ -88,6 +90,7 @@ void PassphraseGenerator::setWordSeparator(const QString& separator)
QString PassphraseGenerator::generatePassphrase() const QString PassphraseGenerator::generatePassphrase() const
{ {
QString tmpWord;
Q_ASSERT(isValid()); Q_ASSERT(isValid());
// In case there was an error loading the wordlist // In case there was an error loading the wordlist
@ -98,7 +101,22 @@ QString PassphraseGenerator::generatePassphrase() const
QStringList words; QStringList words;
for (int i = 0; i < m_wordCount; ++i) { for (int i = 0; i < m_wordCount; ++i) {
int wordIndex = randomGen()->randomUInt(static_cast<quint32>(m_wordlist.length())); int wordIndex = randomGen()->randomUInt(static_cast<quint32>(m_wordlist.length()));
words.append(m_wordlist.at(wordIndex)); tmpWord = m_wordlist.at(wordIndex);
// convert case
switch (m_wordCase) {
case UPPERCASE:
tmpWord = tmpWord.toUpper();
break;
case TITLECASE:
tmpWord = tmpWord.replace(0, 1, tmpWord.left(1).toUpper());
break;
case LOWERCASE:
default:
tmpWord = tmpWord.toLower();
break;
}
words.append(tmpWord);
} }
return words.join(m_separator); return words.join(m_separator);

View File

@ -28,9 +28,17 @@ public:
PassphraseGenerator(); PassphraseGenerator();
Q_DISABLE_COPY(PassphraseGenerator) Q_DISABLE_COPY(PassphraseGenerator)
enum PassphraseWordCase
{
LOWERCASE,
UPPERCASE,
TITLECASE
};
double calculateEntropy(const QString& passphrase); double calculateEntropy(const QString& passphrase);
void setWordCount(int wordCount); void setWordCount(int wordCount);
void setWordList(const QString& path); void setWordList(const QString& path);
void setWordCase(PassphraseWordCase wordCase);
void setDefaultWordList(); void setDefaultWordList();
void setWordSeparator(const QString& separator); void setWordSeparator(const QString& separator);
bool isValid() const; bool isValid() const;
@ -43,6 +51,7 @@ public:
private: private:
int m_wordCount; int m_wordCount;
PassphraseWordCase m_wordCase;
QString m_separator; QString m_separator;
QVector<QString> m_wordlist; QVector<QString> m_wordlist;
}; };

View File

@ -60,6 +60,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
connect(m_ui->comboBoxWordList, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator())); connect(m_ui->comboBoxWordList, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator()));
connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator())); connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator()));
connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateGenerator())); connect(m_ui->tabWidget, SIGNAL(currentChanged(int)), SLOT(updateGenerator()));
connect(m_ui->wordCaseComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateGenerator()));
// set font size of password quality and entropy labels dynamically to 80% of // set font size of password quality and entropy labels dynamically to 80% of
// the default font size, but make it no smaller than 8pt // the default font size, but make it no smaller than 8pt
@ -74,6 +75,11 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
// set default separator to Space // set default separator to Space
m_ui->editWordSeparator->setText(PassphraseGenerator::DefaultSeparator); m_ui->editWordSeparator->setText(PassphraseGenerator::DefaultSeparator);
// add passphrase generator case options
m_ui->wordCaseComboBox->addItem(tr("lower case"), PassphraseGenerator::LOWERCASE);
m_ui->wordCaseComboBox->addItem(tr("UPPER CASE"), PassphraseGenerator::UPPERCASE);
m_ui->wordCaseComboBox->addItem(tr("Title Case"), PassphraseGenerator::TITLECASE);
QDir path(filePath()->wordlistPath("")); QDir path(filePath()->wordlistPath(""));
QStringList files = path.entryList(QDir::Files); QStringList files = path.entryList(QDir::Files);
m_ui->comboBoxWordList->addItems(files); m_ui->comboBoxWordList->addItems(files);
@ -85,7 +91,6 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
m_ui->labelWordList->setVisible(false); m_ui->labelWordList->setVisible(false);
} }
m_dicewareGenerator->setDefaultWordList();
loadSettings(); loadSettings();
reset(); reset();
} }
@ -139,6 +144,7 @@ void PasswordGeneratorWidget::loadSettings()
config()->get("generator/WordSeparator", PassphraseGenerator::DefaultSeparator).toString()); config()->get("generator/WordSeparator", PassphraseGenerator::DefaultSeparator).toString());
m_ui->comboBoxWordList->setCurrentText( m_ui->comboBoxWordList->setCurrentText(
config()->get("generator/WordList", PassphraseGenerator::DefaultWordList).toString()); config()->get("generator/WordList", PassphraseGenerator::DefaultWordList).toString());
m_ui->wordCaseComboBox->setCurrentIndex(config()->get("generator/WordCase", 0).toInt());
// Password or diceware? // Password or diceware?
m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 0).toInt()); m_ui->tabWidget->setCurrentIndex(config()->get("generator/Type", 0).toInt());
@ -174,6 +180,7 @@ void PasswordGeneratorWidget::saveSettings()
config()->set("generator/WordCount", m_ui->spinBoxWordCount->value()); config()->set("generator/WordCount", m_ui->spinBoxWordCount->value());
config()->set("generator/WordSeparator", m_ui->editWordSeparator->text()); config()->set("generator/WordSeparator", m_ui->editWordSeparator->text());
config()->set("generator/WordList", m_ui->comboBoxWordList->currentText()); config()->set("generator/WordList", m_ui->comboBoxWordList->currentText());
config()->set("generator/WordCase", m_ui->wordCaseComboBox->currentIndex());
// Password or diceware? // Password or diceware?
config()->set("generator/Type", m_ui->tabWidget->currentIndex()); config()->set("generator/Type", m_ui->tabWidget->currentIndex());
@ -556,6 +563,9 @@ void PasswordGeneratorWidget::updateGenerator()
m_updatingSpinBox = false; m_updatingSpinBox = false;
} }
m_dicewareGenerator->setWordCase(
static_cast<PassphraseGenerator::PassphraseWordCase>(m_ui->wordCaseComboBox->currentData().toInt()));
m_ui->spinBoxWordCount->setMinimum(minWordCount); m_ui->spinBoxWordCount->setMinimum(minWordCount);
m_ui->sliderWordCount->setMinimum(minWordCount); m_ui->sliderWordCount->setMinimum(minWordCount);

View File

@ -927,16 +927,10 @@ QProgressBar::chunk {
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0" alignment="Qt::AlignRight"> <item row="3" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="labelWordList"> <widget class="QLabel" name="wordCaseLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>Wordlist:</string> <string>Word Case:</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -950,16 +944,39 @@ QProgressBar::chunk {
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" alignment="Qt::AlignRight"> <item row="2" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="labelWordCount"> <widget class="QLabel" name="labelWordSeparator">
<property name="text"> <property name="text">
<string>Word Co&amp;unt:</string> <string>Word Separator:</string>
</property>
<property name="buddy">
<cstring>spinBoxLength</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="labelWordList">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Wordlist:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="1"> <item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -1005,10 +1022,13 @@ QProgressBar::chunk {
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" alignment="Qt::AlignRight"> <item row="1" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="labelWordSeparator"> <widget class="QLabel" name="labelWordCount">
<property name="text"> <property name="text">
<string>Word Separator:</string> <string>Word Co&amp;unt:</string>
</property>
<property name="buddy">
<cstring>spinBoxLength</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -1020,17 +1040,24 @@ QProgressBar::chunk {
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<spacer name="verticalSpacer_3"> <layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="orientation"> <item>
<enum>Qt::Vertical</enum> <widget class="QComboBox" name="wordCaseComboBox"/>
</property> </item>
<property name="sizeHint" stdset="0"> <item>
<size> <spacer name="horizontalSpacer_4">
<width>20</width> <property name="orientation">
<height>40</height> <enum>Qt::Horizontal</enum>
</size> </property>
</property> <property name="sizeHint" stdset="0">
</spacer> <size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -190,6 +190,9 @@ add_unit_test(NAME testmerge SOURCES TestMerge.cpp
add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp add_unit_test(NAME testpasswordgenerator SOURCES TestPasswordGenerator.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testpassphrasegenerator SOURCES TestPassphraseGenerator.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testtotp SOURCES TestTotp.cpp add_unit_test(NAME testtotp SOURCES TestTotp.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "TestPassphraseGenerator.h"
#include "core/PassphraseGenerator.h"
#include "crypto/Crypto.h"
#include <QRegularExpression>
#include <QTest>
QTEST_GUILESS_MAIN(TestPassphraseGenerator)
void TestPassphraseGenerator::initTestCase()
{
QVERIFY(Crypto::init());
}
void TestPassphraseGenerator::testWordCase()
{
PassphraseGenerator generator;
generator.setWordSeparator(" ");
QVERIFY(generator.isValid());
QString passphrase;
passphrase = generator.generatePassphrase();
QCOMPARE(passphrase, passphrase.toLower());
generator.setWordCase(PassphraseGenerator::LOWERCASE);
passphrase = generator.generatePassphrase();
QCOMPARE(passphrase, passphrase.toLower());
generator.setWordCase(PassphraseGenerator::UPPERCASE);
passphrase = generator.generatePassphrase();
QCOMPARE(passphrase, passphrase.toUpper());
generator.setWordCase(PassphraseGenerator::TITLECASE);
passphrase = generator.generatePassphrase();
QRegularExpression regex("^([A-Z][a-z]* ?)+$");
QVERIFY(regex.match(passphrase).hasMatch());
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TESTPASSPHRASEGENERATOR_H
#define KEEPASSXC_TESTPASSPHRASEGENERATOR_H
#include <QObject>
class TestPassphraseGenerator : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void testWordCase();
};
#endif // KEEPASSXC_TESTPASSPHRASEGENERATOR_H