From 47f1f7dd74508a3ce245539afa03516844169811 Mon Sep 17 00:00:00 2001 From: Toni Date: Sat, 25 Apr 2026 13:06:54 +0200 Subject: [PATCH] manage nonces in crypto.h --- CMakeLists.txt | 5 +++++ README.md | 2 +- src/common.h | 9 ++------- src/crypto.h | 41 +++++++++++++++++++++++------------------ src/mainwindow.cc | 26 +++++++++++++++----------- src/vault.cc | 40 +++++++++------------------------------- src/vault.h | 3 --- 7 files changed, 55 insertions(+), 71 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ace832..2c61f3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,11 @@ project(dull) set(CMAKE_CXX_STANDARD 20) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +# fix clang +foreach(flag_var CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_DEBUG) + string(REPLACE "-mno-direct-extern-access" "" ${flag_var} "${${flag_var}}") +endforeach() + find_package(Qt6 REQUIRED COMPONENTS Core Widgets) if(NOT WIN32) find_package(PkgConfig REQUIRED) diff --git a/README.md b/README.md index 573d1b2..3a39dc8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Desktop app for securely storing sensitive files ## Features * **Pretty usable UI** -* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id(m=1GB,t=8,p=4) key derivation +* **Overkill encryption:** XChaCha20-Poly1305 + Argon2id key derivation * **Cross-platform:** Tested on Linux, Windows and macOS * **Drag and Drop support** diff --git a/src/common.h b/src/common.h index fa33265..0b7a6ef 100644 --- a/src/common.h +++ b/src/common.h @@ -9,12 +9,12 @@ if (!(cond)) { \ std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ \ << "\n" \ - << #cond << std::endl; \ + << #cond << '\n'; \ abort(); \ } \ } while (0) -using u8 = unsigned char; +using u8 = uint8_t; using i16 = int16_t; using u16 = uint16_t; using i32 = int32_t; @@ -26,11 +26,6 @@ using f32 = float; static_assert(sizeof(double) * 8 == 64); using f64 = double; -inline std::string path_to_filename(const std::string &path) { - u64 pos = path.find_last_of("/\\"); - return (pos == std::string::npos) ? path : path.substr(pos + 1); -} - template constexpr char *to_char_ptr(T *ptr) noexcept { return reinterpret_cast(ptr); } diff --git a/src/crypto.h b/src/crypto.h index 39ad7ff..6bc2e89 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -1,16 +1,18 @@ #pragma once #include "common.h" #include +#include #include namespace Crypto { inline Botan::secure_vector -encrypt_xchacha20_poly1305(const Botan::secure_vector &plaintext, - const Botan::secure_vector &key, - const std::array &nonce) { +encrypt(const Botan::secure_vector &key, + const Botan::secure_vector &plaintext) { ASSERT(key.size() == 32); - ASSERT(nonce.size() == 24); + + static Botan::AutoSeeded_RNG rng; + auto nonce = rng.random_array<24>(); // XChaCha20 is selected automatically based on the nonce size // https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375 @@ -23,16 +25,20 @@ encrypt_xchacha20_poly1305(const Botan::secure_vector &plaintext, Botan::secure_vector ciphertext = plaintext; cipher->finish(ciphertext); - return ciphertext; + Botan::secure_vector res; + res.insert(res.end(), nonce.begin(), nonce.end()); + res.insert(res.end(), ciphertext.begin(), ciphertext.end()); + return res; } inline Botan::secure_vector -decrypt_xchacha20_poly1305(const Botan::secure_vector &ciphertext, - const Botan::secure_vector &key, - const std::array &nonce) { - ASSERT(ciphertext.size() >= 16); +decrypt(const Botan::secure_vector &key, + const Botan::secure_vector &ciphertext_with_nonce) { + ASSERT(ciphertext_with_nonce.size() >= 40); ASSERT(key.size() == 32); - ASSERT(nonce.size() == 24); + + std::array nonce{}; + std::copy_n(ciphertext_with_nonce.begin(), 24, nonce.begin()); auto cipher = Botan::AEAD_Mode::create_or_throw( "ChaCha20Poly1305", Botan::Cipher_Dir::Decryption); @@ -40,18 +46,17 @@ decrypt_xchacha20_poly1305(const Botan::secure_vector &ciphertext, cipher->set_key(key); cipher->start(nonce); - Botan::secure_vector plaintext = ciphertext; - cipher->finish(plaintext); + Botan::secure_vector ciphertext(ciphertext_with_nonce.begin() + 24, + ciphertext_with_nonce.end()); + cipher->finish(ciphertext); - return plaintext; + return ciphertext; } -inline Botan::secure_vector -derive_key_argon2id(const std::string &password, - const std::array &salt) { - // thousands of years to crack a random 8 char password on a 100 GPUs +inline Botan::secure_vector derive_key(const std::string &password, + const std::array &salt) { auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id") - ->from_params(static_cast(1024 * 1024), 8, 4); + ->from_params(static_cast(512 * 1024), 8, 4); Botan::secure_vector key(32); pwdhash->derive_key(key.data(), key.size(), password.data(), password.size(), diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 0ace10c..beced93 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -8,9 +8,17 @@ #include #include #include -#include #include +namespace { + +std::string basename(const std::string &path) { + u64 pos = path.find_last_of("/\\"); + return (pos == std::string::npos) ? path : path.substr(pos + 1); +} + +} // namespace + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(std::make_unique()) { ui->setupUi(this); @@ -45,21 +53,18 @@ MainWindow::MainWindow(QWidget *parent) static Botan::AutoSeeded_RNG rng; auto salt = rng.random_array<16>(); - auto key = Crypto::derive_key_argon2id(password.toStdString(), salt); - auto check_nonce = rng.random_array<24>(); + auto key = Crypto::derive_key(password.toStdString(), salt); - const std::string content = "LETSGO"; + const std::string_view content = "LETSGO"; Botan::secure_vector content_sv(content.begin(), content.end()); - auto check_ciphertext = - Crypto::encrypt_xchacha20_poly1305(content_sv, key, check_nonce); + auto check_ciphertext = Crypto::encrypt(key, content_sv); 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.write(to_char_ptr(check_nonce.data()), 24); - create.write(to_char_ptr(check_ciphertext.data()), 22); + create.write(to_char_ptr(check_ciphertext.data()), 46); create.close(); m_vault = @@ -118,7 +123,7 @@ MainWindow::MainWindow(QWidget *parent) std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - m_vault->create_file(path_to_filename(path.toStdString()), content); + m_vault->create_file(basename(path.toStdString()), content); } reload_fs_tree(); @@ -294,8 +299,7 @@ void MainWindow::dropEvent(QDropEvent *event) { std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - m_vault->create_file(path_to_filename(u.toLocalFile().toStdString()), - content); + m_vault->create_file(basename(u.toLocalFile().toStdString()), content); } ui->statusbar->showMessage( "Added " + QString::number(event->mimeData()->urls().size()) + " files"); diff --git a/src/vault.cc b/src/vault.cc index 8840516..c221176 100644 --- a/src/vault.cc +++ b/src/vault.cc @@ -20,16 +20,13 @@ Vault::Vault(std::string path, const std::string &password) std::array salt{}; ASSERT(m_file.read(to_char_ptr(salt.data()), 16)); - m_key = Crypto::derive_key_argon2id(password, salt); - - std::array check_nonce{}; - ASSERT(m_file.read(to_char_ptr(check_nonce.data()), 24)); + m_key = Crypto::derive_key(password, salt); Botan::secure_vector check_ciphertext; - check_ciphertext.resize(22); - ASSERT(m_file.read(to_char_ptr(check_ciphertext.data()), 22)); + check_ciphertext.resize(46); + ASSERT(m_file.read(to_char_ptr(check_ciphertext.data()), 46)); - Crypto::decrypt_xchacha20_poly1305(check_ciphertext, m_key, check_nonce); + Crypto::decrypt(m_key, check_ciphertext); } std::vector Vault::read_file_headers() { @@ -70,8 +67,7 @@ std::optional Vault::read_file(const std::string &filename) { break; } - auto plaintext = Crypto::decrypt_xchacha20_poly1305( - ciphertext, m_key, header->content_nonce); + auto plaintext = Crypto::decrypt(m_key, ciphertext); return std::string(to_char_ptr(plaintext.data()), plaintext.size()); } @@ -87,25 +83,17 @@ void Vault::create_file(const std::string &filename, m_file.clear(); m_file.seekp(0, std::ios::end); - static Botan::AutoSeeded_RNG rng; - auto name_nonce = rng.random_array<24>(); - auto content_nonce = rng.random_array<24>(); - Botan::secure_vector filename_sv(filename.begin(), filename.end()); - auto filename_ciphertext = - Crypto::encrypt_xchacha20_poly1305(filename_sv, m_key, name_nonce); + auto filename_ciphertext = Crypto::encrypt(m_key, filename_sv); u64 filename_ciphertext_size = filename_ciphertext.size(); Botan::secure_vector content_sv(content.begin(), content.end()); - auto ciphertext = - Crypto::encrypt_xchacha20_poly1305(content_sv, m_key, content_nonce); + auto ciphertext = Crypto::encrypt(m_key, content_sv); u64 ciphertext_size = ciphertext.size(); - ASSERT(m_file.write(to_char_ptr(name_nonce.data()), name_nonce.size())); ASSERT(m_file.write(to_char_ptr(&filename_ciphertext_size), sizeof(u64))); ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()), static_cast(filename_ciphertext_size))); - ASSERT(m_file.write(to_char_ptr(content_nonce.data()), content_nonce.size())); ASSERT(m_file.write(to_char_ptr(&ciphertext_size), sizeof(u64))); ASSERT(m_file.write(to_char_ptr(ciphertext.data()), static_cast(ciphertext_size))); @@ -129,7 +117,7 @@ void Vault::delete_file(const std::string &filename) { if (header->name == filename) { entry_start = current_pos; - entry_total_size = 24 + sizeof(u64) + header->name_ciphertext_size + 24 + + entry_total_size = sizeof(u64) + header->name_ciphertext_size + sizeof(u64) + header->content_ciphertext_size; m_file.seekg(static_cast(header->content_ciphertext_size), std::ios::cur); @@ -181,10 +169,6 @@ void Vault::update_file(const std::string &filename, std::optional Vault::read_file_header() { FileHeader header{}; - if (!m_file.read(to_char_ptr(header.name_nonce.data()), 24)) { - return std::nullopt; - } - if (!m_file.read(to_char_ptr(&header.name_ciphertext_size), sizeof(u64))) { return std::nullopt; } @@ -198,14 +182,8 @@ std::optional Vault::read_file_header() { return std::nullopt; } - auto name = Crypto::decrypt_xchacha20_poly1305(name_ciphertext, m_key, - header.name_nonce); + auto name = Crypto::decrypt(m_key, name_ciphertext); header.name = std::string(name.begin(), name.end()); - - if (!m_file.read(to_char_ptr(header.content_nonce.data()), 24)) { - return std::nullopt; - } - if (!m_file.read(to_char_ptr(&header.content_ciphertext_size), sizeof(u64))) { return std::nullopt; } diff --git a/src/vault.h b/src/vault.h index 30a4d5b..b89d958 100644 --- a/src/vault.h +++ b/src/vault.h @@ -1,7 +1,6 @@ #pragma once #include "common.h" -#include #include #include #include @@ -11,10 +10,8 @@ constexpr u64 AFTER_HEADER_OFFSET = 68; // !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file struct FileHeader { - std::array name_nonce; u64 name_ciphertext_size; std::string name; - std::array content_nonce; u64 content_ciphertext_size; };