/* * crypto.cpp — Криптографические утилиты (Ed25519 через OpenSSL) */ #include "crypto.h" #include #include #include #include #include #include namespace Crypto { /* ============================================================================ * Помощники * ============================================================================ */ namespace { void postToMain(std::function fn) { auto* app = QCoreApplication::instance(); if (app) { QMetaObject::invokeMethod(app, std::move(fn), Qt::QueuedConnection); } else { fn(); /* нет event loop — синхронно */ } } QByteArray sha512File(const QString& path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) return {}; EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); if (!md_ctx || EVP_DigestInit_ex(md_ctx, EVP_sha512(), NULL) != 1) { if (md_ctx) EVP_MD_CTX_free(md_ctx); return {}; } QByteArray buf(65536, 0); while (true) { qint64 n = file.read(buf.data(), buf.size()); if (n <= 0) break; EVP_DigestUpdate(md_ctx, reinterpret_cast(buf.constData()), static_cast(n)); } file.close(); QByteArray hash; hash.resize(static_cast(kSha512Size)); unsigned int hash_len = static_cast(kSha512Size); if (EVP_DigestFinal_ex(md_ctx, reinterpret_cast(hash.data()), &hash_len) != 1) hash.clear(); EVP_MD_CTX_free(md_ctx); return hash; } } // anonymous /* ============================================================================ * Синхронные: derive ключей * ============================================================================ */ QByteArray deriveEd25519Privkey(const QByteArray& x25519Privkey) { if (x25519Privkey.size() != static_cast(kKeySize)) return QByteArray(); uint8_t hash[kSha512Size]; SHA512(reinterpret_cast(x25519Privkey.constData()), kKeySize, hash); EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, hash, kKeySize); if (!pkey) return QByteArray(); QByteArray result; result.resize(static_cast(kKeySize)); size_t priv_len = kKeySize; if (EVP_PKEY_get_raw_private_key(pkey, reinterpret_cast(result.data()), &priv_len) <= 0 || priv_len != kKeySize) { EVP_PKEY_free(pkey); return QByteArray(); } EVP_PKEY_free(pkey); return result; } QByteArray deriveEd25519Pubkey(const QByteArray& x25519Privkey) { if (x25519Privkey.size() != static_cast(kKeySize)) return QByteArray(); uint8_t hash[kSha512Size]; SHA512(reinterpret_cast(x25519Privkey.constData()), kKeySize, hash); EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, hash, kKeySize); if (!pkey) return QByteArray(); QByteArray result; result.resize(static_cast(kKeySize)); size_t pub_len = kKeySize; if (EVP_PKEY_get_raw_public_key(pkey, reinterpret_cast(result.data()), &pub_len) <= 0 || pub_len != kKeySize) { EVP_PKEY_free(pkey); return QByteArray(); } EVP_PKEY_free(pkey); return result; } /* ============================================================================ * Синхронные: подпись / проверка (малые блоки) * ============================================================================ */ QByteArray sign(const QByteArray& ed25519Privkey, const QByteArray& data) { if (ed25519Privkey.size() != static_cast(kKeySize)) return QByteArray(); EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, reinterpret_cast(ed25519Privkey.constData()), kKeySize); if (!pkey) return QByteArray(); EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); if (!md_ctx || EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, pkey) != 1) { if (md_ctx) EVP_MD_CTX_free(md_ctx); EVP_PKEY_free(pkey); return QByteArray(); } size_t sig_len = kSignSize; QByteArray result; result.resize(static_cast(kSignSize)); if (EVP_DigestSign(md_ctx, reinterpret_cast(result.data()), &sig_len, reinterpret_cast(data.constData()), data.size()) != 1) { EVP_MD_CTX_free(md_ctx); EVP_PKEY_free(pkey); return QByteArray(); } EVP_MD_CTX_free(md_ctx); EVP_PKEY_free(pkey); result.resize(static_cast(sig_len)); return result; } bool verify(const QByteArray& ed25519Pubkey, const QByteArray& data, const QByteArray& sig) { if (ed25519Pubkey.size() != static_cast(kKeySize) || sig.size() > static_cast(kSignSize)) return false; EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, reinterpret_cast(ed25519Pubkey.constData()), kKeySize); if (!pkey) return false; EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); if (!md_ctx || EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, pkey) != 1) { if (md_ctx) EVP_MD_CTX_free(md_ctx); EVP_PKEY_free(pkey); return false; } int ret = EVP_DigestVerify(md_ctx, reinterpret_cast(sig.constData()), sig.size(), reinterpret_cast(data.constData()), data.size()); EVP_MD_CTX_free(md_ctx); EVP_PKEY_free(pkey); return ret == 1; } /* ============================================================================ * Асинхронные: файлы * ============================================================================ */ void signFile(const QString& path, const QByteArray& ed25519Privkey, SignCallback cb) { std::thread([=]() { SignResult r; QByteArray hash = sha512File(path); if (hash.isEmpty()) { r.ok = false; r.error = QStringLiteral("failed to read/hash file"); } else { r.signature = sign(ed25519Privkey, hash); r.ok = !r.signature.isEmpty(); if (!r.ok) r.error = QStringLiteral("sign failed"); } postToMain([cb, r]() { cb(r); }); }).detach(); } void verifyFile(const QString& path, const QByteArray& ed25519Pubkey, const QByteArray& signature, SignCallback cb) { std::thread([=]() { SignResult r; QByteArray hash = sha512File(path); if (hash.isEmpty()) { r.ok = false; r.error = QStringLiteral("failed to read/hash file"); } else { r.ok = verify(ed25519Pubkey, hash, signature); if (!r.ok) r.error = QStringLiteral("signature mismatch"); } postToMain([cb, r]() { cb(r); }); }).detach(); } /* ============================================================================ * Асинхронные: in-memory блоки * ============================================================================ */ void signAsync(const QByteArray& ed25519Privkey, const QByteArray& data, SignCallback cb) { std::thread([=]() { SignResult r; r.signature = sign(ed25519Privkey, data); r.ok = !r.signature.isEmpty(); if (!r.ok) r.error = QStringLiteral("sign failed"); postToMain([cb, r]() { cb(r); }); }).detach(); } void verifyAsync(const QByteArray& ed25519Pubkey, const QByteArray& data, const QByteArray& signature, SignCallback cb) { std::thread([=]() { SignResult r; r.ok = verify(ed25519Pubkey, data, signature); if (!r.ok) r.error = QStringLiteral("signature mismatch"); postToMain([cb, r]() { cb(r); }); }).detach(); } } // namespace Crypto