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.
This commit is contained in:
Kelsi 2026-02-05 15:09:16 -08:00
parent 45466f3d11
commit ca84384402
6 changed files with 107 additions and 4 deletions

View file

@ -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<uint8_t>& 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>();
srp->initializeWithHash(username, authHash);
// Send LOGON_CHALLENGE
sendLogonChallenge();
}
void AuthHandler::sendLogonChallenge() {
LOG_DEBUG("Sending LOGON_CHALLENGE");

View file

@ -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<uint8_t>& 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<uint8_t>& B_bytes,
const std::vector<uint8_t>& g_bytes,
const std::vector<uint8_t>& N_bytes,
@ -50,8 +62,10 @@ void SRP::feed(const std::vector<uint8_t>& B_bytes,
// Now compute everything in sequence
// 1. Compute auth hash: H(I:P)
std::vector<uint8_t> auth_hash = computeAuthHash(stored_username, stored_password);
// 1. Compute auth hash: H(I:P) — use stored hash if available
std::vector<uint8_t> auth_hash = stored_auth_hash.empty()
? computeAuthHash(stored_username, stored_password)
: stored_auth_hash;
// 2. Compute x = H(s | H(I:P))
std::vector<uint8_t> x_input;

View file

@ -1,13 +1,33 @@
#include "ui/auth_screen.hpp"
#include "auth/crypto.hpp"
#include "core/logger.hpp"
#include <imgui.h>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <algorithm>
#include <iomanip>
namespace wowee { namespace ui {
static std::string hexEncode(const std::vector<uint8_t>& 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<uint8_t> hexDecode(const std::string& hex) {
std::vector<uint8_t> bytes;
for (size_t i = 0; i + 1 < hex.size(); i += 2) {
uint8_t b = static_cast<uint8_t>(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);
}