#include "sync/CryptoEnvelope.h" #include #include #include namespace umt { namespace CryptoEnvelope { namespace { const char MAGIC[4] = { 'U', 'M', 'T', 'S' }; const quint8 VERSION = 1; constexpr int SALT_BYTES = 16; // crypto_pwhash_SALTBYTES constexpr int NONCE_BYTES = 24; // crypto_aead_xchacha20poly1305_ietf_NPUBBYTES constexpr int KEY_BYTES = 32; // crypto_aead_xchacha20poly1305_ietf_KEYBYTES constexpr int ABYTES = 16; // crypto_aead_xchacha20poly1305_ietf_ABYTES // libsodium INTERACTIVE limits — a balance of security and CPU cost. Must match // the Android client so snapshots are interchangeable. constexpr quint64 OPSLIMIT = 2ULL; // crypto_pwhash_OPSLIMIT_INTERACTIVE constexpr quint64 MEMLIMIT = 67108864ULL; // crypto_pwhash_MEMLIMIT_INTERACTIVE (64 MiB) constexpr int HEADER_BYTES = 4 + 1 + 4 + 4 + SALT_BYTES + NONCE_BYTES; void setError(QString *error, const QString &msg) { if (error) *error = msg; } bool ensureInit(QString *error) { static const int rc = sodium_init(); if (rc < 0) { setError(error, QStringLiteral("Krypto-Bibliothek konnte nicht initialisiert werden")); return false; } return true; } QByteArray deriveKey(const QString &passphrase, const QByteArray &salt, quint64 ops, quint64 mem, QString *error) { const QByteArray pw = passphrase.toUtf8(); QByteArray key(KEY_BYTES, Qt::Uninitialized); const int rc = crypto_pwhash( reinterpret_cast(key.data()), KEY_BYTES, pw.constData(), static_cast(pw.size()), reinterpret_cast(salt.constData()), ops, static_cast(mem), crypto_pwhash_ALG_ARGON2ID13); if (rc != 0) { setError(error, QStringLiteral("Schlüsselableitung fehlgeschlagen")); return {}; } return key; } } // namespace QByteArray encrypt(const QByteArray &plaintext, const QString &passphrase, QString *error) { if (!ensureInit(error)) return {}; QByteArray salt(SALT_BYTES, Qt::Uninitialized); randombytes_buf(salt.data(), SALT_BYTES); QByteArray nonce(NONCE_BYTES, Qt::Uninitialized); randombytes_buf(nonce.data(), NONCE_BYTES); const QByteArray key = deriveKey(passphrase, salt, OPSLIMIT, MEMLIMIT, error); if (key.isEmpty()) return {}; QByteArray cipher(plaintext.size() + ABYTES, Qt::Uninitialized); unsigned long long cipherLen = 0; const int rc = crypto_aead_xchacha20poly1305_ietf_encrypt( reinterpret_cast(cipher.data()), &cipherLen, reinterpret_cast(plaintext.constData()), static_cast(plaintext.size()), nullptr, 0, // no associated data nullptr, // nsec (unused) reinterpret_cast(nonce.constData()), reinterpret_cast(key.constData())); if (rc != 0) { setError(error, QStringLiteral("Verschlüsselung fehlgeschlagen")); return {}; } cipher.resize(static_cast(cipherLen)); QByteArray out; out.reserve(HEADER_BYTES + cipher.size()); out.append(MAGIC, 4); out.append(static_cast(VERSION)); quint32 opsBE = qToBigEndian(static_cast(OPSLIMIT)); quint32 memBE = qToBigEndian(static_cast(MEMLIMIT)); out.append(reinterpret_cast(&opsBE), 4); out.append(reinterpret_cast(&memBE), 4); out.append(salt); out.append(nonce); out.append(cipher); return out; } QByteArray decrypt(const QByteArray &envelope, const QString &passphrase, QString *error) { if (!ensureInit(error)) return {}; if (envelope.size() < HEADER_BYTES) { setError(error, QStringLiteral("Daten unvollständig")); return {}; } const char *p = envelope.constData(); if (qstrncmp(p, MAGIC, 4) != 0) { setError(error, QStringLiteral("Unbekanntes Format")); return {}; } int off = 4; const quint8 version = static_cast(p[off]); off += 1; if (version != VERSION) { setError(error, QStringLiteral("Nicht unterstützte Version: %1").arg(version)); return {}; } quint32 opsBE, memBE; memcpy(&opsBE, p + off, 4); off += 4; memcpy(&memBE, p + off, 4); off += 4; const quint64 ops = qFromBigEndian(opsBE); const quint64 mem = qFromBigEndian(memBE); const QByteArray salt = envelope.mid(off, SALT_BYTES); off += SALT_BYTES; const QByteArray nonce = envelope.mid(off, NONCE_BYTES); off += NONCE_BYTES; const QByteArray cipher = envelope.mid(off); if (cipher.size() < ABYTES) { setError(error, QStringLiteral("Daten unvollständig")); return {}; } const QByteArray key = deriveKey(passphrase, salt, ops, mem, error); if (key.isEmpty()) return {}; QByteArray plain(cipher.size() - ABYTES, Qt::Uninitialized); unsigned long long plainLen = 0; const int rc = crypto_aead_xchacha20poly1305_ietf_decrypt( reinterpret_cast(plain.data()), &plainLen, nullptr, // nsec (unused) reinterpret_cast(cipher.constData()), static_cast(cipher.size()), nullptr, 0, // no associated data reinterpret_cast(nonce.constData()), reinterpret_cast(key.constData())); if (rc != 0) { setError(error, QStringLiteral("Entschlüsselung fehlgeschlagen (falsche Passphrase?)")); return {}; } plain.resize(static_cast(plainLen)); return plain; } } // namespace CryptoEnvelope } // namespace umt