manage nonces in crypto.h
This commit is contained in:
@@ -4,6 +4,11 @@ project(dull)
|
|||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
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)
|
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
||||||
if(NOT WIN32)
|
if(NOT WIN32)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Desktop app for securely storing sensitive files
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
* **Pretty usable UI**
|
* **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
|
* **Cross-platform:** Tested on Linux, Windows and macOS
|
||||||
* **Drag and Drop support**
|
* **Drag and Drop support**
|
||||||
|
|
||||||
|
|||||||
@@ -9,12 +9,12 @@
|
|||||||
if (!(cond)) { \
|
if (!(cond)) { \
|
||||||
std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ \
|
std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ \
|
||||||
<< "\n" \
|
<< "\n" \
|
||||||
<< #cond << std::endl; \
|
<< #cond << '\n'; \
|
||||||
abort(); \
|
abort(); \
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
using u8 = unsigned char;
|
using u8 = uint8_t;
|
||||||
using i16 = int16_t;
|
using i16 = int16_t;
|
||||||
using u16 = uint16_t;
|
using u16 = uint16_t;
|
||||||
using i32 = int32_t;
|
using i32 = int32_t;
|
||||||
@@ -26,11 +26,6 @@ using f32 = float;
|
|||||||
static_assert(sizeof(double) * 8 == 64);
|
static_assert(sizeof(double) * 8 == 64);
|
||||||
using f64 = double;
|
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 <typename T> constexpr char *to_char_ptr(T *ptr) noexcept {
|
template <typename T> constexpr char *to_char_ptr(T *ptr) noexcept {
|
||||||
return reinterpret_cast<char *>(ptr);
|
return reinterpret_cast<char *>(ptr);
|
||||||
}
|
}
|
||||||
|
|||||||
41
src/crypto.h
41
src/crypto.h
@@ -1,16 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include <botan/aead.h>
|
#include <botan/aead.h>
|
||||||
|
#include <botan/auto_rng.h>
|
||||||
#include <botan/pwdhash.h>
|
#include <botan/pwdhash.h>
|
||||||
|
|
||||||
namespace Crypto {
|
namespace Crypto {
|
||||||
|
|
||||||
inline Botan::secure_vector<u8>
|
inline Botan::secure_vector<u8>
|
||||||
encrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &plaintext,
|
encrypt(const Botan::secure_vector<u8> &key,
|
||||||
const Botan::secure_vector<u8> &key,
|
const Botan::secure_vector<u8> &plaintext) {
|
||||||
const std::array<u8, 24> &nonce) {
|
|
||||||
ASSERT(key.size() == 32);
|
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
|
// XChaCha20 is selected automatically based on the nonce size
|
||||||
// https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375
|
// 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<u8> &plaintext,
|
|||||||
Botan::secure_vector<u8> ciphertext = plaintext;
|
Botan::secure_vector<u8> ciphertext = plaintext;
|
||||||
cipher->finish(ciphertext);
|
cipher->finish(ciphertext);
|
||||||
|
|
||||||
return ciphertext;
|
Botan::secure_vector<u8> res;
|
||||||
|
res.insert(res.end(), nonce.begin(), nonce.end());
|
||||||
|
res.insert(res.end(), ciphertext.begin(), ciphertext.end());
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Botan::secure_vector<u8>
|
inline Botan::secure_vector<u8>
|
||||||
decrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &ciphertext,
|
decrypt(const Botan::secure_vector<u8> &key,
|
||||||
const Botan::secure_vector<u8> &key,
|
const Botan::secure_vector<u8> &ciphertext_with_nonce) {
|
||||||
const std::array<u8, 24> &nonce) {
|
ASSERT(ciphertext_with_nonce.size() >= 40);
|
||||||
ASSERT(ciphertext.size() >= 16);
|
|
||||||
ASSERT(key.size() == 32);
|
ASSERT(key.size() == 32);
|
||||||
ASSERT(nonce.size() == 24);
|
|
||||||
|
std::array<u8, 24> nonce{};
|
||||||
|
std::copy_n(ciphertext_with_nonce.begin(), 24, nonce.begin());
|
||||||
|
|
||||||
auto cipher = Botan::AEAD_Mode::create_or_throw(
|
auto cipher = Botan::AEAD_Mode::create_or_throw(
|
||||||
"ChaCha20Poly1305", Botan::Cipher_Dir::Decryption);
|
"ChaCha20Poly1305", Botan::Cipher_Dir::Decryption);
|
||||||
@@ -40,18 +46,17 @@ decrypt_xchacha20_poly1305(const Botan::secure_vector<u8> &ciphertext,
|
|||||||
cipher->set_key(key);
|
cipher->set_key(key);
|
||||||
cipher->start(nonce);
|
cipher->start(nonce);
|
||||||
|
|
||||||
Botan::secure_vector<u8> plaintext = ciphertext;
|
Botan::secure_vector<u8> ciphertext(ciphertext_with_nonce.begin() + 24,
|
||||||
cipher->finish(plaintext);
|
ciphertext_with_nonce.end());
|
||||||
|
cipher->finish(ciphertext);
|
||||||
|
|
||||||
return plaintext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline Botan::secure_vector<u8>
|
inline Botan::secure_vector<u8> derive_key(const std::string &password,
|
||||||
derive_key_argon2id(const std::string &password,
|
const std::array<u8, 16> &salt) {
|
||||||
const std::array<u8, 16> &salt) {
|
|
||||||
// thousands of years to crack a random 8 char password on a 100 GPUs
|
|
||||||
auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id")
|
auto pwdhash = Botan::PasswordHashFamily::create_or_throw("Argon2id")
|
||||||
->from_params(static_cast<u64>(1024 * 1024), 8, 4);
|
->from_params(static_cast<u64>(512 * 1024), 8, 4);
|
||||||
|
|
||||||
Botan::secure_vector<u8> key(32);
|
Botan::secure_vector<u8> key(32);
|
||||||
pwdhash->derive_key(key.data(), key.size(), password.data(), password.size(),
|
pwdhash->derive_key(key.data(), key.size(), password.data(), password.size(),
|
||||||
|
|||||||
@@ -8,9 +8,17 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <botan/auto_rng.h>
|
|
||||||
#include <botan/exceptn.h>
|
#include <botan/exceptn.h>
|
||||||
|
|
||||||
|
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)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent), ui(std::make_unique<Ui::MainWindow>()) {
|
: QMainWindow(parent), ui(std::make_unique<Ui::MainWindow>()) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@@ -45,21 +53,18 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
static Botan::AutoSeeded_RNG rng;
|
static Botan::AutoSeeded_RNG rng;
|
||||||
auto salt = rng.random_array<16>();
|
auto salt = rng.random_array<16>();
|
||||||
|
|
||||||
auto key = Crypto::derive_key_argon2id(password.toStdString(), salt);
|
auto key = Crypto::derive_key(password.toStdString(), salt);
|
||||||
auto check_nonce = rng.random_array<24>();
|
|
||||||
|
|
||||||
const std::string content = "LETSGO";
|
const std::string_view content = "LETSGO";
|
||||||
Botan::secure_vector<u8> content_sv(content.begin(), content.end());
|
Botan::secure_vector<u8> content_sv(content.begin(), content.end());
|
||||||
auto check_ciphertext =
|
auto check_ciphertext = Crypto::encrypt(key, content_sv);
|
||||||
Crypto::encrypt_xchacha20_poly1305(content_sv, key, check_nonce);
|
|
||||||
|
|
||||||
std::ofstream create(path.toStdString(), std::ios::binary);
|
std::ofstream create(path.toStdString(), std::ios::binary);
|
||||||
create.write("DULL", 4);
|
create.write("DULL", 4);
|
||||||
create.write(to_char_ptr(&VERSION), sizeof(VERSION));
|
create.write(to_char_ptr(&VERSION), sizeof(VERSION));
|
||||||
create.write(to_char_ptr(salt.data()), 16);
|
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()), 46);
|
||||||
create.write(to_char_ptr(check_ciphertext.data()), 22);
|
|
||||||
create.close();
|
create.close();
|
||||||
|
|
||||||
m_vault =
|
m_vault =
|
||||||
@@ -118,7 +123,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
std::string content((std::istreambuf_iterator<char>(file)),
|
std::string content((std::istreambuf_iterator<char>(file)),
|
||||||
std::istreambuf_iterator<char>());
|
std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
m_vault->create_file(path_to_filename(path.toStdString()), content);
|
m_vault->create_file(basename(path.toStdString()), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
reload_fs_tree();
|
reload_fs_tree();
|
||||||
@@ -294,8 +299,7 @@ void MainWindow::dropEvent(QDropEvent *event) {
|
|||||||
std::string content((std::istreambuf_iterator<char>(file)),
|
std::string content((std::istreambuf_iterator<char>(file)),
|
||||||
std::istreambuf_iterator<char>());
|
std::istreambuf_iterator<char>());
|
||||||
|
|
||||||
m_vault->create_file(path_to_filename(u.toLocalFile().toStdString()),
|
m_vault->create_file(basename(u.toLocalFile().toStdString()), content);
|
||||||
content);
|
|
||||||
}
|
}
|
||||||
ui->statusbar->showMessage(
|
ui->statusbar->showMessage(
|
||||||
"Added " + QString::number(event->mimeData()->urls().size()) + " files");
|
"Added " + QString::number(event->mimeData()->urls().size()) + " files");
|
||||||
|
|||||||
40
src/vault.cc
40
src/vault.cc
@@ -20,16 +20,13 @@ Vault::Vault(std::string path, const std::string &password)
|
|||||||
std::array<u8, 16> salt{};
|
std::array<u8, 16> salt{};
|
||||||
ASSERT(m_file.read(to_char_ptr(salt.data()), 16));
|
ASSERT(m_file.read(to_char_ptr(salt.data()), 16));
|
||||||
|
|
||||||
m_key = Crypto::derive_key_argon2id(password, salt);
|
m_key = Crypto::derive_key(password, salt);
|
||||||
|
|
||||||
std::array<u8, 24> check_nonce{};
|
|
||||||
ASSERT(m_file.read(to_char_ptr(check_nonce.data()), 24));
|
|
||||||
|
|
||||||
Botan::secure_vector<u8> check_ciphertext;
|
Botan::secure_vector<u8> check_ciphertext;
|
||||||
check_ciphertext.resize(22);
|
check_ciphertext.resize(46);
|
||||||
ASSERT(m_file.read(to_char_ptr(check_ciphertext.data()), 22));
|
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<FileHeader> Vault::read_file_headers() {
|
std::vector<FileHeader> Vault::read_file_headers() {
|
||||||
@@ -70,8 +67,7 @@ std::optional<std::string> Vault::read_file(const std::string &filename) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto plaintext = Crypto::decrypt_xchacha20_poly1305(
|
auto plaintext = Crypto::decrypt(m_key, ciphertext);
|
||||||
ciphertext, m_key, header->content_nonce);
|
|
||||||
return std::string(to_char_ptr(plaintext.data()), plaintext.size());
|
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.clear();
|
||||||
m_file.seekp(0, std::ios::end);
|
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<u8> filename_sv(filename.begin(), filename.end());
|
Botan::secure_vector<u8> filename_sv(filename.begin(), filename.end());
|
||||||
auto filename_ciphertext =
|
auto filename_ciphertext = Crypto::encrypt(m_key, filename_sv);
|
||||||
Crypto::encrypt_xchacha20_poly1305(filename_sv, m_key, name_nonce);
|
|
||||||
u64 filename_ciphertext_size = filename_ciphertext.size();
|
u64 filename_ciphertext_size = filename_ciphertext.size();
|
||||||
|
|
||||||
Botan::secure_vector<u8> content_sv(content.begin(), content.end());
|
Botan::secure_vector<u8> content_sv(content.begin(), content.end());
|
||||||
auto ciphertext =
|
auto ciphertext = Crypto::encrypt(m_key, content_sv);
|
||||||
Crypto::encrypt_xchacha20_poly1305(content_sv, m_key, content_nonce);
|
|
||||||
u64 ciphertext_size = ciphertext.size();
|
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_size), sizeof(u64)));
|
||||||
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()),
|
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()),
|
||||||
static_cast<i64>(filename_ciphertext_size)));
|
static_cast<i64>(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_size), sizeof(u64)));
|
||||||
ASSERT(m_file.write(to_char_ptr(ciphertext.data()),
|
ASSERT(m_file.write(to_char_ptr(ciphertext.data()),
|
||||||
static_cast<i64>(ciphertext_size)));
|
static_cast<i64>(ciphertext_size)));
|
||||||
@@ -129,7 +117,7 @@ void Vault::delete_file(const std::string &filename) {
|
|||||||
|
|
||||||
if (header->name == filename) {
|
if (header->name == filename) {
|
||||||
entry_start = current_pos;
|
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;
|
sizeof(u64) + header->content_ciphertext_size;
|
||||||
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
||||||
std::ios::cur);
|
std::ios::cur);
|
||||||
@@ -181,10 +169,6 @@ void Vault::update_file(const std::string &filename,
|
|||||||
std::optional<FileHeader> Vault::read_file_header() {
|
std::optional<FileHeader> Vault::read_file_header() {
|
||||||
FileHeader 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))) {
|
if (!m_file.read(to_char_ptr(&header.name_ciphertext_size), sizeof(u64))) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -198,14 +182,8 @@ std::optional<FileHeader> Vault::read_file_header() {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto name = Crypto::decrypt_xchacha20_poly1305(name_ciphertext, m_key,
|
auto name = Crypto::decrypt(m_key, name_ciphertext);
|
||||||
header.name_nonce);
|
|
||||||
header.name = std::string(name.begin(), name.end());
|
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))) {
|
if (!m_file.read(to_char_ptr(&header.content_ciphertext_size), sizeof(u64))) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include <array>
|
|
||||||
#include <botan/secmem.h>
|
#include <botan/secmem.h>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -11,10 +10,8 @@ constexpr u64 AFTER_HEADER_OFFSET = 68;
|
|||||||
|
|
||||||
// !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file
|
// !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file
|
||||||
struct FileHeader {
|
struct FileHeader {
|
||||||
std::array<u8, 24> name_nonce;
|
|
||||||
u64 name_ciphertext_size;
|
u64 name_ciphertext_size;
|
||||||
std::string name;
|
std::string name;
|
||||||
std::array<u8, 24> content_nonce;
|
|
||||||
u64 content_ciphertext_size;
|
u64 content_ciphertext_size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user