Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add db statistic output to CLI db-info command. #7032

Merged
merged 1 commit into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions share/translations/keepassxc_en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7561,6 +7561,74 @@ Please consider generating a new key file.</source>
<source>Use custom character set</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Location</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database created</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Last saved</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unsaved changes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of groups</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of expired entries</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unique passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Non-unique passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Maximum password reuse</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of short passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Number of weak passwords</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Entries excluded from reports</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Average password length</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>%1 characters</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtIOCompressor</name>
Expand Down
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(keepassx_SOURCES
core/Config.cpp
core/CustomData.cpp
core/Database.cpp
core/DatabaseStats.cpp
core/Entry.cpp
core/EntryAttachments.cpp
core/EntryAttributes.cpp
Expand Down
22 changes: 22 additions & 0 deletions src/cli/Info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include "Info.h"

#include "Utils.h"
#include "core/DatabaseStats.h"
#include "core/Global.h"
#include "core/Group.h"
#include "core/Metadata.h"

#include <QCommandLineParser>
Expand Down Expand Up @@ -47,5 +49,25 @@ int Info::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<
} else {
out << QObject::tr("Recycle bin is not enabled.") << endl;
}

DatabaseStats stats(database);
out << QObject::tr("Location") << ": " << database->filePath() << endl;
out << QObject::tr("Database created") << ": "
<< database->rootGroup()->timeInfo().creationTime().toString(Qt::DefaultLocaleShortDate) << endl;
out << QObject::tr("Last saved") << ": " << stats.modified.toString(Qt::DefaultLocaleShortDate) << endl;
out << QObject::tr("Unsaved changes") << ": " << (database->isModified() ? QObject::tr("yes") : QObject::tr("no"))
<< endl;
out << QObject::tr("Number of groups") << ": " << QString::number(stats.groupCount) << endl;
out << QObject::tr("Number of entries") << ": " << QString::number(stats.entryCount) << endl;
out << QObject::tr("Number of expired entries") << ": " << QString::number(stats.expiredEntries) << endl;
out << QObject::tr("Unique passwords") << ": " << QString::number(stats.uniquePasswords) << endl;
out << QObject::tr("Non-unique passwords") << ": " << QString::number(stats.reusedPasswords) << endl;
out << QObject::tr("Maximum password reuse") << ": " << QString::number(stats.maxPwdReuse()) << endl;
out << QObject::tr("Number of short passwords") << ": " << QString::number(stats.shortPasswords) << endl;
out << QObject::tr("Number of weak passwords") << ": " << QString::number(stats.weakPasswords) << endl;
out << QObject::tr("Entries excluded from reports") << ": " << QString::number(stats.excludedEntries) << endl;
out << QObject::tr("Average password length") << ": " << QObject::tr("%1 characters").arg(stats.averagePwdLength())
<< endl;

return EXIT_SUCCESS;
}
119 changes: 119 additions & 0 deletions src/core/DatabaseStats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2021 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 "DatabaseStats.h"

// Ctor does all the work
DatabaseStats::DatabaseStats(QSharedPointer<Database> db)
: modified(QFileInfo(db->filePath()).lastModified())
, m_db(db)
{
gatherStats(db->rootGroup()->groupsRecursive(true));
}

// Get average password length
int DatabaseStats::averagePwdLength() const
{
const auto passwords = uniquePasswords + reusedPasswords;
return passwords == 0 ? 0 : std::round(totalPasswordLength / double(passwords));
}

// Get max number of password reuse (=how many entries
// share the same password)
int DatabaseStats::maxPwdReuse() const
{
int ret = 0;
for (const auto& count : m_passwords) {
ret = std::max(ret, count);
}
return ret;
}

// A warning sign is displayed if one of the
// following returns true.
bool DatabaseStats::isAnyExpired() const
{
return expiredEntries > 0;
}

bool DatabaseStats::areTooManyPwdsReused() const
{
return reusedPasswords > uniquePasswords / 10;
}

bool DatabaseStats::arePwdsReusedTooOften() const
{
return maxPwdReuse() > 3;
}

bool DatabaseStats::isAvgPwdTooShort() const
{
return averagePwdLength() < 10;
}

void DatabaseStats::gatherStats(const QList<Group*>& groups)
{
auto checker = HealthChecker(m_db);

for (const auto* group : groups) {
// Don't count anything in the recycle bin
if (group->isRecycled()) {
continue;
}

++groupCount;

for (const auto* entry : group->entries()) {
// Don't count anything in the recycle bin
if (entry->isRecycled()) {
continue;
}

++entryCount;

if (entry->isExpired()) {
++expiredEntries;
}

// Get password statistics
const auto pwd = entry->password();
if (!pwd.isEmpty()) {
if (!m_passwords.contains(pwd)) {
++uniquePasswords;
} else {
++reusedPasswords;
}

if (pwd.size() < PasswordHealth::Length::Short) {
++shortPasswords;
}

// Speed up Zxcvbn process by excluding very long passwords and most passphrases
if (pwd.size() < PasswordHealth::Length::Long
&& checker.evaluate(entry)->quality() <= PasswordHealth::Quality::Weak) {
++weakPasswords;
}

if (entry->excludeFromReports()) {
++excludedEntries;
}

totalPasswordLength += pwd.size();
m_passwords[pwd]++;
}
}
}
}
59 changes: 59 additions & 0 deletions src/core/DatabaseStats.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (C) 2021 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_DATABASESTATS_H
#define KEEPASSXC_DATABASESTATS_H
#include "PasswordHealth.h"
#include "core/Group.h"
#include <QFileInfo>
#include <cmath>
class DatabaseStats
{
public:
// The statistics we collect:
QDateTime modified; // File modification time
int groupCount = 0; // Number of groups in the database
int entryCount = 0; // Number of entries (across all groups)
int expiredEntries = 0; // Number of expired entries
int excludedEntries = 0; // Number of known bad entries
int weakPasswords = 0; // Number of weak or poor passwords
int shortPasswords = 0; // Number of passwords 8 characters or less in size
int uniquePasswords = 0; // Number of unique passwords
int reusedPasswords = 0; // Number of non-unique passwords
int totalPasswordLength = 0; // Total length of all passwords

explicit DatabaseStats(QSharedPointer<Database> db);

int averagePwdLength() const;

int maxPwdReuse() const;

bool isAnyExpired() const;

bool areTooManyPwdsReused() const;

bool arePwdsReusedTooOften() const;

bool isAvgPwdTooShort() const;

private:
QSharedPointer<Database> m_db;
QHash<QString, int> m_passwords;

void gatherStats(const QList<Group*>& groups);
};
#endif // KEEPASSXC_DATABASESTATS_H
6 changes: 6 additions & 0 deletions src/core/PasswordHealth.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ class PasswordHealth
return m_entropy;
}

struct Length
{
static const int Short = 8;
static const int Long = 25;
};

private:
int m_score = 0;
double m_entropy = 0.0;
Expand Down
Loading