add password hashing and commands

This commit is contained in:
Sen 2025-05-30 21:12:59 +02:00
parent 1b61f085e3
commit ec0a1aa5c3
Signed by: sen
GPG key ID: 3AC50A6F47D1B722
9 changed files with 148 additions and 19 deletions

View file

@ -67,8 +67,8 @@ public class GuiForm extends Gui implements ButtonCallback {
GuiForm.this.labels[index].setText(name);
}
};
this.inputs[z] = this.add((param & 0x80000000) != 0 ? new PasswordField(0, 50 * z, 300, 24, Math.min(param & 0xffff, 256), callback, (String)obj) :
new Field(0, 50 * z, 300, 24, Math.min(param & 0xffff, 256), callback, (String)obj));
this.inputs[z] = this.add((param & 0x80000000) != 0 ? new PasswordField(0, 50 * z, 300, 24, Math.min(param & 0xffff, 1024), callback, (String)obj) :
new Field(0, 50 * z, 300, 24, Math.min(param & 0xffff, 1024), callback, (String)obj));
}
}
this.add(new NavButton(0, 50 * (this.inputs.length + 1), 148, 24, null, "Abbrechen"));

View file

@ -39,7 +39,7 @@ public class CPacketForm implements Packet<IPlayer>
obj = buf.readVarInt();
break;
default:
obj = buf.readString(256);
obj = buf.readString(1024);
break;
}
this.data[z] = obj;

View file

