fix: generate session keys from the system entropy source

The key came from the shared game random engine, which is seeded from
the start time. It now comes from std::random_device through a small
helper. The key stays a 32 bit number, so the packets do not change.

The master and world servers no longer write the key or its hash to
the log.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-11-10 12:54:33 -06:00
parent 0a0e37e96f
commit e8479cd6e5
7 changed files with 59 additions and 3 deletions

View File

@@ -372,6 +372,23 @@ namespace GeneralUtils {
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
/**
* Generates a number from the operating system's entropy source instead of the shared game engine.
* Use this for values that must not be predictable from earlier ones, such as session keys.
*/
template <typename T>
inline T GenerateUnpredictableNumber() {
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>, "Not an unsigned integral type");
std::random_device device;
T value = 0;
for (uint32_t bytesFilled = 0; bytesFilled < sizeof(T); bytesFilled += sizeof(uint32_t)) {
// Two half shifts, a full 32 bit shift is not valid on a 32 bit type
value = static_cast<T>(static_cast<T>(value << 16) << 16) | static_cast<T>(device());
}
return value;
}
// https://www.quora.com/How-do-you-round-to-specific-increments-like-0-5-in-C
// Rounds to the nearest floating point value specified.
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>

View File

@@ -847,7 +847,7 @@ namespace {
}
activeSessions.insert(std::make_pair(sessionKey, username.string));
LOG("Got sessionKey %i for user %s", sessionKey, username.string.c_str());
LOG("Got a session key for user %s", username.string.c_str());
}
void OnRequestSessionKey(const RequestSessionKey& request, const SystemAddress& sysAddr) {

View File

@@ -308,7 +308,7 @@ void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAdd
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("version_minor")).value_or(ClientVersion::minor);
// The user key
uint32_t sessionKey = GeneralUtils::GenerateRandomNumber<uint32_t>();
uint32_t sessionKey = GeneralUtils::GenerateUnpredictableNumber<uint32_t>();
std::string userHash = std::to_string(sessionKey);
userHash = md5(userHash);
loginResponse.userKey = LUWString(userHash);

View File

@@ -882,7 +882,7 @@ namespace {
//Verify it:
if (userHash != it->second.hash) {
LOG("SOMEONE IS TRYING TO HACK? SESSION KEY MISMATCH: ours: %s != master: %s", userHash.c_str(), it->second.hash.c_str());
LOG("Session key mismatch for user %s", username.GetAsString().c_str());
Game::server->Disconnect(it->second.sysAddr, eServerDisconnectIdentifiers::INVALID_SESSION_KEY);
return;
} else {

View File

@@ -10,6 +10,7 @@ set(DCOMMONTEST_SOURCES
"TestLDFFormat.cpp"
"TestNiPoint3.cpp"
"TestEncoding.cpp"
"TestGeneralUtils.cpp"
"TestLUString.cpp"
"TestLUWString.cpp"
"TestBitStreamUtils.cpp"

View File

@@ -0,0 +1,35 @@
#include <gtest/gtest.h>
#include <random>
#include <set>
#include "Game.h"
#include "GeneralUtils.h"
TEST(GeneralUtilsTest, UnpredictableNumberLeavesTheGameEngineAlone) {
Game::randomEngine = std::mt19937(1234);
std::mt19937 expected(1234);
GeneralUtils::GenerateUnpredictableNumber<uint32_t>();
GeneralUtils::GenerateUnpredictableNumber<uint64_t>();
EXPECT_EQ(Game::randomEngine(), expected());
}
TEST(GeneralUtilsTest, UnpredictableNumberDoesNotFollowTheGameEngineSeed) {
std::set<uint64_t> seen;
for (uint32_t i = 0; i < 16; i++) {
// The same seed every time would give the same number every time from the game engine
Game::randomEngine = std::mt19937(1234);
seen.insert(GeneralUtils::GenerateUnpredictableNumber<uint64_t>());
}
EXPECT_EQ(seen.size(), 16);
}
TEST(GeneralUtilsTest, UnpredictableNumberFillsEveryByte) {
uint64_t seenBits = 0;
for (uint32_t i = 0; i < 64; i++) seenBits |= GeneralUtils::GenerateUnpredictableNumber<uint64_t>();
EXPECT_EQ(seenBits, 0xFFFFFFFFFFFFFFFFULL);
}

View File

@@ -1,9 +1,12 @@
#include "Game.h"
#include <random>
class Logger;
class dConfig;
namespace Game
{
Logger* logger;
dConfig* config = nullptr; // Permissions.cpp reads the permission levels from it
std::mt19937 randomEngine;
} // namespace Game