diff --git a/qml/Config.qml b/qml/Config.qml index a7eb4897..b4aa0204 100644 --- a/qml/Config.qml +++ b/qml/Config.qml @@ -26,6 +26,7 @@ QtObject { property string screenName: "" property string password: "" property string cipherText + property string aeskey // Client data property int roomCapacity: 0 diff --git a/qml/Logic.js b/qml/Logic.js index 3c3992e6..37289a07 100644 --- a/qml/Logic.js +++ b/qml/Logic.js @@ -19,17 +19,23 @@ let sheduled_download = ""; callbacks["NetworkDelayTest"] = function(jsonData) { // jsonData: RSA pub key - let cipherText + let cipherText; + let aeskey; if (config.savedPassword[config.serverAddr] !== undefined && config.savedPassword[config.serverAddr].shorten_password === config.password) { cipherText = config.savedPassword[config.serverAddr].password; + aeskey = config.savedPassword[config.serverAddr].key; + config.aeskey = aeskey; + Backend.setAESKey(aeskey); if (Debugging) console.log("use remembered password", config.password); } else { cipherText = Backend.pubEncrypt(jsonData, config.password); + config.aeskey = Backend.getAESKey(); } config.cipherText = cipherText; Backend.replyDelayTest(config.screenName, cipherText); + Backend.installAESKey(); } callbacks["ErrorMsg"] = function(jsonData) { @@ -69,6 +75,7 @@ callbacks["EnterLobby"] = function(jsonData) { config.savedPassword[config.serverAddr] = { username: config.screenName, password: config.cipherText, + key: config.aeskey, shorten_password: config.cipherText.slice(0, 8) } mainStack.push(lobby); diff --git a/src/client/client.cpp b/src/client/client.cpp index 38f5cd1f..30658998 100644 --- a/src/client/client.cpp +++ b/src/client/client.cpp @@ -68,3 +68,5 @@ void Client::removePlayer(int id) { void Client::clearPlayers() { players.clear(); } lua_State *Client::getLuaState() { return L; } + +void Client::installAESKey(const QByteArray &key) { router->installAESKey(key); } diff --git a/src/client/client.h b/src/client/client.h index c8504252..be906eb9 100644 --- a/src/client/client.h +++ b/src/client/client.h @@ -29,6 +29,7 @@ public: Q_INVOKABLE void clearPlayers(); lua_State *getLuaState(); + void installAESKey(const QByteArray &key); signals: void error_message(const QString &msg); diff --git a/src/network/client_socket.cpp b/src/network/client_socket.cpp index ed242155..6b14a6c9 100644 --- a/src/network/client_socket.cpp +++ b/src/network/client_socket.cpp @@ -1,8 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "client_socket.h" +#include +#include -ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { init(); } +ClientSocket::ClientSocket() : socket(new QTcpSocket(this)) { + aes_ready = false; + init(); +} ClientSocket::ClientSocket(QTcpSocket *socket) { socket->setParent(this); @@ -28,6 +33,7 @@ void ClientSocket::connectToHost(const QString &address, ushort port) { void ClientSocket::getMessage() { while (socket->canReadLine()) { auto msg = socket->readLine(); + msg = aesDecrypt(msg); if (msg.startsWith("Compressed")) { msg = msg.sliced(10); msg = qUncompress(QByteArray::fromBase64(msg)); @@ -36,18 +42,22 @@ void ClientSocket::getMessage() { } } -void ClientSocket::disconnectFromHost() { socket->disconnectFromHost(); } +void ClientSocket::disconnectFromHost() { + aes_ready = false; + socket->disconnectFromHost(); +} void ClientSocket::send(const QByteArray &msg) { + QByteArray _msg; if (msg.length() >= 1024) { auto comp = qCompress(msg); - auto _msg = "Compressed" + comp.toBase64() + "\n"; - socket->write(_msg); - socket->flush(); + _msg = "Compressed" + comp.toBase64(); + _msg = aesEncrypt(_msg) + "\n"; + } else { + _msg = aesEncrypt(msg) + "\n"; } - socket->write(msg); - if (!msg.endsWith("\n")) - socket->write("\n"); + + socket->write(_msg); socket->flush(); } @@ -96,3 +106,62 @@ void ClientSocket::raiseError(QAbstractSocket::SocketError socket_error) { .arg(socket_error) .arg(reason)); } + +void ClientSocket::installAESKey(const QByteArray &key) { + if (key.length() != 32) { + return; + } + auto key_ = QByteArray::fromHex(key); + + AES_set_encrypt_key((const unsigned char *)key_.data(), 16 * 8, &aes_key); + aes_ready = true; +} + +QByteArray ClientSocket::aesEncrypt(const QByteArray &in) { + if (!aes_ready) { + return in; + } + int num = 0; + QByteArray out; + out.resize(in.length()); + + auto rand_generator = QRandomGenerator::securelySeeded(); + + QByteArray iv; + iv.append(QByteArray::number(rand_generator.generate64(), 16)); + iv.append(QByteArray::number(rand_generator.generate64(), 16)); + if (iv.length() < 32) { + iv.append(QByteArray("0").repeated(32 - iv.length())); + } + auto iv_raw = QByteArray::fromHex(iv); + + unsigned char tempIv[16]; + strncpy((char *)tempIv, iv_raw.constData(), 16); + AES_cfb128_encrypt((const unsigned char *)in.constData(), + (unsigned char *)out.data(), in.length(), &aes_key, tempIv, + &num, AES_ENCRYPT); + + return iv + out.toBase64(); +} +QByteArray ClientSocket::aesDecrypt(const QByteArray &in) { + if (!aes_ready) { + return in; + } + + int num = 0; + auto iv = in.first(32); + auto aes_iv = QByteArray::fromHex(iv); + + auto real_in = in; + real_in.remove(0, 32); + auto inenc = QByteArray::fromBase64(real_in); + QByteArray out; + out.resize(inenc.length()); + unsigned char tempIv[16]; + strncpy((char *)tempIv, aes_iv.constData(), 16); + AES_cfb128_encrypt((const unsigned char *)inenc.constData(), + (unsigned char *)out.data(), inenc.length(), &aes_key, + tempIv, &num, AES_DECRYPT); + + return out; +} diff --git a/src/network/client_socket.h b/src/network/client_socket.h index d8e3ae50..12dfc202 100644 --- a/src/network/client_socket.h +++ b/src/network/client_socket.h @@ -3,6 +3,8 @@ #ifndef _CLIENT_SOCKET_H #define _CLIENT_SOCKET_H +#include + class ClientSocket : public QObject { Q_OBJECT @@ -13,6 +15,7 @@ public: void connectToHost(const QString &address = "127.0.0.1", ushort port = 9527u); void disconnectFromHost(); + void installAESKey(const QByteArray &key); void send(const QByteArray& msg); bool isConnected() const; QString peerName() const; @@ -30,6 +33,10 @@ private slots: void raiseError(QAbstractSocket::SocketError error); private: + QByteArray aesEncrypt(const QByteArray &in); + QByteArray aesDecrypt(const QByteArray &out); + AES_KEY aes_key; + bool aes_ready; QTcpSocket *socket; void init(); }; diff --git a/src/network/router.cpp b/src/network/router.cpp index ffdc330b..4909edd4 100644 --- a/src/network/router.cpp +++ b/src/network/router.cpp @@ -43,6 +43,10 @@ void Router::setSocket(ClientSocket *socket) { } } +void Router::installAESKey(const QByteArray &key) { + socket->installAESKey(key); +} + #ifndef FK_CLIENT_ONLY void Router::setReplyReadySemaphore(QSemaphore *semaphore) { extraReplyReadySemaphore = semaphore; diff --git a/src/network/router.h b/src/network/router.h index 7df10c44..6a1cc37b 100644 --- a/src/network/router.h +++ b/src/network/router.h @@ -30,6 +30,7 @@ public: ClientSocket *getSocket() const; void setSocket(ClientSocket *socket); + void installAESKey(const QByteArray &key); #ifndef FK_CLIENT_ONLY void setReplyReadySemaphore(QSemaphore *semaphore); diff --git a/src/server/server.cpp b/src/server/server.cpp index 51cd7817..f28ff971 100644 --- a/src/server/server.cpp +++ b/src/server/server.cpp @@ -227,6 +227,15 @@ void Server::handleNameAndPassword(ClientSocket *client, const QString &name, buf, rsa, RSA_PKCS1_PADDING); auto decrypted_pw = QByteArray::fromRawData((const char *)buf, strlen((const char *)buf)); + + if (decrypted_pw.length() > 32) { + auto aes_bytes = decrypted_pw.first(32); + client->installAESKey(aes_bytes); + decrypted_pw.remove(0, 32); + } else { + decrypted_pw = "\xFF"; + } + bool passed = false; QString error_msg; QJsonArray result; diff --git a/src/ui/qmlbackend.cpp b/src/ui/qmlbackend.cpp index 7e5c6319..bbe7ad87 100644 --- a/src/ui/qmlbackend.cpp +++ b/src/ui/qmlbackend.cpp @@ -1,11 +1,14 @@ // SPDX-License-Identifier: GPL-3.0-or-later #include "qmlbackend.h" -#include -#include + #include #include #include + +#include +#include +#include #ifndef Q_OS_WASM #include "server.h" #endif @@ -179,14 +182,29 @@ QString QmlBackend::callLuaFunction(const QString &func_name, } QString QmlBackend::pubEncrypt(const QString &key, const QString &data) { + // 在用公钥加密口令时,也随机生成AES密钥/IV,并随着口令一起加密 + // AES密钥和IV都是固定16字节的,所以可以放在开头 auto key_bytes = key.toLatin1(); BIO *keyio = BIO_new_mem_buf(key_bytes.constData(), -1); PEM_read_bio_RSAPublicKey(keyio, &rsa, NULL, NULL); BIO_free_all(keyio); auto data_bytes = data.toUtf8(); + auto rand_generator = QRandomGenerator::securelySeeded(); + QByteArray aes_key_; + for (int i = 0; i < 2; i++) { + aes_key_.append(QByteArray::number(rand_generator.generate64(), 16)); + } + if (aes_key_.length() < 32) { + aes_key_.append(QByteArray("0").repeated(32 - aes_key_.length())); + } + + aes_key = aes_key_; + + data_bytes.prepend(aes_key_); + unsigned char buf[RSA_size(rsa)]; - RSA_public_encrypt(data.length(), + RSA_public_encrypt(data.length() + 32, (const unsigned char *)data_bytes.constData(), buf, rsa, RSA_PKCS1_PADDING); return QByteArray::fromRawData((const char *)buf, RSA_size(rsa)).toBase64(); @@ -258,3 +276,11 @@ void QmlBackend::playSound(const QString &name, int index) { void QmlBackend::copyToClipboard(const QString &s) { QGuiApplication::clipboard()->setText(s); } + +void QmlBackend::setAESKey(const QString &key) { aes_key = key; } + +QString QmlBackend::getAESKey() const { return aes_key; } + +void QmlBackend::installAESKey() { + ClientInstance->installAESKey(aes_key.toLatin1()); +} diff --git a/src/ui/qmlbackend.h b/src/ui/qmlbackend.h index 62f526ba..1f9c0a46 100644 --- a/src/ui/qmlbackend.h +++ b/src/ui/qmlbackend.h @@ -3,6 +3,7 @@ #ifndef _QMLBACKEND_H #define _QMLBACKEND_H +#include class QmlBackend : public QObject { Q_OBJECT public: @@ -42,12 +43,17 @@ public: Q_INVOKABLE void copyToClipboard(const QString &s); + Q_INVOKABLE void setAESKey(const QString &key); + Q_INVOKABLE QString getAESKey() const; + Q_INVOKABLE void installAESKey(); + signals: void notifyUI(const QString &command, const QString &jsonData); private: QQmlApplicationEngine *engine; RSA *rsa; + QString aes_key; void pushLuaValue(lua_State *L, QVariant v); };