@ -47,7 +47,7 @@ public class RPacketServerConfig implements Packet<IClientLoginHandler> {
}
public boolean isAuthenticating() {
return this.passwordAuth;
return this.requiresAuth;
}
public boolean canUsePassword() {

View file

@ -10,9 +10,11 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;
@ -24,7 +26,9 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import common.log.Log;
@ -189,6 +193,7 @@ public class EncryptUtil {
private static final long CRC24_INIT = 0xB704CEL;
private static final long CRC24_POLY = 0x1864CFBL;
private static final byte[] CLIENT_SALT = "ItIsSaltySalt2765666 tftffs ##89n!.le98udMeowHAsh44m;+*3m9DI9Sqn".getBytes();
private static byte[] crc24(byte[] data) {
long crc = CRC24_INIT;
@ -203,4 +208,23 @@ public class EncryptUtil {
int value = (int)(crc & 0xFFFFFFL);
return new byte[] {(byte)((value >> 16) & 0xff), (byte)((value >> 8) & 0xff), (byte)(value & 0xff)};
}
public static byte[] hashPassword(String password, byte[] salt) {
try {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return factory.generateSecret(spec).getEncoded();
}
catch(NoSuchAlgorithmException | InvalidKeySpecException e) {
Log.SYSTEM.error(e, "Konnte Passwort-Prüfwert nicht errechnen");
return null;
}
}
public static Pair<byte[], byte[]> hashPassword(String password) {
SecureRandom rand = new SecureRandom();
byte[] salt = new byte[64];
rand.nextBytes(salt);
return new Pair<byte[], byte[]>(hashPassword(password, salt), salt);
}
}

View file

@ -7,6 +7,7 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
@ -854,7 +855,7 @@ public final class Server implements IThreadListener {
if(tag != null)
conn.readTags(tag);
if(Config.authenticate) {
if(conn.getPassword() == null && conn.getPubkey() == null) {
if(conn.getPasswordHash() == null && conn.getPubkey() == null) {
if(tag != null)
return loginKey != null ? "Falscher Pubkey" : "Falsches Passwort";
if(!Config.register)
@ -870,11 +871,12 @@ public final class Server implements IThreadListener {
return "Ein neues Passwort ist erforderlich um diesen Server zu betreten (mindestens " + Config.minPassLength + " Zeichen)";
if(loginPass.length() < Config.minPassLength)
return "Passwort ist zu kurz, mindestens " + Config.minPassLength + " Zeichen";
conn.setPassword(loginPass);
conn.setPasswordHash(EncryptUtil.hashPassword(loginPass));
Log.NETWORK.info(loginUser + " registrierte sich mit Passwort");
}
}
else if(conn.getPubkey() != null ? !conn.getPubkey().equals(loginKey) : !conn.getPassword().equals(loginPass)) {
else if(conn.getPubkey() != null ? !conn.getPubkey().equals(loginKey) :
(loginPass == null || !MessageDigest.isEqual(EncryptUtil.hashPassword(loginPass, conn.getPasswordHash().second()), conn.getPasswordHash().first()))) {
return loginKey != null ? "Falscher Pubkey" : "Falsches Passwort";
}
else {

View file

@ -25,6 +25,7 @@ import server.command.commands.CommandOfflinetp;
import server.command.commands.CommandPasswd;
import server.command.commands.CommandPlayers;
import server.command.commands.CommandPotion;
import server.command.commands.CommandPubkey;
import server.command.commands.CommandRegister;
import server.command.commands.CommandRemove;
import server.command.commands.CommandRevoke;
@ -280,6 +281,7 @@ public class CommandEnvironment {
this.registerExecutable(new CommandMessage());
this.registerExecutable(new CommandShutdown());
this.registerExecutable(new CommandPasswd());
this.registerExecutable(new CommandPubkey());
this.registerExecutable(new CommandPlayers());
this.registerExecutable(new CommandSave());
this.registerExecutable(new CommandRegister());

View file

@ -1,8 +1,11 @@
package server.command.commands;
import java.security.MessageDigest;
import common.color.TextColor;
import common.init.Config;
import common.network.IPlayer;
import common.util.EncryptUtil;
import server.command.Command;
import server.command.CommandEnvironment;
import server.command.Executor;
@ -25,6 +28,10 @@ public class CommandPasswd extends Command {
throw new RunException("Bei Verwendung als Spieler darf kein Passwort angegeben werden");
if(player.getAdmin() && player != exec)
throw new RunException("%s ist ein Admin", player.getUser());
if(player.getPubkey() != null && player == exec)
throw new RunException("Es darf kein Pubkey vorhanden sein, um diesen Befehl an sich selbst anzuwenden");
if(player.getPasswordHash() == null && player == exec)
throw new RunException("Es muss ein Passwort vorhanden sein, um diesen Befehl an sich selbst anzuwenden");
((Player)exec).displayForm(new Form() {
private Field checkField;
private Field passwordField;
@ -42,11 +49,11 @@ public class CommandPasswd extends Command {
protected void accept() {
Player plr = env.getServer().getPlayer(player.getUser());
if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec)) {
if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec) || (plr.getPasswordHash() == null && plr == exec) || (plr.getPubkey() != null && plr == exec)) {
exec.logConsole(TextColor.DRED + "Ein Fehler ist aufgetreten");
return;
}
if(this.checkField != null && !this.checkField.get().equals(plr.getPassword())) {
if(this.checkField != null && !MessageDigest.isEqual(EncryptUtil.hashPassword(this.checkField.get(), plr.getPasswordHash().second()), plr.getPasswordHash().first())) {
exec.logConsole(TextColor.RED + "Falsches Passwort eingegeben");
return;
}
@ -54,7 +61,8 @@ public class CommandPasswd extends Command {
exec.logConsole(TextColor.RED + "Passwörter stimmen nicht überein");
return;
}
plr.setPassword(this.passwordField.get());
plr.setPasswordHash(EncryptUtil.hashPassword(this.passwordField.get()));
plr.setPubkey(null);
exec.logConsole(TextColor.GREEN + "Passwort" + (plr != exec ? " für %s" : "") + " gesetzt", plr.getUser());
}
});
@ -62,7 +70,10 @@ public class CommandPasswd extends Command {
else if(exec.isConsole()) {
if(password == null)
throw new RunException("Bei Verwendung in der Konsole muss ein Passwort angegeben werden");
player.setPassword(password);
if(password.length() < 8)
throw new RunException("Das Passwort ist zu kurz, mindestens 8 Zeichen sind erforderlich");
player.setPasswordHash(EncryptUtil.hashPassword(password));
player.setPubkey(null);
exec.logConsole(TextColor.GREEN + "Passwort für %s gesetzt", player.getUser());
}
}

View file

@ -0,0 +1,87 @@
package server.command.commands;
import java.security.MessageDigest;
import java.security.PublicKey;
import common.color.TextColor;
import common.network.IPlayer;
import common.util.EncryptUtil;
import common.util.Pair;
import server.command.Command;
import server.command.CommandEnvironment;
import server.command.Executor;
import server.command.RunException;
import server.network.Player;
import server.util.Form;
public class CommandPubkey extends Command {
public CommandPubkey() {
super("pubkey");
this.addPlayer("player", true);
this.setParamsOptional();
this.addString("keySpec", false);
this.addString("keyData", false);
this.addString("keyHash", false);
this.addString("keyName", false);
}
public void exec(CommandEnvironment env, Executor exec, Player player, String keySpec, String keyData, String keyHash, String keyName) {
if(exec.isPlayer()) {
if(keySpec != null || keyData != null || keyHash != null || keyName != null)
throw new RunException("Bei Verwendung als Spieler darf kein Schlüssel angegeben werden");
if(player.getAdmin() && player != exec)
throw new RunException("%s ist ein Admin", player.getUser());
((Player)exec).displayForm(new Form() {
private Field checkField;
private Field keyField;
protected void init() {
this.checkField = player.getPasswordHash() == null || player != exec ? null : this.addPassword("Aktuelles Passwort", 0, IPlayer.MAX_PASS_LENGTH, "");
this.keyField = this.addField("Neuer Pubkey", 1, 960, "");
}
public String getTitle() {
return "Schlüssel für " + player.getUser() + " ändern";
}
protected void accept() {
Player plr = env.getServer().getPlayer(player.getUser());
if(!((Player)exec).isAdmin() || plr == null || (plr.isAdmin() && plr != exec)) {
exec.logConsole(TextColor.DRED + "Ein Fehler ist aufgetreten");
return;
}
if(this.checkField != null && plr.getPasswordHash() != null && !MessageDigest.isEqual(EncryptUtil.hashPassword(this.checkField.get(), plr.getPasswordHash().second()), plr.getPasswordHash().first())) {
exec.logConsole(TextColor.RED + "Falsches Passwort eingegeben");
return;
}
Pair<PublicKey, String> key;
try {
key = EncryptUtil.parseArmoredPubkey(this.keyField.get());
}
catch(IllegalArgumentException e) {
exec.logConsole(TextColor.RED + "Ungültiger Schlüssel");
return;
}
plr.setPasswordHash(null);
plr.setPubkey(key.first());
exec.logConsole(TextColor.GREEN + "Schlüssel" + (plr != exec ? " für %s" : "") + " gesetzt", plr.getUser());
}
});
}
else if(exec.isConsole()) {
if(keySpec == null || keyData == null || keyHash == null)
throw new RunException("Bei Verwendung in der Konsole muss ein Schlüssel angegeben werden");
Pair<PublicKey, String> key;
try {
key = EncryptUtil.parseArmoredPubkey(keySpec + " " + keyData + " " + keyHash + (keyName == null ? null : " " + keyName));
}
catch(IllegalArgumentException e) {
throw new RunException(e, "Ungültiger Schlüssel");
}
player.setPasswordHash(null);
player.setPubkey(key.first());
exec.logConsole(TextColor.GREEN + "Schlüssel für %s gesetzt", player.getUser());
}
}
}

View file

@ -115,6 +115,7 @@ import common.util.EncryptUtil;
import common.util.ExtMath;
import common.util.Facing;
import common.util.IntHashMap;
import common.util.Pair;
import common.util.PortalType;
import common.util.Position;
import common.util.Vec3i;
@ -172,7 +173,7 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer
private boolean admin;
private int ping;
private boolean deleted;
private String password;
private Pair<byte[], byte[]> password;
private PublicKey pubkey;
private boolean profiling;
@ -343,11 +344,11 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer
return this.isAdmin();
}
public void setPassword(String pass) {
this.password = pass;
public void setPasswordHash(Pair<byte[], byte[]> hash) {
this.password = hash;
}
public String getPassword() {
public Pair<byte[], byte[]> getPasswordHash() {
return this.password;
}
@ -628,8 +629,8 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer
public void readTags(TagObject tag) {
this.admin = tag.getBool("admin");
if(tag.hasString("password"))
this.password = tag.getString("password");
if(tag.hasByteArray("passwordHash") && tag.hasByteArray("passwordSalt"))
this.password = new Pair<byte[], byte[]>(tag.getByteArray("passwordHash"), tag.getByteArray("passwordSalt"));
if(tag.hasByteArray("pubkey"))
this.pubkey = EncryptUtil.decodePublicKey(tag.getByteArray("pubkey"));
this.selected = tag.getInt("selected");
@ -644,8 +645,10 @@ public class Player extends NetHandler implements ICrafting, Executor, IPlayer
public void writeTags(TagObject tag) {
if(this.admin)
tag.setBool("admin", this.admin);
if(this.password != null)
tag.setString("password", this.password);
if(this.password != null) {
tag.setByteArray("passwordHash", this.password.first());
tag.setByteArray("passwordSalt", this.password.second());
}
if(this.pubkey != null)
tag.setByteArray("pubkey", this.pubkey.getEncoded());
if(!this.characters.isEmpty()) {