From 871c76d0181d836e4fba73afe7e44ebde27dde4c Mon Sep 17 00:00:00 2001 From: Toni Date: Mon, 3 Nov 2025 16:01:17 +0100 Subject: [PATCH] per-vault salt, readme, windows --- CMakeLists.txt | 6 ++++-- README.md | 28 ++++++++++++++++++++++++++++ src/crypto.h | 6 +----- src/mainwindow.cc | 26 ++++++++++++++++++-------- src/vault.cc | 17 +++++++++++------ src/vault.h | 2 +- 6 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 README.md diff --git a/CMakeLists.txt b/CMakeLists.txt index bfb4d2f..6aecb39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,8 +5,10 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets) -find_package(PkgConfig REQUIRED) -pkg_check_modules(BOTAN REQUIRED botan-3) +if(NOT WIN32) + find_package(PkgConfig REQUIRED) + pkg_check_modules(BOTAN REQUIRED botan-3) +endif() set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) diff --git a/README.md b/README.md new file mode 100644 index 0000000..b63cb91 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# dull + +Desktop app for securely storing sensitive files + +## Features +* **Pretty usable UI** +* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id(m=1GB,t=8,p=4) key derivation +* **Cross-platform-ish:** Supports Linux and Windows + +## Building + +### Debian/Ubuntu +``` +sudo apt update && sudo apt install build-essential qt6-base-dev libbotan-3-dev cmake + +cmake -S . -B build +cmake --build build -j $(nproc) +./build/dull +``` + +### Windows / MSYS2 +``` +pacman -S --needed mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools mingw-w64-x86_64-libbotan + +cmake -S . -B build -DBOTAN_INCLUDE_DIRS=/mingw64/include/botan-3 -DBOTAN_LIBRARIES=/mingw64/lib/libbotan-3.a +cmake --build build -j $(nproc) +./build/dull +``` \ No newline at end of file diff --git a/src/crypto.h b/src/crypto.h index f788bd2..1fcdd00 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -1,17 +1,12 @@ #pragma once #include "common.h" #include -#include #include #include #include namespace Crypto { -// TODO: random salt per per-vault -const std::vector SALT = {0xab, 0xfb, 0x45, 0x1, 0x62, 0xcf, 0xd7, 0x46, - 0xdf, 0xaf, 0x4e, 0xa9, 0xf0, 0x4b, 0x9f, 0x38}; - inline Botan::secure_vector encrypt_xchacha20_poly1305(const Botan::secure_vector &plaintext, const Botan::secure_vector &key, @@ -55,6 +50,7 @@ decrypt_xchacha20_poly1305(const Botan::secure_vector &ciphertext, inline Botan::secure_vector derive_key_argon2id(const std::string &password, const std::vector &salt) { + // thousands of years to crack a random 8 char password on a 100 GPUs auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id") ->from_params(static_cast(1024 * 1024), 8, 4); diff --git a/src/mainwindow.cc b/src/mainwindow.cc index dbb4e57..c566c5f 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include MainWindow::MainWindow(QWidget *parent) @@ -22,15 +23,20 @@ MainWindow::MainWindow(QWidget *parent) QString password = QInputDialog::getText( this, "Choose a password", "Choose a password", QLineEdit::Password); - if (password.length() < 5) { + if (password.length() < 8) { QMessageBox::critical(this, "Error", - "Password must be at least 5 characters long."); + "Password must be at least 8 characters long."); return; } + static Botan::AutoSeeded_RNG rng; + auto salt_sv = rng.random_vec(16); + std::vector salt(salt_sv.begin(), salt_sv.end()); + std::ofstream create(path.toStdString(), std::ios::binary); create.write("DULL", 4); create.write(to_char_ptr(&VERSION), sizeof(VERSION)); + create.write(to_char_ptr(salt.data()), 16); create.close(); m_vault = @@ -48,11 +54,6 @@ MainWindow::MainWindow(QWidget *parent) QString password = QInputDialog::getText( this, "Unlock the vault", "Enter vault password", QLineEdit::Password); - if (password.length() < 5) { - QMessageBox::critical(this, "Error", - "Password must be at least 5 characters long."); - return; - } // TODO: check if password valid @@ -100,7 +101,9 @@ void MainWindow::reload_fs_tree() { item->setText(2, QString::number(header.offset)); item->setText(3, QString::fromStdString(Botan::hex_encode(header.nonce))); } - ui->fsTreeWidget->resizeColumnToContents(0); + for (int i = 0; i < ui->fsTreeWidget->columnCount(); ++i) { + ui->fsTreeWidget->resizeColumnToContents(i); + } } void MainWindow::preview_file(const std::string &filename) { @@ -159,5 +162,12 @@ void MainWindow::file_context_menu(const QPoint &pos) { connect(edit_action, &QAction::triggered, this, [this, item]() { edit_file(item->text(0).toStdString()); }); + QAction *delete_action = menu.addAction( + style()->standardIcon(QStyle::SP_DialogCancelButton), "Delete"); + connect(delete_action, &QAction::triggered, this, [this, item]() { + m_vault->delete_file(item->text(0).toStdString()); + reload_fs_tree(); + }); + menu.exec(ui->fsTreeWidget->mapToGlobal(pos)); } diff --git a/src/vault.cc b/src/vault.cc index 2779c22..2d363c5 100644 --- a/src/vault.cc +++ b/src/vault.cc @@ -2,6 +2,7 @@ #include "common.h" #include "crypto.h" #include +#include #include Vault::Vault(std::string path, const std::string &password) @@ -9,15 +10,19 @@ Vault::Vault(std::string path, const std::string &password) m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); ASSERT(m_file.good()); - m_key = Crypto::derive_key_argon2id(password, Crypto::SALT); - - std::array header{}; - ASSERT(static_cast(m_file.read(header.data(), header.size()))); - ASSERT(std::string_view(header.data(), header.size()) == "DULL"); + std::array magic{}; + ASSERT(m_file.read(magic.data(), 4)); + ASSERT(std::string_view(magic.data(), magic.size()) == "DULL"); i16 version = 0; ASSERT(m_file.read(to_char_ptr(&version), sizeof(version))); ASSERT(version == VERSION); + + std::vector salt{}; + salt.resize(16); + ASSERT(m_file.read(to_char_ptr(salt.data()), 16)); + + m_key = Crypto::derive_key_argon2id(password, salt); } std::vector Vault::read_file_headers() { @@ -77,7 +82,7 @@ void Vault::create_file(const std::string &filename, Botan::secure_vector plaintext(content.begin(), content.end()); - Botan::AutoSeeded_RNG rng; + static Botan::AutoSeeded_RNG rng; auto nonce_sv = rng.random_vec(24); std::vector nonce(nonce_sv.begin(), nonce_sv.end()); diff --git a/src/vault.h b/src/vault.h index c086359..c6a0726 100644 --- a/src/vault.h +++ b/src/vault.h @@ -7,7 +7,7 @@ #include constexpr i16 VERSION = 1; -constexpr u64 AFTER_HEADER_OFFSET = 6; +constexpr u64 AFTER_HEADER_OFFSET = 22; struct FileHeader { std::string name;