Nexus HWID-Agent (Hardware-Fingerprint)

„Hardware-Tracking": Vanilla-Minecraft liefert keine Hardware-ID; sie muss auf dem Client berechnet werden. Der HWID-Agent ist die Referenz-Implementierung dafür und die Grundlage für hardwarebasierte Alt- und Ban-Evasion-Erkennung.

Diese Seite ist die vollständige Referenz: Fingerprint-Verfahren, Übertragungswege, Wire-Formate, Speicher-Schema, REST-API, Build und Datenschutz — inklusive Entwickler-Beispielen.


1. Überblick

Der HWID-Agent berechnet aus stabilen Maschinensignalen einen deterministischen SHA-256-Fingerprint und liefert ihn ins Panel. Daraus ergeben sich:

  • Alt-Erkennung: mehrere Konten auf derselben Hardware.
  • Ban-Evasion-Schutz: ein gebannter Spieler kehrt mit neuem Account, aber gleicher Maschine zurück.
  • Geräte-Profile: Konten lassen sich über ihr Gerät verknüpfen.

Es gibt zwei Lieferwege, die beide im Core-Event player.device münden und vom PlayerData-Modul persistiert werden:

WegWieWann sinnvoll
In-game (nahtlos)Client-Mod sendet den HWID über den Plugin-Channel nexus:hwid; die Paper-Bridge leitet ihn als player.device weiterEigene Client-Mod im Modpack
Standalone-AgentDie hwid-agent-JAR postet den Fingerprint direkt an den token-gesicherten Ingest-EndpunktEigener Launcher

2. Fingerprint-Verfahren

net.jubyte.nexus.hwid.HardwareFingerprint (reines Java, keine native Lib, unit-getestet in HardwareFingerprintTest).

2.1 Gesammelte Signale

Signal-KeyQuelle (Java-API)StabilitätHinweis
net.macsNetworkInterface.getNetworkInterfaces()am höchstenphysische NICs, lexikografisch sortiert, komma-getrennt; Loopback/virtuelle NICs gefiltert
os.nameSystem.getProperty("os.name")sehr hochz. B. „Windows 11", „Linux"
os.archSystem.getProperty("os.arch")sehr hochz. B. „amd64", „aarch64"
cpu.coresRuntime.getRuntime().availableProcessors()hochKernzahl
hostInetAddress.getLocalHost().getHostName()mittel-hochbei Sandbox leerer Fallback

2.2 Hashing

1. Kanonische Form (TreeMap → deterministisch sortiert):
   cpu.cores=16;host=gaming-pc;net.macs=00aabb…,aabb00…;os.arch=amd64;os.name=Windows 11
2. SHA-256 über die UTF-8-Bytes
3. Hex-kodiert, 64 Zeichen, lowercase  →  z. B. 7f8e9a1b2c3d4e5f…

Derselbe Rechner ergibt denselben Fingerprint; ein anderer Rechner einen anderen (SHA-256-Kollisionsresistenz). Die rohen MAC-Adressen werden nie übertragen oder gespeichert — sie fließen nur in den Hash und werden danach verworfen.

2.3 Entwickler-Beispiel: Fingerprint berechnen

import net.jubyte.nexus.hwid.HardwareFingerprint;

// Bequem in einem Schritt:
String hwid = HardwareFingerprint.fingerprint();         // 64-hex SHA-256

// Oder Signale inspizieren (z. B. zum Debuggen):
SortedMap<String,String> signals = HardwareFingerprint.gather();
String same = HardwareFingerprint.compute(signals);
assert hwid.equals(same);                                // deterministisch

3. Lieferweg A – In-game über nexus:hwid

3.1 Plugin-Channel

EigenschaftWert
Channel-Namenexus:hwid (case-sensitive)
RichtungClient → Server
Wire-FormatUTF-8-String (der rohe 64-Hex-HWID)
Größen-Grenze1–128 Zeichen (defensiv geprüft)

3.2 Server-seitige Verarbeitung

net.jubyte.nexus.bridge.paper.HwidChannel registriert den eingehenden Channel, validiert die Länge, reichert mit Spieler-UUID, Name und Client-Brand an und sendet einen player.device-Frame über die Bridge an den Core:

@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
  if (!CHANNEL.equals(channel) || message == null || message.length == 0) return;
  String hwid = new String(message, StandardCharsets.UTF_8).trim();
  if (hwid.isEmpty() || hwid.length() > 128) return;          // VARCHAR(128)-Schutz

  Map<String,Object> data = new HashMap<>();
  data.put("gameId", player.getUniqueId().toString());
  data.put("name",   player.getName());
  data.put("hardwareId", hwid);
  String brand = player.getClientBrandName();                 // "fabric", "forge", …
  if (brand != null && !brand.isBlank()) data.put("client", brand);
  plugin.client().send("player.device", data);                // WebSocket → Core
}

3.3 Entwickler-Beispiel: Client-Mod (Fabric/Forge)

