change key exchange to ED25519

This commit is contained in:
Sen 2025-06-16 15:12:39 +02:00
parent 256721aa12
commit 6afc26e601
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
11 changed files with 148 additions and 105 deletions

View file

@ -97,7 +97,7 @@ public class GuiServer extends Gui implements FieldCallback {
if(GuiServer.this.keypair == null) { if(GuiServer.this.keypair == null) {
GuiServer.this.keypair = EncryptUtil.createKeypair(); GuiServer.this.keypair = EncryptUtil.createKeypair();
GuiServer.this.keyDigest = EncryptUtil.getXorSha512Hash(GuiServer.this.keypair.getPublic().getEncoded()); GuiServer.this.keyDigest = EncryptUtil.getXorSha512Hash(GuiServer.this.keypair.getPublic().getEncoded());
GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-4096 " + GuiServer.this.keyDigest); GuiServer.this.keyLabel.setText("Anmelde-Pubkey: " + EncryptUtil.KEY_ALGO_DISPLAY + " " + GuiServer.this.keyDigest);
GuiServer.this.keyButton.setText("Schlüsselpaar entfernen"); GuiServer.this.keyButton.setText("Schlüsselpaar entfernen");
GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)"); GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)");
GuiServer.this.copyKeyButton.enabled = true; GuiServer.this.copyKeyButton.enabled = true;
@ -124,7 +124,7 @@ public class GuiServer extends Gui implements FieldCallback {
GuiServer.this.encToggle.setValue(reqEnc); GuiServer.this.encToggle.setValue(reqEnc);
GuiServer.this.serverKey = key; GuiServer.this.serverKey = key;
GuiServer.this.serverDigest = sdigest; GuiServer.this.serverDigest = sdigest;
GuiServer.this.idLabel.setText("Server-Pubkey: " + (key != null ? "RSA-4096 " + GuiServer.this.serverDigest : "nicht vorhanden")); GuiServer.this.idLabel.setText("Server-Pubkey: " + (key != null ? EncryptUtil.KEY_ALGO_DISPLAY + " " + GuiServer.this.serverDigest : "nicht vorhanden"));
GuiServer.this.resetButton.enabled = key != null; GuiServer.this.resetButton.enabled = key != null;
GuiServer.this.copyIdButton.enabled = key != null; GuiServer.this.copyIdButton.enabled = key != null;
GuiServer.this.copyKeyButton.enabled = !confirmed; GuiServer.this.copyKeyButton.enabled = !confirmed;
@ -138,7 +138,7 @@ public class GuiServer extends Gui implements FieldCallback {
else { else {
GuiServer.this.keypair = keys; GuiServer.this.keypair = keys;
GuiServer.this.keyDigest = digest; GuiServer.this.keyDigest = digest;
GuiServer.this.keyLabel.setText("Anmelde-Pubkey: RSA-4096 " + GuiServer.this.keyDigest); GuiServer.this.keyLabel.setText("Anmelde-Pubkey: " + EncryptUtil.KEY_ALGO_DISPLAY + " " + GuiServer.this.keyDigest);
GuiServer.this.keyButton.setText("Schlüsselpaar entfernen"); GuiServer.this.keyButton.setText("Schlüsselpaar entfernen");
GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)"); GuiServer.this.passLabel.setText("Ersatz-Passwort (mind. 8 Zeichen)");
} }
@ -154,7 +154,7 @@ public class GuiServer extends Gui implements FieldCallback {
} }
}, "Kopieren")); }, "Kopieren"));
this.copyKeyButton.enabled = this.keypair != null; this.copyKeyButton.enabled = this.keypair != null;
this.keyLabel = this.add(new Label(0, 102, 480, "Anmelde-Pubkey: " + (this.keypair != null ? "RSA-4096 " + this.keyDigest : "nicht vorhanden"), true)); this.keyLabel = this.add(new Label(0, 102, 480, "Anmelde-Pubkey: " + (this.keypair != null ? EncryptUtil.KEY_ALGO_DISPLAY + " " + this.keyDigest : "nicht vorhanden"), true));
this.encToggle = this.add(new Toggle(0, 190, 480, 0, false, this.server.requiresEncryption(), null, "Nur Verschlüsselte Verbindung akzeptieren")); this.encToggle = this.add(new Toggle(0, 190, 480, 0, false, this.server.requiresEncryption(), null, "Nur Verschlüsselte Verbindung akzeptieren"));
this.serverKey = this.server.getServerKey(); this.serverKey = this.server.getServerKey();
this.serverDigest = this.serverKey == null ? null : EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded()); this.serverDigest = this.serverKey == null ? null : EncryptUtil.getXorSha512Hash(this.serverKey.getEncoded());
@ -177,7 +177,7 @@ public class GuiServer extends Gui implements FieldCallback {
} }
}, "Kopieren")); }, "Kopieren"));
this.copyIdButton.enabled = this.serverKey != null; this.copyIdButton.enabled = this.serverKey != null;
this.idLabel = this.add(new Label(0, 224, 480, "Server-Pubkey: " + (this.serverKey != null ? "RSA-4096 " + this.serverDigest : "nicht vorhanden"), true)); this.idLabel = this.add(new Label(0, 224, 480, "Server-Pubkey: " + (this.serverKey != null ? EncryptUtil.KEY_ALGO_DISPLAY + " " + this.serverDigest : "nicht vorhanden"), true));
} }
this.shift(); this.shift();
} }

