1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

feat(database): use own public key as salt to encrypt the database

fixes #3583

BREAKING CHANGE:
The database will be encrypted with a new key, qTox versions before this
commit won't be able to decrypt the database.
This commit is contained in:
sudden6 2016-09-22 20:50:58 +02:00
parent 949e3cb830
commit c4b9d302d0
No known key found for this signature in database
GPG Key ID: 279509B499E032B9
4 changed files with 105 additions and 24 deletions

View File

@ -91,15 +91,43 @@
* @param password If empty, the database will be opened unencrypted.
* Otherwise we will use toxencryptsave to derive a key and encrypt the database.
*/
RawDatabase::RawDatabase(const QString &path, const QString& password)
: workerThread{new QThread}, path{path}, currentHexKey{deriveKey(password)}
RawDatabase::RawDatabase(const QString& path, const QString& password, const QByteArray& salt)
: workerThread{new QThread}
, path{path}
, currentSalt{salt} // we need the salt later if a new password should be set
, currentHexKey{deriveKey(password, salt)}
{
workerThread->setObjectName("qTox Database");
moveToThread(workerThread.get());
workerThread->start();
if (!open(path, currentHexKey))
// first try with the new salt
if (open(path, currentHexKey))
{
return;
}
// avoid opening the same db twice
close();
// fall back to the old salt
currentHexKey = deriveKey(password);
if(open(path, currentHexKey))
{
// still using old salt, upgrade
if(setPassword(password))
{
qDebug() << "Successfully upgraded to dynamic salt";
}
else
{
qWarning() << "Failed to set password with new salt";
}
}
else
{
qDebug() << "Failed to open database with old salt";
}
}
RawDatabase::~RawDatabase()
@ -313,7 +341,7 @@ bool RawDatabase::setPassword(const QString& password)
if (!password.isEmpty())
{
QString newHexKey = deriveKey(password);
QString newHexKey = deriveKey(password, currentSalt);
if (!currentHexKey.isEmpty())
{
if (!execNow("PRAGMA rekey = \"x'"+newHexKey+"'\""))
@ -445,6 +473,7 @@ bool RawDatabase::remove()
* @brief Derives a 256bit key from the password and returns it hex-encoded
* @param password Password to decrypt database
* @return String representation of key
* @deprecated deprecated on 2016-11-06, kept for compatibility, replaced by the salted version
*/
QString RawDatabase::deriveKey(const QString &password)
{
@ -457,8 +486,37 @@ QString RawDatabase::deriveKey(const QString &password)
static const uint8_t expandConstant[TOX_PASS_SALT_LENGTH+1] = "L'ignorance est le pire des maux";
TOX_PASS_KEY key;
tox_derive_key_with_salt((uint8_t*)passData.data(), passData.size(), expandConstant, &key, nullptr);
return QByteArray((char*)key.key, 32).toHex();
tox_derive_key_with_salt(reinterpret_cast<uint8_t*>(passData.data()), static_cast<std::size_t>(passData.size()), expandConstant, &key, nullptr);
return QByteArray(reinterpret_cast<char*>(key.key), 32).toHex();
}
/**
* @brief Derives a 256bit key from the password and returns it hex-encoded
* @param password Password to decrypt database
* @param salt Salt to improve password strength, must be TOX_PASS_SALT_LENGTH bytes
* @return String representation of key
*/
QString RawDatabase::deriveKey(const QString& password, const QByteArray& salt)
{
if (password.isEmpty())
{
return {};
}
if (salt.length() != TOX_PASS_SALT_LENGTH)
{
qWarning() << "Salt length doesn't match toxencryptsave expections";
return {};
}
QByteArray passData = password.toUtf8();
static_assert(TOX_PASS_KEY_LENGTH >= 32, "toxcore must provide 256bit or longer keys");
TOX_PASS_KEY key;
tox_derive_key_with_salt(reinterpret_cast<uint8_t*>(passData.data()), static_cast<std::size_t>(passData.size()),
reinterpret_cast<const uint8_t*>(salt.constData()), &key, nullptr);
return QByteArray(reinterpret_cast<char*>(key.key), 32).toHex();
}
/**

View File

@ -41,7 +41,7 @@ public:
};
public:
RawDatabase(const QString& path, const QString& password);
RawDatabase(const QString& path, const QString& password, const QByteArray& salt);
~RawDatabase();
bool isOpen();
@ -68,7 +68,8 @@ private:
QString anonymizeQuery(const QByteArray& query);
protected:
static QString deriveKey(const QString &password);
static QString deriveKey(const QString& password, const QByteArray& salt);
static QString deriveKey(const QString& password);
static QVariant extractData(sqlite3_stmt* stmt, int col);
private:
@ -85,6 +86,7 @@ private:
QQueue<Transaction> pendingTransactions;
QMutex transactionsMutex;
QString path;
QByteArray currentSalt;
QString currentHexKey;
};

View File

@ -56,7 +56,6 @@ QVector<QString> Profile::profiles;
Profile::Profile(QString name, const QString& password, bool isNewProfile)
: name{name}, password{password}
, database(std::make_shared<RawDatabase>(getDbPath(name), password))
, newProfile{isNewProfile}, isRemoved{false}
{
if (!password.isEmpty())
@ -68,22 +67,10 @@ Profile::Profile(QString name, const QString& password, bool isNewProfile)
s.setCurrentProfile(name);
s.saveGlobal();
// At this point it's too early to load the personal settings (Nexus will do
// it), so we always load the history, and if it fails we can't change the
// setting now, but we keep a nullptr
if (database->isOpen())
{
history.reset(new History(database));
}
else
{
qWarning() << "Failed to open history for profile" << name;
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
}
coreThread = new QThread();
coreThread->setObjectName("qTox Core");
core = new Core(coreThread, *this);
QObject::connect(core, &Core::idSet, this, &Profile::loadDatabase, Qt::QueuedConnection);
core->moveToThread(coreThread);
QObject::connect(coreThread, &QThread::started, core, &Core::start);
}
@ -512,6 +499,34 @@ QByteArray Profile::loadAvatarData(const QString& ownerId, const QString& passwo
return pic;
}
void Profile::loadDatabase(const QString& id)
{
if(isRemoved)
{
qDebug() << "Can't load database of removed profile";
return;
}
QByteArray salt = QByteArray::fromHex(ToxId {id}.publicKey.toUtf8());
if(salt.size() != TOX_PASS_SALT_LENGTH)
{
qWarning() << "Couldn't compute salt from public key" << name;
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
}
// At this point it's too early to load the personal settings (Nexus will do it), so we always load
// the history, and if it fails we can't change the setting now, but we keep a nullptr
database = std::make_shared<RawDatabase>(getDbPath(name), password, salt);
if (database && database->isOpen())
{
history.reset(new History(database));
}
else
{
qWarning() << "Failed to open database for profile" << name;
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
}
}
/**
* @brief Save an avatar to cache.
* @param pic Picture to save.
@ -688,11 +703,12 @@ QVector<QString> Profile::remove()
}
QString dbPath = getDbPath(name);
if (database->isOpen() && !database->remove() && QFile::exists(dbPath))
if (database && database->isOpen() && !database->remove() && QFile::exists(dbPath))
{
ret.push_back(dbPath);
qWarning() << "Could not remove file " << dbPath;
}
history.release();
database.reset();

View File

@ -25,6 +25,7 @@
#include <QString>
#include <QByteArray>
#include <QPixmap>
#include <QObject>
#include <tox/toxencryptsave.h>
#include <memory>
#include "src/persistence/history.h"
@ -32,8 +33,10 @@
class Core;
class QThread;
class Profile
class Profile : public QObject
{
Q_OBJECT
public:
static Profile* loadProfile(QString name, const QString& password = QString());
static Profile* createProfile(QString name, QString password);
@ -78,6 +81,8 @@ public:
static bool isEncrypted(QString name);
static QString getDbPath(const QString& profileName);
private slots:
void loadDatabase(const QString& id);
private:
Profile(QString name, const QString& password, bool newProfile);
static QVector<QString> getFilesByExt(QString extension);