Berechne den HWID auf dem Client (gern mit derselben HardwareFingerprint-Klasse) und sende ihn beim Join auf dem Channel nexus:hwid:

String hwid = HardwareFingerprint.fingerprint();
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeCharSequence(hwid, StandardCharsets.UTF_8);
player.connection.send(new ServerboundCustomPayloadPacket(
    new ResourceLocation("nexus", "hwid"), buf));

3.4 Core-seitige Weiterleitung

net.jubyte.nexus.core.server.BridgeWsHandler publiziert den Frame als NexusEvent, den das PlayerData-Modul abonniert:

case "player.device" -> eventBus.publish(NexusEvent.of("player.device", Map.of(
    "gameId",     data.path("gameId").asText(""),
    "name",       data.path("name").asText(""),
    "hardwareId", data.path("hardwareId").asText(""),
    "os",         data.path("os").asText(""),
    "mcVersion",  data.path("mcVersion").asText(""),
    "client",     data.path("client").asText(""),
    "source",     "bridge")));

4. Lieferweg B – Standalone-Agent (Launcher)

net.jubyte.nexus.hwid.HwidAgent ist die main-Klasse der JAR. Sie berechnet den Fingerprint und postet ihn an den Ingest-Endpunkt.

4.1 CLI

java -jar jubyte-nexus-hwid-agent-0.1.0.jar \
     --core-url https://panel.example.net \
     --token    <X-PlayerData-Token aus den PlayerData-Modul-Einstellungen> \
     --uuid     <player-uuid> \
     [--name    <player-name>]
FlagEnv-FallbackPflichtFormat
--core-urlNEXUS_CORE_URLjaHTTPS-URL
--tokenNEXUS_PLAYERDATA_TOKENjagemeinsames Secret
--uuidjaUUID mit Bindestrichen
--nameneinString