View file

@ -1,12 +1,10 @@
package client.network; package client.network;
import java.security.KeyPair;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import javax.crypto.SecretKey;
import client.Client; import client.Client;
import client.gui.GuiConfirm; import client.gui.GuiConfirm;
import client.gui.GuiConnect; import client.gui.GuiConnect;
@ -41,6 +39,7 @@ public class ClientLoginHandler implements IClientLoginHandler {
private final Client gm; private final Client gm;
private final NetConnection connection; private final NetConnection connection;
private final ServerInfo server; private final ServerInfo server;
private final KeyPair tempKeys;
private LoginState state = LoginState.HANDSHAKE; private LoginState state = LoginState.HANDSHAKE;
private byte[] token; private byte[] token;
@ -49,6 +48,7 @@ public class ClientLoginHandler implements IClientLoginHandler {
this.connection = conn; this.connection = conn;
this.gm = gm; this.gm = gm;
this.server = server; this.server = server;
this.tempKeys = EncryptUtil.createDHKeypair();
} }
public void onDisconnect(String reason) public void onDisconnect(String reason)
@ -67,9 +67,8 @@ public class ClientLoginHandler implements IClientLoginHandler {
return; return;
} }
this.connection.setConnectionState(PacketRegistry.LOGIN); this.connection.setConnectionState(PacketRegistry.LOGIN);
final SecretKey secret = EncryptUtil.createSharedKey();
final PublicKey pubkey = packet.getKey(); final PublicKey pubkey = packet.getKey();
final byte[] token = packet.getToken(); final PublicKey tempKey = packet.getTempKey();
if(this.server.getServerKey() == null) { if(this.server.getServerKey() == null) {
this.state = LoginState.CONFIRMING; this.state = LoginState.CONFIRMING;
this.gm.schedule(new Runnable() { this.gm.schedule(new Runnable() {
@ -80,33 +79,33 @@ public class ClientLoginHandler implements IClientLoginHandler {
ClientLoginHandler.this.server.setServerKey(pubkey); ClientLoginHandler.this.server.setServerKey(pubkey);
GuiConnect.INSTANCE.editServer(ClientLoginHandler.this.server); GuiConnect.INSTANCE.editServer(ClientLoginHandler.this.server);
ClientLoginHandler.this.gm.displayConnecting(ClientLoginHandler.this.server); ClientLoginHandler.this.gm.displayConnecting(ClientLoginHandler.this.server);
ClientLoginHandler.this.startEncryption(secret, pubkey, token); ClientLoginHandler.this.startEncryption(tempKey);
} }
else { else {
ClientLoginHandler.this.connection.closeChannel("Verbindung wurde abgebrochen"); ClientLoginHandler.this.connection.closeChannel("Verbindung wurde abgebrochen");
} }
} }
}, "Die Identität des Servers ist unbekannt", "Es wurde noch nie mit diesem Server verbunden.\nSoll die Verbindung wirklich fortgesetzt werden?\n\nDie Pubkey-ID des Servers lautet:\nRSA-4096 " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Util.breakString(Base64.getEncoder().encodeToString(pubkey.getEncoded()), 64), "Verbindung herstellen", "Abbrechen und trennen")); }, "Die Identität des Servers ist unbekannt", "Es wurde noch nie mit diesem Server verbunden.\nSoll die Verbindung wirklich fortgesetzt werden?\n\nDie Pubkey-ID des Servers lautet:\n" + EncryptUtil.KEY_ALGO_DISPLAY + " " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Util.breakString(Base64.getEncoder().encodeToString(pubkey.getEncoded()), 64), "Verbindung herstellen", "Abbrechen und trennen"));
} }
}); });
return; return;
} }
else if(!this.server.getServerKey().equals(pubkey)) { else if(!this.server.getServerKey().equals(pubkey)) {
this.connection.closeChannel("Die Identität des Servers hat sich geändert\n\nDer Server hat einen anderen öffentlichen Schlüssel als vorher bekannt war,\ndies kann bedeuten dass der Inhaber jetzt einen anderen Schlüssel verwendet\noder sich ein anderer Server als dieser ausgibt (\"Man-in-the-middle-attack\").\n\nDie Pubkey-ID des Servers lautet: RSA-4096 " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Util.breakString(Base64.getEncoder().encodeToString(pubkey.getEncoded()), 64) + "\n\nDie vertrauenswürdige Pubkey-ID lautet: RSA-4096 " + EncryptUtil.getXorSha512Hash(this.server.getServerKey().getEncoded()) + this.connection.closeChannel("Die Identität des Servers hat sich geändert\n\nDer Server hat einen anderen öffentlichen Schlüssel als vorher bekannt war,\ndies kann bedeuten dass der Inhaber jetzt einen anderen Schlüssel verwendet\noder sich ein anderer Server als dieser ausgibt (\"Man-in-the-middle-attack\").\n\nDie Pubkey-ID des Servers lautet: " + EncryptUtil.KEY_ALGO_DISPLAY + " " + EncryptUtil.getXorSha512Hash(pubkey.getEncoded()) + "\n\nDer öffentliche Schlüssel des Servers lautet:\n" + Util.breakString(Base64.getEncoder().encodeToString(pubkey.getEncoded()), 64) + "\n\nDie vertrauenswürdige Pubkey-ID lautet: " + EncryptUtil.KEY_ALGO_DISPLAY + " " + EncryptUtil.getXorSha512Hash(this.server.getServerKey().getEncoded()) +
"\n\nFalls der Server trotzdem vertrauenswürdig wirkt, kann die Server-Identifizierung\nin den Einstellungen von '" + this.server.getName() + "' zurückgesetzt werden."); "\n\nFalls der Server trotzdem vertrauenswürdig wirkt, kann die Server-Identifizierung\nin den Einstellungen von '" + this.server.getName() + "' zurückgesetzt werden.");
return; return;
} }
this.startEncryption(secret, pubkey, token); this.startEncryption(tempKey);
} }
private void startEncryption(SecretKey secret, PublicKey pubkey, byte[] token) { private void startEncryption(PublicKey serverKey) {
this.state = LoginState.CHALLENGE; this.state = LoginState.CHALLENGE;
this.connection.sendPacket(new LPacketStartEncrypt(secret, pubkey, token), new GenericFutureListener < Future <? super Void >> () { this.connection.sendPacket(new LPacketStartEncrypt(this.tempKeys.getPublic()), new GenericFutureListener < Future <? super Void >> () {
public void operationComplete(Future <? super Void > u) throws Exception { public void operationComplete(Future <? super Void > u) throws Exception {
ClientLoginHandler.this.connection.startEncryption(secret); ClientLoginHandler.this.connection.startEncryption(EncryptUtil.makeKeyAgreement(ClientLoginHandler.this.tempKeys.getPrivate(), serverKey));
ClientLoginHandler.this.token = new byte[32]; ClientLoginHandler.this.token = new byte[32];
TOKEN_RNG.nextBytes(ClientLoginHandler.this.token); TOKEN_RNG.nextBytes(ClientLoginHandler.this.token);
ClientLoginHandler.this.connection.sendPacket(new LPacketChallenge(pubkey, ClientLoginHandler.this.token)); ClientLoginHandler.this.connection.sendPacket(new LPacketChallenge(ClientLoginHandler.this.token));
} }
}); });
} }
@ -116,7 +115,7 @@ public class ClientLoginHandler implements IClientLoginHandler {
this.connection.closeChannel("Unerwartetes Beweis-Paket"); this.connection.closeChannel("Unerwartetes Beweis-Paket");
return; return;
} }
if(!Arrays.equals(this.token, packet.getToken())) { if(!packet.verifyToken(this.server.getServerKey(), this.token)) {
this.connection.closeChannel("Fehlerhaftes Beweis-Token"); this.connection.closeChannel("Fehlerhaftes Beweis-Token");
return; return;
} }
@ -189,7 +188,7 @@ public class ClientLoginHandler implements IClientLoginHandler {
return; return;
} }
this.state = LoginState.AUTHENTICATING; this.state = LoginState.AUTHENTICATING;
this.connection.sendPacket(new LPacketResponse(packet.getToken(this.server.getKeypair().getPrivate()))); this.connection.sendPacket(new LPacketResponse(this.server.getKeypair().getPrivate(), packet.getToken()));
} }
public void handleLoginSuccess(RPacketLoginSuccess packet) public void handleLoginSuccess(RPacketLoginSuccess packet)

