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:
| Weg | Wie | Wann sinnvoll |
|---|---|---|
| In-game (nahtlos) | Client-Mod sendet den HWID über den Plugin-Channel nexus:hwid; die Paper-Bridge leitet ihn als player.device weiter | Eigene Client-Mod im Modpack |
| Standalone-Agent | Die hwid-agent-JAR postet den Fingerprint direkt an den token-gesicherten Ingest-Endpunkt | Eigener Launcher |
2. Fingerprint-Verfahren
net.jubyte.nexus.hwid.HardwareFingerprint (reines Java, keine native Lib,
unit-getestet in HardwareFingerprintTest).
2.1 Gesammelte Signale
| Signal-Key | Quelle (Java-API) | Stabilität | Hinweis |
|---|---|---|---|
net.macs | NetworkInterface.getNetworkInterfaces() | am höchsten | physische NICs, lexikografisch sortiert, komma-getrennt; Loopback/virtuelle NICs gefiltert |
os.name | System.getProperty("os.name") | sehr hoch | z. B. „Windows 11", „Linux" |
os.arch | System.getProperty("os.arch") | sehr hoch | z. B. „amd64", „aarch64" |
cpu.cores | Runtime.getRuntime().availableProcessors() | hoch | Kernzahl |
host | InetAddress.getLocalHost().getHostName() | mittel-hoch | bei 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
| Eigenschaft | Wert |
|---|---|
| Channel-Name | nexus:hwid (case-sensitive) |
| Richtung | Client → Server |
| Wire-Format | UTF-8-String (der rohe 64-Hex-HWID) |
| Größen-Grenze | 1–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>]
| Flag | Env-Fallback | Pflicht | Format |
|---|---|---|---|
--core-url | NEXUS_CORE_URL | ja | HTTPS-URL |
--token | NEXUS_PLAYERDATA_TOKEN | ja | gemeinsames Secret |
--uuid | – | ja | UUID mit Bindestrichen |
--name | – | nein | String |
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[] (kind ∈ MAIN/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)
| Methode | Pfad | Permission | Zweck |
|---|---|---|---|
GET | /devices?gameId=<uuid> | playermanagement.devices.view | Geräte eines Spielers (mit firstSeen, lastSeen, loginCount) |
GET | /inventory?gameId=<uuid> | playermanagement.inventory.view | Inventar-Snapshots |
POST | /inventory/give | playermanagement.inventory.manage | /give <player> <material> <amount> |
POST | /inventory/clear | playermanagement.inventory.manage | /clear <player> [material] |
POST | /inventory/set-slot | playermanagement.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):
| Spalte | Typ | Bedeutung |
|---|---|---|
id | UUID | Primärschlüssel |
game_id | VARCHAR(64) | Minecraft-UUID des Spielers |
player_name | VARCHAR(100) | Name (Audit-Trail) |
hardware_id | VARCHAR(128) | SHA-256-Fingerprint oder Agent-ID |
os | VARCHAR(64) | Betriebssystem |
mc_version | VARCHAR(32) | Minecraft-Version |
client | VARCHAR(64) | Client-Brand (fabric/forge/vanilla) |
source | VARCHAR(48) | bridge / hwid-agent / unknown |
first_seen, last_seen | TIMESTAMP | erster/letzter Login |
login_count | BIGINT | hochgezä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)
| Option | Typ | Default | Secret | Bedeutung |
|---|---|---|---|---|
ingest.token | String | "" | ja | Shared-Token für Adapter/Agent; leer → alle Ingest-Anfragen werden abgelehnt |
retention.days | Integer | 60 | nein | Aufbewahrung 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
| Aspekt | Umsetzung |
|---|---|
| Rohe MAC-Adressen | nie übertragen/gespeichert — nur Input des SHA-256 |
| Gespeicherter Wert | nur der 64-Hex-Fingerprint + Metadaten (OS, Client) |
| Hashing | SHA-256, einweg, deterministisch |
| Token-Vergleich | konstantzeit (MessageDigest.isEqual), Token nie geloggt |
| Transport | nur HTTPS für Ingest empfohlen |
| Aufbewahrung | Default 60 Tage, stündliches prune(), manuell pro gameId löschbar |
| Opt-in | Standalone-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
- Minecraft-Bridge – liefert
player.deviceaus dem Spiel - Bridge-Protokoll – Frame-Format
- Modul-Katalog – PlayerData-Modul im Detail