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
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
|
|
|