From 669d89c1088394cb14260078f8047e0241464a5d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 5 Feb 2026 15:09:16 -0800 Subject: [PATCH] Store password hash instead of plaintext for login persistence Save SHA1(UPPER(user):UPPER(pass)) hash to login.cfg instead of the plaintext password. On subsequent logins, use the stored hash directly with a new authenticateWithHash() method that bypasses password hashing. The password field shows a placeholder when using a stored hash. --- include/auth/auth_handler.hpp | 1 + include/auth/srp.hpp | 4 +++ include/ui/auth_screen.hpp | 5 +++ src/auth/auth_handler.cpp | 26 ++++++++++++++++ src/auth/srp.cpp | 18 +++++++++-- src/ui/auth_screen.cpp | 57 +++++++++++++++++++++++++++++++++-- 6 files changed, 107 insertions(+), 4 deletions(-) diff --git a/include/auth/auth_handler.hpp b/include/auth/auth_handler.hpp index ab457291..2317488a 100644 --- a/include/auth/auth_handler.hpp +++ b/include/auth/auth_handler.hpp @@ -43,6 +43,7 @@ public: // Authentication void authenticate(const std::string& username, const std::string& password); + void authenticateWithHash(const std::string& username, const std::vector& authHash); // Realm list void requestRealmList(); diff --git a/include/auth/srp.hpp b/include/auth/srp.hpp index ce4fefd0..00fd2c1d 100644 --- a/include/auth/srp.hpp +++ b/include/auth/srp.hpp @@ -18,6 +18,9 @@ public: // Initialize with username and password void initialize(const std::string& username, const std::string& password); + // Initialize with username and pre-computed auth hash (SHA1(UPPER(user):UPPER(pass))) + void initializeWithHash(const std::string& username, const std::vector& authHash); + // Feed server challenge data (B, g, N, salt) void feed(const std::vector& B, const std::vector& g, @@ -67,6 +70,7 @@ private: // Stored credentials std::string stored_username; std::string stored_password; + std::vector stored_auth_hash; // Pre-computed SHA1(UPPER(user):UPPER(pass)) bool initialized = false; }; diff --git a/include/ui/auth_screen.hpp b/include/ui/auth_screen.hpp index ef11ed94..1d94c787 100644 --- a/include/ui/auth_screen.hpp +++ b/include/ui/auth_screen.hpp @@ -57,6 +57,11 @@ private: float authTimer = 0.0f; // Timeout tracker static constexpr float AUTH_TIMEOUT = 10.0f; + // Saved password hash (SHA1(UPPER(user):UPPER(pass)) as hex) + std::string savedPasswordHash; + bool usingStoredHash = false; + static constexpr const char* PASSWORD_PLACEHOLDER = "\x01\x01\x01\x01\x01\x01\x01\x01"; + // Callbacks std::function onSuccess; std::function onSinglePlayer; diff --git a/src/auth/auth_handler.cpp b/src/auth/auth_handler.cpp index a8703119..3867454c 100644 --- a/src/auth/auth_handler.cpp +++ b/src/auth/auth_handler.cpp @@ -93,6 +93,32 @@ void AuthHandler::authenticate(const std::string& user, const std::string& pass) sendLogonChallenge(); } +void AuthHandler::authenticateWithHash(const std::string& user, const std::vector& authHash) { + if (!isConnected()) { + LOG_ERROR("Cannot authenticate: not connected to auth server"); + fail("Not connected"); + return; + } + + if (state != AuthState::CONNECTED) { + LOG_ERROR("Cannot authenticate: invalid state"); + fail("Invalid state"); + return; + } + + LOG_INFO("Starting authentication for user (with hash): ", user); + + username = user; + password.clear(); + + // Initialize SRP with pre-computed hash + srp = std::make_unique(); + srp->initializeWithHash(username, authHash); + + // Send LOGON_CHALLENGE + sendLogonChallenge(); +} + void AuthHandler::sendLogonChallenge() { LOG_DEBUG("Sending LOGON_CHALLENGE"); diff --git a/src/auth/srp.cpp b/src/auth/srp.cpp index defb7a1f..c427e0d1 100644 --- a/src/auth/srp.cpp +++ b/src/auth/srp.cpp @@ -19,11 +19,23 @@ void SRP::initialize(const std::string& username, const std::string& password) { // Store credentials for later use stored_username = username; stored_password = password; + stored_auth_hash.clear(); initialized = true; LOG_DEBUG("SRP initialized"); } +void SRP::initializeWithHash(const std::string& username, const std::vector& authHash) { + LOG_DEBUG("Initializing SRP with username and pre-computed hash: ", username); + + stored_username = username; + stored_password.clear(); + stored_auth_hash = authHash; + + initialized = true; + LOG_DEBUG("SRP initialized with hash"); +} + void SRP::feed(const std::vector& B_bytes, const std::vector& g_bytes, const std::vector& N_bytes, @@ -50,8 +62,10 @@ void SRP::feed(const std::vector& B_bytes, // Now compute everything in sequence - // 1. Compute auth hash: H(I:P) - std::vector auth_hash = computeAuthHash(stored_username, stored_password); + // 1. Compute auth hash: H(I:P) — use stored hash if available + std::vector auth_hash = stored_auth_hash.empty() + ? computeAuthHash(stored_username, stored_password) + : stored_auth_hash; // 2. Compute x = H(s | H(I:P)) std::vector x_input; diff --git a/src/ui/auth_screen.cpp b/src/ui/auth_screen.cpp index f3f804b4..b619fd92 100644 --- a/src/ui/auth_screen.cpp +++ b/src/ui/auth_screen.cpp @@ -1,13 +1,33 @@ #include "ui/auth_screen.hpp" +#include "auth/crypto.hpp" #include "core/logger.hpp" #include #include #include #include +#include #include +#include +#include namespace wowee { namespace ui { +static std::string hexEncode(const std::vector& data) { + std::ostringstream ss; + for (uint8_t b : data) + ss << std::hex << std::setfill('0') << std::setw(2) << (int)b; + return ss.str(); +} + +static std::vector hexDecode(const std::string& hex) { + std::vector bytes; + for (size_t i = 0; i + 1 < hex.size(); i += 2) { + uint8_t b = static_cast(std::stoul(hex.substr(i, 2), nullptr, 16)); + bytes.push_back(b); + } + return bytes; +} + AuthScreen::AuthScreen() { } @@ -79,6 +99,18 @@ void AuthScreen::render(auth::AuthHandler& authHandler) { setStatus("Authentication successful!", false); authenticating = false; + // Compute and save password hash if user typed a fresh password + if (!usingStoredHash) { + std::string upperUser = username; + std::string upperPass = password; + std::transform(upperUser.begin(), upperUser.end(), upperUser.begin(), ::toupper); + std::transform(upperPass.begin(), upperPass.end(), upperPass.begin(), ::toupper); + std::string combined = upperUser + ":" + upperPass; + auto hash = auth::Crypto::sha1(combined); + savedPasswordHash = hexEncode(hash); + } + saveLoginInfo(); + // Call success callback if (onSuccess) { onSuccess(); @@ -139,7 +171,10 @@ void AuthScreen::attemptAuth(auth::AuthHandler& authHandler) { return; } - if (strlen(password) == 0) { + // Check if using stored hash (password field contains placeholder) + bool useHash = usingStoredHash && std::strcmp(password, PASSWORD_PLACEHOLDER) == 0; + + if (!useHash && strlen(password) == 0) { setStatus("Password cannot be empty", true); return; } @@ -169,7 +204,13 @@ void AuthScreen::attemptAuth(auth::AuthHandler& authHandler) { saveLoginInfo(); // Send authentication credentials - authHandler.authenticate(username, password); + if (useHash) { + auto hashBytes = hexDecode(savedPasswordHash); + authHandler.authenticateWithHash(username, hashBytes); + } else { + usingStoredHash = false; + authHandler.authenticate(username, password); + } } else { std::stringstream errSs; errSs << "Failed to connect to " << hostname << ":" << port @@ -210,6 +251,9 @@ void AuthScreen::saveLoginInfo() { out << "hostname=" << hostname << "\n"; out << "port=" << port << "\n"; out << "username=" << username << "\n"; + if (!savedPasswordHash.empty()) { + out << "password_hash=" << savedPasswordHash << "\n"; + } LOG_INFO("Login info saved to ", path); } @@ -234,9 +278,18 @@ void AuthScreen::loadLoginInfo() { } else if (key == "username" && !val.empty()) { strncpy(username, val.c_str(), sizeof(username) - 1); username[sizeof(username) - 1] = '\0'; + } else if (key == "password_hash" && !val.empty()) { + savedPasswordHash = val; } } + // If we have a saved hash, fill password with placeholder + if (!savedPasswordHash.empty()) { + strncpy(password, PASSWORD_PLACEHOLDER, sizeof(password) - 1); + password[sizeof(password) - 1] = '\0'; + usingStoredHash = true; + } + LOG_INFO("Login info loaded from ", path); }