Exit-Code 0 bei HTTP 200, sonst 1. Der Token wandert ausschließlich in den Header X-PlayerData-Token und wird nie geloggt. Der JSON-Payload wird gegen Injection escaped (", \, Steuerzeichen < 0x20\uXXXX).

4.2 Entwickler-Beispiel: Launcher-Integration

# Im Launcher nach erfolgreichem Auth, vor Spielstart:
export NEXUS_CORE_URL=https://panel.example.net
export NEXUS_PLAYERDATA_TOKEN="$INGEST_TOKEN"
java -jar jubyte-nexus-hwid-agent-0.1.0.jar \
     --uuid "$PLAYER_UUID" --name "$PLAYER_NAME" || echo "HWID-Report fehlgeschlagen (non-fatal)"

5. REST-API (PlayerData-Modul)

Basis: /api/v1/modules/playerdata/. Ingest ist token- , Lesen/Verwalten ist **permission-**authentifiziert.

5.1 Ingest (Token-Auth via X-PlayerData-Token)

POST /ingest/device

{
  "devices": [
    {
      "gameId": "550e8400-e29b-41d4-a716-446655440000",
      "playerName": "Steve",
      "hardwareId": "7f8e9a1b2c3d4e5f…",
      "os": "Windows 11",
      "mcVersion": "1.21.4",
      "client": "Fabric",
      "source": "hwid-agent"
    }
  ]
}

Antwort 200: { "ingested": 1 }. Fehlender/falscher Token → 403 Forbidden (konstantzeit-Vergleich MessageDigest.isEqual).

POST /ingest/inventory — analog mit snapshots[] (kindMAIN/ARMOR/OFFHAND/ENDERCHEST).

curl -X POST https://panel.example.net/api/v1/modules/playerdata/ingest/device \
  -H "X-PlayerData-Token: $INGEST_TOKEN" -H "Content-Type: application/json" \
  -d '{"devices":[{"gameId":"550e…","playerName":"Steve","hardwareId":"7f8e…","os":"Windows 11","source":"hwid-agent"}]}'

5.2 Lesen (Permission-Auth)

MethodePfadPermissionZweck
GET/devices?gameId=<uuid>playermanagement.devices.viewGeräte eines Spielers (mit firstSeen, lastSeen, loginCount)
GET/inventory?gameId=<uuid>playermanagement.inventory.viewInventar-Snapshots
POST/inventory/giveplayermanagement.inventory.manage/give <player> <material> <amount>
POST/inventory/clearplayermanagement.inventory.manage/clear <player> [material]
POST/inventory/set-slotplayermanagement.inventory.manage/item replace entity <player> <slot> with <material> <amount>

Beispielantwort GET /devices:

{ "items": [ {
  "id": "00000000-0000-0000-0000-000000000001",
  "playerName": "Steve", "hardwareId": "7f8e…", "os": "Windows 11",
  "mcVersion": "1.21.4", "client": "Fabric", "source": "bridge",
  "firstSeen": "2026-06-01T14:30:00Z", "lastSeen": "2026-06-29T10:15:00Z",
  "loginCount": 12 } ] }

6. Speicher-Schema

Tabelle mod_playerdata.device_records (Migration V60__playerdata.sql):

SpalteTypBedeutung
idUUIDPrimärschlüssel
game_idVARCHAR(64)Minecraft-UUID des Spielers
player_nameVARCHAR(100)Name (Audit-Trail)
hardware_idVARCHAR(128)SHA-256-Fingerprint oder Agent-ID
osVARCHAR(64)Betriebssystem
mc_versionVARCHAR(32)Minecraft-Version
clientVARCHAR(64)Client-Brand (fabric/forge/vanilla)
sourceVARCHAR(48)bridge / hwid-agent / unknown
first_seen, last_seenTIMESTAMPerster/letzter Login
login_countBIGINThochgezählt bei Wiederkehr derselben Hardware

Eindeutigkeit: UNIQUE (game_id, hardware_id) — gleiches Gerät beim gleichen Spieler ist ein Upsert (last_seen aktualisiert, login_count += 1). Index: (game_id, last_seen).

PlayerDataService.onDeviceEvent() schreibt eingehende player.device-Events; saveDevices() führt den Upsert aus; prune() läuft stündlich und löscht Datensätze älter als retention.days.


7. Konfiguration (PlayerData-Modul)

OptionTypDefaultSecretBedeutung
ingest.tokenString""jaShared-Token für Adapter/Agent; leer → alle Ingest-Anfragen werden abgelehnt
retention.daysInteger60neinAufbewahrung von Geräte-/Inventar-Daten (min. 1)

Permissions: playermanagement.devices.view, playermanagement.inventory.view, playermanagement.inventory.manage.

Setup-Beispiel:

1. Token erzeugen:   ingest.token = nxk_$(openssl rand -hex 32)
2. Im ACP unter PlayerData-Modul-Einstellungen setzen (Secret-Feld)
3. Token an Launcher/Adapter weitergeben → --token / X-PlayerData-Token
4. Geräte erscheinen in der Spielerakte → Tab „Geräte"

8. Distribution

Die HWID-Agent-JAR (jubyte-nexus-hwid-agent-*.jar, Uber-JAR, ~50 KB, 0 Laufzeit-Deps) gibt es im JuByte-Store-Konto unter Downloads.

  • GroupId/ArtifactId: net.jubyte.nexus:nexus-hwid-agent:0.1.0
  • Main-Class: net.jubyte.nexus.hwid.HwidAgent
  • Java: 21+, keine externen Laufzeit-Abhängigkeiten (nur Stdlib).

Distribution: entweder die JAR vom Launcher aufrufen lassen (Weg B) oder die HardwareFingerprint-Klasse in eine Client-Mod einbetten und über nexus:hwid senden (Weg A).


9. Datenschutz & Sicherheit

AspektUmsetzung
Rohe MAC-Adressennie übertragen/gespeichert — nur Input des SHA-256
Gespeicherter Wertnur der 64-Hex-Fingerprint + Metadaten (OS, Client)
HashingSHA-256, einweg, deterministisch
Token-Vergleichkonstantzeit (MessageDigest.isEqual), Token nie geloggt
Transportnur HTTPS für Ingest empfohlen
AufbewahrungDefault 60 Tage, stündliches prune(), manuell pro gameId löschbar
Opt-inStandalone-Agent: Launcher steuert/informiert; In-game: nur wenn Mod installiert

Empfehlung: In Launcher-Splashscreen bzw. Server-MOTD transparent machen, dass Geräte-Fingerprinting zur Anti-Cheat-/Ban-Evasion-Erkennung aktiv ist.

Alt-/Ban-Evasion-Erkennung (Ablauf)

1. Alice gebannt:  device_records(Alice,  hwid=XYZ, login_count=10)
2. AliceAlt joint mit gleicher Hardware → Upsert device_records(AliceAlt, hwid=XYZ)
3. Staff sieht im Tab „Geräte" denselben hwid → Abfrage aller Konten mit XYZ:
   SELECT * FROM mod_playerdata.device_records WHERE hardware_id = 'XYZ';
4. Geräte-Ban (BanSystem-Integration) möglich.

Das PlayerData-Modul speichert die Geräte; die „Query-by-Device"-Verknüpfung ist Aufgabe eines BanSystem-/Alt-Detection-Moduls.


10. Ergebnis im Panel & Tests

Geräte erscheinen in der Spielerakte → Geräte (Perm playermanagement.devices.view). Da gleiche Hardware denselben Fingerprint ergibt, taucht ein von mehreren Konten genutztes Gerät bei jedem dieser Konten auf.

Tests: HardwareFingerprintTest (Determinismus, Hex-Format, Signal-Sensitivität), HwidAgentTest (Payload-Form, JSON-Escaping), PlayerDataBridgeIntegrationTest und PlayerDataIntegrationTest (End-to-End-Ingest + Token-403).

Siehe auch