update: encryption/decryption logic, backup encryption on save and decryption on restore

This commit is contained in:
MrMirDan
2025-12-18 12:25:55 +02:00
parent a7ae0bc65e
commit ba580891cd
12 changed files with 308 additions and 215 deletions
@@ -110,6 +110,8 @@ void SettingsUiController::exportLogsFile(const QString &fileName)
if (!SystemController::saveFile(fileName, Logger::getLogFile())) {
qInfo() << "SettingsUiController::exportLogsFile: save or share was cancelled or failed";
}
if (isFileEncryptionEnabled())
SystemController::encryptFile(fileName, getPassword(), getHint());
#endif
}
@@ -121,6 +123,8 @@ void SettingsUiController::exportServiceLogsFile(const QString &fileName)
if (!SystemController::saveFile(fileName, Logger::getServiceLogFile())) {
qInfo() << "SettingsUiController::exportServiceLogsFile: save or share was cancelled or failed";
}
if (isFileEncryptionEnabled())
SystemController::encryptFile(fileName, getPassword(), getHint());
#endif
}
@@ -139,6 +143,8 @@ void SettingsUiController::backupAppConfig(const QString &fileName)
if (!SystemController::saveFile(fileName, data)) {
qInfo() << "SettingsUiController::backupAppConfig: save or share was cancelled or failed";
}
if (isFileEncryptionEnabled())
SystemController::encryptFile(fileName, getPassword(), getHint());
}
void SettingsUiController::restoreAppConfig(const QString &fileName)
+254
View File
@@ -11,6 +11,20 @@
#include <QUrl>
#include <QtConcurrent>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
namespace
{
constexpr int SALT_LEN = 16;
constexpr int IV_LEN = 16;
constexpr int KEY_LEN = 32;
constexpr int PBKDF2_ITER = 100000;
const QByteArray magicString { "EncData" };
}
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
@@ -104,6 +118,246 @@ bool SystemController::readFile(const QString &fileName, QString &data)
return true;
}
static QString opensslErrString()
{
unsigned long e = ERR_get_error();
if (!e)
return QStringLiteral("Unknown OpenSSL error");
char buf[256];
ERR_error_string_n(e, buf, sizeof(buf));
return QString::fromUtf8(buf);
}
static bool deriveKey(const QByteArray &password, const QByteArray &salt, QByteArray &outKey, QString *err)
{
outKey.resize(KEY_LEN);
const unsigned char *pw = reinterpret_cast<const unsigned char *>(password.constData());
const unsigned char *s = reinterpret_cast<const unsigned char *>(salt.constData());
int ok = PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(pw), password.size(), s, salt.size(), PBKDF2_ITER,
EVP_sha256(), KEY_LEN, reinterpret_cast<unsigned char *>(outKey.data()));
if (!ok) {
if (err)
*err = opensslErrString();
}
return ok == 1;
}
static bool aesCrypt(const QByteArray &in, const QByteArray &key, const QByteArray &iv, QByteArray &out, bool encrypt,
QString *err)
{
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
if (err)
*err = "EVP_CIPHER_CTX_new failed";
return false;
}
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
if (1
!= EVP_CipherInit_ex(ctx, cipher, nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
reinterpret_cast<const unsigned char *>(iv.constData()), encrypt ? 1 : 0)) {
if (err)
*err = opensslErrString();
EVP_CIPHER_CTX_free(ctx);
return false;
}
out.clear();
out.resize(in.size() + EVP_CIPHER_block_size(cipher));
int outlen1 = 0;
if (1
!= EVP_CipherUpdate(ctx, reinterpret_cast<unsigned char *>(out.data()), &outlen1,
reinterpret_cast<const unsigned char *>(in.constData()), in.size())) {
if (err)
*err = opensslErrString();
EVP_CIPHER_CTX_free(ctx);
return false;
}
int outlen2 = 0;
if (1 != EVP_CipherFinal_ex(ctx, reinterpret_cast<unsigned char *>(out.data()) + outlen1, &outlen2)) {
if (err)
*err = opensslErrString();
EVP_CIPHER_CTX_free(ctx);
return false;
}
out.resize(outlen1 + outlen2);
EVP_CIPHER_CTX_free(ctx);
return true;
}
bool SystemController::encryptFile(const QString &filePath, const QString &password, const QString &hint, QString *error)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
if (error)
*error = QStringLiteral("Cannot open file for read: %1").arg(f.errorString());
return false;
}
QByteArray content = f.readAll();
f.close();
if (content.startsWith(magicString)) {
if (error)
*error = QStringLiteral("File already encrypted (magic found)");
return false;
}
QByteArray qba_hint = hint.toUtf8();
quint32 qba_hint_len = static_cast<quint32>(qba_hint.size());
QByteArray salt(SALT_LEN, 0);
QByteArray iv(IV_LEN, 0);
QByteArray key;
QByteArray cipher;
QByteArray out;
if (1 != RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_LEN)
|| 1 != RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_LEN)) {
if (error)
*error = opensslErrString();
return false;
}
if (!deriveKey(password.toUtf8(), salt, key, error))
return false;
if (!aesCrypt(content, key, iv, cipher, true, error))
return false;
out.reserve(magicString.size() + SALT_LEN + IV_LEN + cipher.size());
out += magicString;
out.append(reinterpret_cast<const char *>(&qba_hint_len), sizeof(qba_hint_len));
out += hint.toUtf8();
out += salt;
out += iv;
out += cipher;
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
if (error)
*error = QStringLiteral("Cannot open file for write: %1").arg(f.errorString());
return false;
}
qint64 written = f.write(out);
f.close();
if (written != out.size()) {
if (error)
*error = QStringLiteral("Write failed or incomplete");
return false;
}
return true;
}
bool SystemController::decryptFile(const QString &filePath, const QString &password, QString *error)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
if (error)
*error = QStringLiteral("Cannot open file for read: %1").arg(f.errorString());
return false;
}
QByteArray content = f.readAll();
f.close();
if (!content.startsWith(magicString)) {
if (error)
*error = QStringLiteral("File is not recognized as encrypted (magic missing)");
return false;
}
int pos = magicString.size();
quint32 hintLen = 0;
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
pos += hintLen;
if (content.size() < pos + SALT_LEN + IV_LEN) {
if (error)
*error = QStringLiteral("Encrypted file too small / corrupted");
return false;
}
QByteArray salt = content.mid(pos, SALT_LEN);
pos += SALT_LEN;
QByteArray iv = content.mid(pos, IV_LEN);
pos += IV_LEN;
QByteArray cipher = content.mid(pos);
QByteArray key;
QByteArray plain;
if (!deriveKey(password.toUtf8(), salt, key, error))
return false;
if (!aesCrypt(cipher, key, iv, plain, false, error))
return false;
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
if (error)
*error = QStringLiteral("Cannot open file for write: %1").arg(f.errorString());
return false;
}
qint64 written = f.write(plain);
f.close();
if (written != plain.size()) {
if (error)
*error = QStringLiteral("Write failed or incomplete");
return false;
}
return true;
}
bool SystemController::isFileEncrypted(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file for read: %1", f.errorString();
return false;
}
QByteArray content = f.readAll();
f.close();
if (!content.startsWith(magicString)) {
qDebug() << "File is not recognized as encrypted (magic missing)";
return false;
}
return true;
}
QString SystemController::readHint(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << f.errorString();
return {};
}
QByteArray data = f.readAll();
f.close();
if (!data.startsWith(magicString)) {
qDebug() << "File is not encrypted";
return {};
}
int pos = magicString.size();
if (data.size() < pos + static_cast<int>(sizeof(quint32))) {
qDebug() << "Corrupted file (no hint length)";
return {};
}
quint32 hintLen = 0;
memcpy(&hintLen, data.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
if (data.size() < pos + static_cast<int>(hintLen)) {
qDebug() << "Corrupted file (hint truncated)";
return {};
}
return QString::fromUtf8(data.constData() + pos, hintLen);
}
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
{
+7
View File
@@ -15,10 +15,17 @@ public:
static bool readFile(const QString &fileName, QByteArray &data);
static bool readFile(const QString &fileName, QString &data);
static bool encryptFile(const QString &filePath, const QString &password, const QString &hint, QString *error = nullptr);
public slots:
QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "",
const bool isSaveMode = false, const QString &defaultSuffix = "");
bool decryptFile(const QString &filePath, const QString &password, QString *error = nullptr);
bool isFileEncrypted(const QString &filePath);
QString readHint(const QString &filePath);
void setQmlRoot(QObject *qmlRoot);
bool isAuthenticated();