You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

221 lines
7.7 KiB

/*
* crypto.cpp — Криптографические утилиты (Ed25519 через OpenSSL)
*/
#include "crypto.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <QFile>
#include <QCoreApplication>
#include <thread>
#include <cstring>
namespace Crypto {
/* ============================================================================
* Помощники
* ============================================================================ */
namespace {
void postToMain(std::function<void()> 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<const uint8_t*>(buf.constData()),
static_cast<size_t>(n));
}
file.close();
QByteArray hash;
hash.resize(static_cast<int>(kSha512Size));
unsigned int hash_len = static_cast<unsigned int>(kSha512Size);
if (EVP_DigestFinal_ex(md_ctx, reinterpret_cast<uint8_t*>(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<int>(kKeySize)) return QByteArray();
uint8_t hash[kSha512Size];
SHA512(reinterpret_cast<const uint8_t*>(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<int>(kKeySize));
size_t priv_len = kKeySize;
if (EVP_PKEY_get_raw_private_key(pkey, reinterpret_cast<uint8_t*>(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<int>(kKeySize)) return QByteArray();
uint8_t hash[kSha512Size];
SHA512(reinterpret_cast<const uint8_t*>(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<int>(kKeySize));
size_t pub_len = kKeySize;
if (EVP_PKEY_get_raw_public_key(pkey, reinterpret_cast<uint8_t*>(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<int>(kKeySize)) return QByteArray();
EVP_PKEY* pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL,
reinterpret_cast<const uint8_t*>(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<int>(kSignSize));
if (EVP_DigestSign(md_ctx, reinterpret_cast<uint8_t*>(result.data()), &sig_len,
reinterpret_cast<const uint8_t*>(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<int>(sig_len));
return result;
}
bool verify(const QByteArray& ed25519Pubkey, const QByteArray& data, const QByteArray& sig) {
if (ed25519Pubkey.size() != static_cast<int>(kKeySize)
|| sig.size() > static_cast<int>(kSignSize)) return false;
EVP_PKEY* pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL,
reinterpret_cast<const uint8_t*>(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<const uint8_t*>(sig.constData()), sig.size(),
reinterpret_cast<const uint8_t*>(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