View file

@ -1,13 +1,9 @@
package common.packet; package common.packet;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import common.network.ILoginHandler; import common.network.ILoginHandler;
import common.network.Packet; import common.network.Packet;
import common.network.PacketBuffer; import common.network.PacketBuffer;
import common.util.EncryptUtil;
public class LPacketChallenge implements Packet<ILoginHandler> { public class LPacketChallenge implements Packet<ILoginHandler> {
private byte[] token; private byte[] token;
@ -15,8 +11,8 @@ public class LPacketChallenge implements Packet<ILoginHandler> {
public LPacketChallenge() { public LPacketChallenge() {
} }
public LPacketChallenge(PublicKey pubkey, byte[] token) { public LPacketChallenge(byte[] token) {
this.token = EncryptUtil.encryptData(pubkey, token); this.token = token;
} }
public final void readPacketData(PacketBuffer buf) throws IOException { public final void readPacketData(PacketBuffer buf) throws IOException {
@ -31,7 +27,7 @@ public class LPacketChallenge implements Packet<ILoginHandler> {
handler.processChallenge(this); handler.processChallenge(this);
} }
public byte[] getToken(PrivateKey key) { public byte[] getToken() {
return EncryptUtil.decryptData(key, this.token); return this.token;
} }
} }

View file

@ -1,9 +1,13 @@
package common.packet; package common.packet;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import common.network.ILoginHandler; import common.network.ILoginHandler;
import common.network.Packet; import common.network.Packet;
import common.network.PacketBuffer; import common.network.PacketBuffer;
import common.util.EncryptUtil;
public class LPacketResponse implements Packet<ILoginHandler> { public class LPacketResponse implements Packet<ILoginHandler> {
private byte[] token = new byte[0]; private byte[] token = new byte[0];
@ -11,8 +15,8 @@ public class LPacketResponse implements Packet<ILoginHandler> {
public LPacketResponse() { public LPacketResponse() {
} }
public LPacketResponse(byte[] token) { public LPacketResponse(PrivateKey key, byte[] token) {
this.token = token; this.token = EncryptUtil.createSignature(key, token);
} }
public void readPacketData(PacketBuffer buf) throws IOException { public void readPacketData(PacketBuffer buf) throws IOException {
@ -27,7 +31,7 @@ public class LPacketResponse implements Packet<ILoginHandler> {
handler.processResponse(this); handler.processResponse(this);
} }
public byte[] getToken() { public boolean verifyToken(PublicKey key, byte[] token) {
return this.token; return EncryptUtil.verifySignature(key, token, this.token);
} }
} }

View file

@ -1,47 +1,36 @@
package common.packet; package common.packet;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import javax.crypto.SecretKey;
import common.network.ILoginHandler; import common.network.ILoginHandler;
import common.network.Packet; import common.network.Packet;
import common.network.PacketBuffer; import common.network.PacketBuffer;
import common.util.EncryptUtil; import common.util.EncryptUtil;
public class LPacketStartEncrypt implements Packet<ILoginHandler> { public class LPacketStartEncrypt implements Packet<ILoginHandler> {
private byte[] key = new byte[0]; private PublicKey key;
private byte[] token = new byte[0];
public LPacketStartEncrypt() { public LPacketStartEncrypt() {
} }
public LPacketStartEncrypt(SecretKey secret, PublicKey pubkey, byte[] token) { public LPacketStartEncrypt(PublicKey pubkey) {
this.key = EncryptUtil.encryptData(pubkey, secret.getEncoded()); this.key = pubkey;
this.token = EncryptUtil.encryptData(pubkey, token);
} }
public void readPacketData(PacketBuffer buf) throws IOException { public void readPacketData(PacketBuffer buf) throws IOException {
this.key = buf.readByteArray(); this.key = EncryptUtil.decodeDHPublicKey(buf.readByteArray());
this.token = buf.readByteArray();
} }
public void writePacketData(PacketBuffer buf) throws IOException { public void writePacketData(PacketBuffer buf) throws IOException {
buf.writeByteArray(this.key); buf.writeByteArray(this.key.getEncoded());
buf.writeByteArray(this.token);
} }
public void processPacket(ILoginHandler handler) { public void processPacket(ILoginHandler handler) {
handler.processEncryption(this); handler.processEncryption(this);
} }
public SecretKey getKey(PrivateKey key) { public PublicKey getKey() {
return EncryptUtil.decryptSharedKey(key, this.key); return this.key;
}
public byte[] getToken(PrivateKey key) {
return EncryptUtil.decryptData(key, this.token);
} }
} }

View file

@ -1,13 +1,9 @@
package common.packet; package common.packet;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import common.network.IClientLoginHandler; import common.network.IClientLoginHandler;
import common.network.Packet; import common.network.Packet;
import common.network.PacketBuffer; import common.network.PacketBuffer;
import common.util.EncryptUtil;
public class RPacketChallenge implements Packet<IClientLoginHandler> { public class RPacketChallenge implements Packet<IClientLoginHandler> {
private byte[] token; private byte[] token;
@ -15,8 +11,8 @@ public class RPacketChallenge implements Packet<IClientLoginHandler> {
public RPacketChallenge() { public RPacketChallenge() {
} }
public RPacketChallenge(PublicKey pubkey, byte[] token) { public RPacketChallenge(byte[] token) {
this.token = EncryptUtil.encryptData(pubkey, token); this.token = token;
} }
public final void readPacketData(PacketBuffer buf) throws IOException { public final void readPacketData(PacketBuffer buf) throws IOException {
@ -31,7 +27,7 @@ public class RPacketChallenge implements Packet<IClientLoginHandler> {
handler.handleChallenge(this); handler.handleChallenge(this);
} }
public byte[] getToken(PrivateKey key) { public byte[] getToken() {
return EncryptUtil.decryptData(key, this.token); return this.token;
} }
} }

View file

@ -10,24 +10,24 @@ import common.util.EncryptUtil;
public class RPacketRequestEncrypt implements Packet<IClientLoginHandler> { public class RPacketRequestEncrypt implements Packet<IClientLoginHandler> {
private PublicKey key; private PublicKey key;
private byte[] token; private PublicKey tempKey;
public RPacketRequestEncrypt() { public RPacketRequestEncrypt() {
} }
public RPacketRequestEncrypt(PublicKey key, byte[] token) { public RPacketRequestEncrypt(PublicKey key, PublicKey tempKey) {
this.key = key; this.key = key;
this.token = token; this.tempKey = tempKey;
} }
public final void readPacketData(PacketBuffer buf) throws IOException { public final void readPacketData(PacketBuffer buf) throws IOException {
this.key = EncryptUtil.decodePublicKey(buf.readByteArray()); this.key = EncryptUtil.decodePublicKey(buf.readByteArray());
this.token = buf.readByteArray(); this.tempKey = EncryptUtil.decodeDHPublicKey(buf.readByteArray());
} }
public final void writePacketData(PacketBuffer buf) throws IOException { public final void writePacketData(PacketBuffer buf) throws IOException {
buf.writeByteArray(this.key.getEncoded()); buf.writeByteArray(this.key.getEncoded());
buf.writeByteArray(this.token); buf.writeByteArray(this.tempKey.getEncoded());
} }
public void processPacket(IClientLoginHandler handler) { public void processPacket(IClientLoginHandler handler) {
@ -38,7 +38,7 @@ public class RPacketRequestEncrypt implements Packet<IClientLoginHandler> {
return this.key; return this.key;
} }
public byte[] getToken() { public PublicKey getTempKey() {
return this.token; return this.tempKey;
} }
} }

View file

@ -1,9 +1,13 @@
package common.packet; package common.packet;
import java.io.IOException; import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import common.network.IClientLoginHandler; import common.network.IClientLoginHandler;
import common.network.Packet; import common.network.Packet;
import common.network.PacketBuffer; import common.network.PacketBuffer;
import common.util.EncryptUtil;
public class RPacketResponse implements Packet<IClientLoginHandler> { public class RPacketResponse implements Packet<IClientLoginHandler> {
private byte[] token = new byte[0]; private byte[] token = new byte[0];
@ -11,8 +15,8 @@ public class RPacketResponse implements Packet<IClientLoginHandler> {
public RPacketResponse() { public RPacketResponse() {
} }
public RPacketResponse(byte[] token) { public RPacketResponse(PrivateKey key, byte[] token) {
this.token = token; this.token = EncryptUtil.createSignature(key, token);
} }
public void readPacketData(PacketBuffer buf) throws IOException { public void readPacketData(PacketBuffer buf) throws IOException {
@ -27,7 +31,7 @@ public class RPacketResponse implements Packet<IClientLoginHandler> {
handler.handleResponse(this); handler.handleResponse(this);
} }
public byte[] getToken() { public boolean verifyToken(PublicKey key, byte[] token) {
return this.token; return EncryptUtil.verifySignature(key, token, this.token);
} }
} }

View file

@ -11,6 +11,8 @@ import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.EncodedKeySpec; import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
@ -23,7 +25,7 @@ import java.util.Base64;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException; import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator; import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
@ -34,28 +36,93 @@ import javax.crypto.spec.SecretKeySpec;
import common.log.Log; import common.log.Log;
public class EncryptUtil { public class EncryptUtil {
public static final String KEY_ALGO_NAME = "tcr-ed25519";
public static final String KEY_ALGO_DISPLAY = "Ed25519";
private static final long CRC24_INIT = 0xB704CEL; private static final long CRC24_INIT = 0xB704CEL;
private static final long CRC24_POLY = 0x1864CFBL; private static final long CRC24_POLY = 0x1864CFBL;
public static SecretKey createSharedKey() { // private static byte[] deriveKey(byte[] secret) {
// try {
// KeySpec spec = new PBEKeySpec
// SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
// return factory.generateSecret(spec).getEncoded();
// }
// catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
// Log.SYSTEM.error(e, "Konnte Passwort-Prüfwert nicht berechnen");
// return null;
// }
// }
public static SecretKey makeKeyAgreement(PrivateKey key, PublicKey pubkey) {
try { try {
KeyGenerator keygen = KeyGenerator.getInstance("AES"); KeyAgreement agreement = KeyAgreement.getInstance("X25519");
keygen.init(128); agreement.init(key);
return keygen.generateKey(); agreement.doPhase(pubkey, true);
return new SecretKeySpec(agreement.generateSecret(), 0, 16, "AES");
}
catch(NoSuchAlgorithmException | InvalidKeyException e) {
Log.SYSTEM.error(e, "Konnte Diffie-Hellman-Schlüsselaushandlung nicht absolvieren");
return null;
}
}
public static KeyPair createDHKeypair() {
try {
KeyPairGenerator pairgen = KeyPairGenerator.getInstance("X25519");
return pairgen.generateKeyPair();
} }
catch(NoSuchAlgorithmException e) { catch(NoSuchAlgorithmException e) {
throw new RuntimeException(e); Log.SYSTEM.error(e, "Konnte Schlüsselpaar für Diffie-Hellman nicht generieren");
return null;
} }
} }
public static KeyPair createKeypair() { public static KeyPair createKeypair() {
try { try {
KeyPairGenerator pairgen = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator pairgen = KeyPairGenerator.getInstance("Ed25519");
pairgen.initialize(4096);
return pairgen.generateKeyPair(); return pairgen.generateKeyPair();
} }
catch(NoSuchAlgorithmException e) { catch(NoSuchAlgorithmException e) {
Log.SYSTEM.error(e, "Konnte Schlüsselpaar nicht generiren"); Log.SYSTEM.error(e, "Konnte Schlüsselpaar nicht generieren");
return null;
}
}
public static byte[] createSignature(PrivateKey key, byte[] token) {
try {
Signature sig = Signature.getInstance("Ed25519");
sig.initSign(key);
sig.update(token);
return sig.sign();
}
catch(SignatureException | InvalidKeyException | NoSuchAlgorithmException e) {
Log.SYSTEM.error(e, "Konnte Signatur nicht generieren");
return null;
}
}
public static boolean verifySignature(PublicKey key, byte[] token, byte[] signature) {
try {
Signature sig = Signature.getInstance("Ed25519");
sig.initVerify(key);
sig.update(token);
return sig.verify(signature);
}
catch(SignatureException | InvalidKeyException | NoSuchAlgorithmException e) {
Log.SYSTEM.error(e, "Konnte Signatur nicht verifizieren");
return false;
}
}
public static PublicKey decodeDHPublicKey(byte[] encoded) {
try {
EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
KeyFactory factory = KeyFactory.getInstance("X25519");
return factory.generatePublic(spec);
}
catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
Log.SYSTEM.error(e, "Öffentlicher Schlüssel für Diffie-Hellman konnte nicht dekodiert werden");
return null; return null;
} }
} }
@ -63,7 +130,7 @@ public class EncryptUtil {
public static PublicKey decodePublicKey(byte[] encoded) { public static PublicKey decodePublicKey(byte[] encoded) {
try { try {
EncodedKeySpec spec = new X509EncodedKeySpec(encoded); EncodedKeySpec spec = new X509EncodedKeySpec(encoded);
KeyFactory factory = KeyFactory.getInstance("RSA"); KeyFactory factory = KeyFactory.getInstance("Ed25519");
return factory.generatePublic(spec); return factory.generatePublic(spec);
} }
catch(NoSuchAlgorithmException | InvalidKeySpecException e) { catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
@ -75,7 +142,7 @@ public class EncryptUtil {
public static PrivateKey decodePrivateKey(byte[] encoded) { public static PrivateKey decodePrivateKey(byte[] encoded) {
try { try {
EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded); EncodedKeySpec spec = new PKCS8EncodedKeySpec(encoded);
KeyFactory factory = KeyFactory.getInstance("RSA"); KeyFactory factory = KeyFactory.getInstance("Ed25519");
return factory.generatePrivate(spec); return factory.generatePrivate(spec);
} }
catch(NoSuchAlgorithmException | InvalidKeySpecException e) { catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
@ -84,10 +151,6 @@ public class EncryptUtil {
} }
} }
public static SecretKey decryptSharedKey(PrivateKey key, byte[] secret) {
return new SecretKeySpec(decryptData(key, secret), "AES");
}
public static byte[] encryptData(Key key, byte[] data) { public static byte[] encryptData(Key key, byte[] data) {
return cipher(Cipher.ENCRYPT_MODE, key, data); return cipher(Cipher.ENCRYPT_MODE, key, data);
} }
@ -149,7 +212,7 @@ public class EncryptUtil {
} }
public static String getArmoredPubkey(PublicKey pubkey, String cn) { public static String getArmoredPubkey(PublicKey pubkey, String cn) {
StringBuilder sb = new StringBuilder("tcr-rsa-4096 "); StringBuilder sb = new StringBuilder(EncryptUtil.KEY_ALGO_NAME + " ");
sb.append(Base64.getEncoder().encodeToString(pubkey.getEncoded())); sb.append(Base64.getEncoder().encodeToString(pubkey.getEncoded()));
sb.append(' ').append(Base64.getEncoder().encodeToString(crc24(pubkey.getEncoded()))); sb.append(' ').append(Base64.getEncoder().encodeToString(crc24(pubkey.getEncoded())));
return cn == null || cn.isEmpty() ? sb.toString() : sb.append(' ').append(Util.sanitizeCommonName(cn)).toString(); return cn == null || cn.isEmpty() ? sb.toString() : sb.append(' ').append(Util.sanitizeCommonName(cn)).toString();
@ -159,8 +222,8 @@ public class EncryptUtil {
String[] tok = armor.trim().split(" "); String[] tok = armor.trim().split(" ");
if(tok.length != 3 && tok.length != 4) if(tok.length != 3 && tok.length != 4)
throw new IllegalArgumentException("Key muss aus 3 oder 4 Segmenten bestehen"); throw new IllegalArgumentException("Key muss aus 3 oder 4 Segmenten bestehen");
if(!tok[0].equals("tcr-rsa-4096")) if(!tok[0].equals(EncryptUtil.KEY_ALGO_NAME))
throw new IllegalArgumentException("Algorithmus '" + tok[0] + "' ist nicht unterstützt, es wird derzeit nur tcr-rsa-4096 verwendet"); throw new IllegalArgumentException("Algorithmus '" + tok[0] + "' ist nicht unterstützt, es wird derzeit nur " + EncryptUtil.KEY_ALGO_NAME + " verwendet");
byte[] key; byte[] key;
try { try {
key = Base64.getDecoder().decode(tok[1]); key = Base64.getDecoder().decode(tok[1]);
@ -183,7 +246,7 @@ public class EncryptUtil {
PublicKey pubkey; PublicKey pubkey;
try { try {
EncodedKeySpec spec = new X509EncodedKeySpec(key); EncodedKeySpec spec = new X509EncodedKeySpec(key);
KeyFactory factory = KeyFactory.getInstance("RSA"); KeyFactory factory = KeyFactory.getInstance("Ed25519");
pubkey = factory.generatePublic(spec); pubkey = factory.generatePublic(spec);
} }
catch(NoSuchAlgorithmException | InvalidKeySpecException e) { catch(NoSuchAlgorithmException | InvalidKeySpecException e) {

View file

@ -235,7 +235,7 @@ public final class Server implements IThreadListener, Executor {
Log.IO.info("Config-Version: %s", version); Log.IO.info("Config-Version: %s", version);
Log.IO.info("Weltzeit: %d Ticks / %d Sekunden", time, time / 20L); Log.IO.info("Weltzeit: %d Ticks / %d Sekunden", time, time / 20L);
Log.IO.info("Zuletzt geladen: %s", new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date(lastPlayed))); Log.IO.info("Zuletzt geladen: %s", new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date(lastPlayed)));
if(tag.hasByteArray("PrivateKey") && tag.hasByteArray("PublicKey")) { if(System.getProperty("server.regenkey") == null && tag.hasByteArray("PrivateKey") && tag.hasByteArray("PublicKey")) {
PrivateKey key = EncryptUtil.decodePrivateKey(tag.getByteArray("PrivateKey")); PrivateKey key = EncryptUtil.decodePrivateKey(tag.getByteArray("PrivateKey"));
PublicKey pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("PublicKey")); PublicKey pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("PublicKey"));
if(key != null && pubkey != null) if(key != null && pubkey != null)

View file

@ -1,13 +1,9 @@
package server.network; package server.network;
import java.security.KeyPair;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.SecretKey;
import common.color.TextColor; import common.color.TextColor;
import common.log.Log; import common.log.Log;
import common.net.util.concurrent.Future; import common.net.util.concurrent.Future;
@ -39,6 +35,7 @@ public class LoginHandler implements ILoginHandler
private final Server server; private final Server server;
public final NetConnection netManager; public final NetConnection netManager;
private final KeyPair tempKeys;
private LoginState state = LoginState.INIT; private LoginState state = LoginState.INIT;
private int timer; private int timer;
@ -51,6 +48,7 @@ public class LoginHandler implements ILoginHandler
{ {
this.netManager = netManager; this.netManager = netManager;
this.server = server; this.server = server;
this.tempKeys = EncryptUtil.createDHKeypair();
} }
public void closeConnection(String reason) public void closeConnection(String reason)
@ -139,9 +137,7 @@ public class LoginHandler implements ILoginHandler
throw new IllegalStateException("Unerwartetes Handshake-Paket"); throw new IllegalStateException("Unerwartetes Handshake-Paket");
if(SVars.encrypt) { if(SVars.encrypt) {
this.state = LoginState.ENCRYPT; this.state = LoginState.ENCRYPT;
this.loginToken = new byte[4]; this.netManager.sendPacket(new RPacketRequestEncrypt(this.server.getPublicKey(), this.tempKeys.getPublic()));
TOKEN_RNG.nextBytes(this.loginToken);
this.netManager.sendPacket(new RPacketRequestEncrypt(this.server.getPublicKey(), this.loginToken));
} }
else { else {
this.state = LoginState.PASSWORD; this.state = LoginState.PASSWORD;
@ -152,11 +148,7 @@ public class LoginHandler implements ILoginHandler
public void processEncryption(LPacketStartEncrypt packet) { public void processEncryption(LPacketStartEncrypt packet) {
if(this.state != LoginState.ENCRYPT) if(this.state != LoginState.ENCRYPT)
throw new IllegalStateException("Unerwartetes Verschlüsselungs-Paket"); throw new IllegalStateException("Unerwartetes Verschlüsselungs-Paket");
PrivateKey pkey = this.server.getPrivateKey(); this.netManager.startEncryption(EncryptUtil.makeKeyAgreement(this.tempKeys.getPrivate(), packet.getKey()));
if(!Arrays.equals(this.loginToken, packet.getToken(pkey)))
throw new IllegalStateException("Fehlerhaftes Token");
SecretKey key = packet.getKey(pkey);
this.netManager.startEncryption(key);
this.state = LoginState.PROOF; this.state = LoginState.PROOF;
} }
@ -164,7 +156,7 @@ public class LoginHandler implements ILoginHandler
if(this.state != LoginState.PROOF) if(this.state != LoginState.PROOF)
throw new IllegalStateException("Unerwartetes Anforderungs-Paket"); throw new IllegalStateException("Unerwartetes Anforderungs-Paket");
this.state = LoginState.PASSWORD; this.state = LoginState.PASSWORD;
this.netManager.sendPacket(new RPacketResponse(packet.getToken(this.server.getPrivateKey())), new GenericFutureListener < Future <? super Void >> () { this.netManager.sendPacket(new RPacketResponse(this.server.getPrivateKey(), packet.getToken()), new GenericFutureListener < Future <? super Void >> () {
public void operationComplete(Future <? super Void > u) throws Exception { public void operationComplete(Future <? super Void > u) throws Exception {
LoginHandler.this.netManager.sendPacket(new RPacketServerConfig(SVars.accessRequired, SVars.authenticate, SVars.authenticate && SVars.passwordAuth, LoginHandler.this.netManager.sendPacket(new RPacketServerConfig(SVars.accessRequired, SVars.authenticate, SVars.authenticate && SVars.passwordAuth,
SVars.authenticate && SVars.pubkeyAuth)); SVars.authenticate && SVars.pubkeyAuth));
@ -219,7 +211,7 @@ public class LoginHandler implements ILoginHandler
this.loginKey = packet.getKey(); this.loginKey = packet.getKey();
this.loginToken = new byte[32]; this.loginToken = new byte[32];
TOKEN_RNG.nextBytes(this.loginToken); TOKEN_RNG.nextBytes(this.loginToken);
this.netManager.sendPacket(new RPacketChallenge(this.loginKey, this.loginToken)); this.netManager.sendPacket(new RPacketChallenge(this.loginToken));
this.state = LoginState.CHALLENGE; this.state = LoginState.CHALLENGE;
} }
else { else {
@ -230,7 +222,7 @@ public class LoginHandler implements ILoginHandler
public void processResponse(LPacketResponse packet) { public void processResponse(LPacketResponse packet) {
if(this.state != LoginState.CHALLENGE) if(this.state != LoginState.CHALLENGE)
throw new IllegalStateException("Unerwartetes Beweis-Paket"); throw new IllegalStateException("Unerwartetes Beweis-Paket");
if(!Arrays.equals(this.loginToken, packet.getToken())) if(!packet.verifyToken(this.loginKey, this.loginToken))
throw new IllegalStateException("Fehlerhaftes Beweis-Token"); throw new IllegalStateException("Fehlerhaftes Beweis-Token");
this.state = LoginState.AUTHENTICATED; this.state = LoginState.AUTHENTICATED;
} }