Compare commits

..

15 Commits

Author SHA1 Message Date
b5d74c5dad Update to 1.21.8 2025-07-20 10:43:42 +02:00
f756838fab Update to 1.21.5 2025-04-16 21:10:44 +02:00
c9c52f2d22 Update to 1.21.4 2024-12-05 14:41:18 +01:00
63c548a8f1 Update 2024-08-14 18:46:44 +02:00
3f370438c1 Update to 1.21 2024-06-14 14:53:13 +02:00
735d533309 Update to 1.21 2024-06-14 14:52:40 +02:00
ec7a70a3cf Update to 1.21 2024-06-14 14:43:03 +02:00
2c9f3d0a6e Change icon 2024-05-25 14:32:59 +02:00
963640810d Update to 1.20.6 2024-05-05 08:43:49 +02:00
5918002c97 Fix 2024-04-26 19:26:10 +02:00
e59f16c8fb Fix 2024-04-08 21:28:32 +02:00
bd2184006d Unflip ciphers, load on chat message 2024-04-08 12:37:49 +02:00
1c0dd64566 Remove redundant cipher 2024-04-08 12:34:10 +02:00
1b21099378 Unswap key and Iv 2024-04-08 10:51:23 +02:00
250b36f9ee Test 2024-04-08 07:21:41 +02:00
9 changed files with 239 additions and 55 deletions

View File

@@ -1,5 +1,5 @@
plugins { plugins {
id 'fabric-loom' version '1.6-SNAPSHOT' id 'fabric-loom' version '1.11-SNAPSHOT'
id 'maven-publish' id 'maven-publish'
} }
@@ -12,9 +12,18 @@ repositories {
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically. // Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html // See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories. // for more information about repositories.
maven {
name 'Xander Maven'
url 'https://maven.isxander.dev/releases'
}
maven {
name 'TerraformersMC'
url 'https://maven.terraformersmc.com/releases'
}
} }
dependencies { dependencies {
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
// To change the versions see the gradle.properties file // To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}" minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
@@ -22,6 +31,8 @@ dependencies {
// Fabric API. This is technically optional, but you probably want it anyway. // Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "dev.isxander:yet-another-config-lib:${project.yacl_version}"
modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}"
} }
processResources { processResources {
@@ -33,6 +44,9 @@ processResources {
filesMatching("fabric.mod.json") { filesMatching("fabric.mod.json") {
expand "version": project.version, expand "version": project.version,
"minecraft_version": project.minecraft_version, "minecraft_version": project.minecraft_version,
"yacl_version": project.yacl_version,
"modmenu_version": project.modmenu_version,
"fabric_version": project.fabric_version,
"loader_version": project.loader_version "loader_version": project.loader_version
} }
} }

View File

@@ -1,17 +1,15 @@
# Done to increase the memory available to gradle. # Done to increase the memory available to gradle.
org.gradle.jvmargs=-Xmx1G org.gradle.jvmargs=-Xmx1G
minecraft_version=1.21.8
# Fabric Properties yarn_mappings=1.21.8+build.1
# check these on https://modmuss50.me/fabric.html loader_version=0.16.14
minecraft_version=1.20.4 # Fabric API
yarn_mappings=1.20.4+build.3 fabric_version=0.129.0+1.21.8
loader_version=0.15.9
# Mod Properties # Mod Properties
mod_version = 1.0 mod_version=0.5.4
maven_group = systems.brn maven_group=systems.brn
archives_base_name = chatencryptor archives_base_name=chatencryptor
# Dependencies # Dependencies
# check this on https://modmuss50.me/fabric.html # check this on https://modmuss50.me/fabric.html
fabric_version=0.96.11+1.20.4 yacl_version=3.7.1+1.21.6-fabric
modmenu_version=15.0.0-beta.3

View File

@@ -1 +1 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip

View File

@@ -0,0 +1,24 @@
package systems.brn.chatencryptor;
public class ChatCoder {
// Encode byte array to BMP characters
public static String encodeToBmp(byte[] byteArray) {
StringBuilder bmpText = new StringBuilder();
for (int i = 0; i < byteArray.length; i += 2) {
char bmpChar = (char) ((byteArray[i] & 0xFF) << 8 | (byteArray[i + 1] & 0xFF));
bmpText.append(bmpChar);
}
return bmpText.toString();
}
// Decode BMP characters to byte array
public static byte[] decodeFromBmp(String bmpString) {
byte[] byteArray = new byte[bmpString.length() * 2];
for (int i = 0; i < bmpString.length(); i++) {
char bmpChar = bmpString.charAt(i);
byteArray[i * 2] = (byte) ((bmpChar >> 8) & 0xFF);
byteArray[i * 2 + 1] = (byte) (bmpChar & 0xFF);
}
return byteArray;
}
}

View File

@@ -0,0 +1,131 @@
package systems.brn.chatencryptor;
import com.google.gson.GsonBuilder;
import dev.isxander.yacl3.api.*;
import dev.isxander.yacl3.api.controller.StringControllerBuilder;
import dev.isxander.yacl3.api.controller.TickBoxControllerBuilder;
import dev.isxander.yacl3.config.v2.api.ConfigClassHandler;
import dev.isxander.yacl3.config.v2.api.SerialEntry;
import dev.isxander.yacl3.config.v2.api.serializer.GsonConfigSerializerBuilder;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class Config {
public static ConfigClassHandler<Config> HANDLER = ConfigClassHandler.createBuilder(Config.class)
.id(Identifier.of("securechat", "securechat_config"))
.serializer(config -> GsonConfigSerializerBuilder.create(config)
.setPath(FabricLoader.getInstance().getConfigDir().resolve("securechat.json"))
.appendGsonBuilder(GsonBuilder::setPrettyPrinting) // not needed, pretty print by default
.build())
.build();
@SerialEntry
public boolean enabled = false;
@SerialEntry
public String SecretKey = "";
@SerialEntry
public String Iv = "";
public IvParameterSpec getRawIv() {
byte[] byteIv = Base64.getDecoder().decode(HANDLER.instance().Iv);
return new IvParameterSpec(byteIv);
}
public SecretKey getRawKey() {
byte[] binary_key = Base64.getDecoder().decode(HANDLER.instance().SecretKey);
// Convert byte array back to SecretKey
return new SecretKeySpec(binary_key, 0, binary_key.length, "AES");
}
public Boolean isEnabled() {
return HANDLER.instance().enabled;
}
private static IvParameterSpec generateIv() {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
return new IvParameterSpec(iv);
}
private String getDefaultIv() {
IvParameterSpec generatedIv = generateIv();
byte [] bytesIv = generatedIv.getIV();
return Base64.getEncoder().encodeToString(bytesIv);
}
private SecretKey generateKey() {
KeyGenerator kg;
try {
kg = KeyGenerator.getInstance("AES");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
kg.init(256);
return kg.generateKey();
}
private String getDefaultKey() {
SecretKey generatedKey = generateKey();
byte[] encodedKey = generatedKey.getEncoded();
return Base64.getEncoder().encodeToString(encodedKey);
}
private void setEnabled(Boolean newEnabled){
HANDLER.instance().enabled = newEnabled;
HANDLER.save();
}
private void setIv(String newIv){
HANDLER.instance().Iv = newIv;
HANDLER.save();
}
private void setSecretKey(String newSecretKey){
HANDLER.instance().SecretKey = newSecretKey;
HANDLER.save();
}
public Screen makeConfig(Screen parentScreen) {
HANDLER.load();
return YetAnotherConfigLib.createBuilder()
.title(Text.literal("SecureChat Config."))
.category(ConfigCategory.createBuilder()
.name(Text.literal("General"))
.tooltip(Text.literal("Here you can set some generic settings."))
.group(OptionGroup.createBuilder()
.name(Text.literal("General"))
.description(OptionDescription.of(Text.literal("Here you can set some generic settings.")))
.option(Option.<Boolean>createBuilder()
.name(Text.literal("Enabled"))
.description(OptionDescription.of(Text.literal("This will enable the encryption. You can toggle it by sending with shift+enter for that message.")))
.binding(false, () -> HANDLER.instance().enabled, this::setEnabled)
.controller(TickBoxControllerBuilder::create)
.build())
.option(Option.<String>createBuilder()
.name(Text.literal("Initialization vector"))
.description(OptionDescription.of(Text.literal("This be the key that will be used.")))
.binding(getDefaultIv(), () -> HANDLER.instance().Iv, this::setIv)
.controller(StringControllerBuilder::create)
.build())
.option(Option.<String>createBuilder()
.name(Text.literal("Secret key"))
.description(OptionDescription.of(Text.literal("This be the initialization vector that will be used.")))
.binding(getDefaultKey(), () -> HANDLER.instance().SecretKey, this::setSecretKey)
.controller(StringControllerBuilder::create)
.build())
.build())
.build())
.build().generateScreen(parentScreen);
}
}

View File

@@ -0,0 +1,15 @@
package systems.brn.chatencryptor;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
public class ModMenuIntegration implements ModMenuApi {
@Override
public ConfigScreenFactory<?> getModConfigScreenFactory() {
return parent -> {
Config config = new Config();
Config.HANDLER.load();
return config.makeConfig(parent);
};
}
}

View File

@@ -1,6 +1,7 @@
package systems.brn.chatencryptor; package systems.brn.chatencryptor;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.brigadier.Message;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents; import net.fabricmc.fabric.api.client.message.v1.ClientReceiveMessageEvents;
@@ -11,53 +12,41 @@ import net.minecraft.network.message.SignedMessage;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.minecraft.text.TranslatableTextContent; import net.minecraft.text.TranslatableTextContent;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
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.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.*; import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant; import java.time.Instant;
import java.util.Arrays;
public class SecureChat implements ClientModInitializer { public class SecureChat implements ClientModInitializer {
private PublicKey publicKey;
private PrivateKey privateKey;
private void initKeys() {
KeyPairGenerator kpg;
try {
kpg = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
kpg.initialize(512);
KeyPair kp = kpg.genKeyPair();
publicKey = kp.getPublic();
privateKey = kp.getPrivate();
}
private boolean decryptChatMessage(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp) { private boolean decryptChatMessage(Text message, @Nullable SignedMessage signedMessage, @Nullable GameProfile sender, MessageType.Parameters params, Instant receptionTimestamp) {
TranslatableTextContent content = (TranslatableTextContent) message.getContent(); TranslatableTextContent content = (TranslatableTextContent) message.getContent();
String message_content = content.getArg(1).getString(); String message_content = content.getArg(1).getString();
String player_name = content.getArg(0).getString(); String player_name = content.getArg(0).getString();
if(message_content.startsWith("®") && message_content.endsWith("®")){ if(message_content.startsWith("®") && message_content.endsWith("®")){
try { try {
String strippedMessage = message_content.replace("®", ""); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] decodedMessage = strippedMessage.getBytes(java.nio.charset.StandardCharsets.UTF_16); cipher.init(Cipher.DECRYPT_MODE, Config.HANDLER.instance().getRawKey(), Config.HANDLER.instance().getRawIv());
byte[] unpaddedMessage = Arrays.copyOfRange(decodedMessage, 2, decodedMessage.length); String strippedMessage = message_content.substring(1, message_content.length() - 1);
Cipher decryptingCipher = Cipher.getInstance("RSA"); byte[] decodedMessage = ChatCoder.decodeFromBmp(strippedMessage);
decryptingCipher.init(Cipher.DECRYPT_MODE, privateKey); cipher.update(decodedMessage);
decryptingCipher.update(unpaddedMessage); String decryptedMessage = new String(cipher.doFinal());
String decryptedMessage = new String(decryptingCipher.doFinal());
String outputMessage = "{" + player_name + "} " + decryptedMessage; String outputMessage = "{" + player_name + "} " + decryptedMessage;
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of(outputMessage)); MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of(outputMessage));
return false; return false;
} }
catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException |
InvalidKeyException e){ InvalidKeyException e){
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of((Message) e));
return true; return true;
} }
} }
@@ -65,27 +54,35 @@ public class SecureChat implements ClientModInitializer {
} }
private String encryptChatMessage(String message) { private String encryptChatMessage(String message) {
String encodedMessage; if(Config.HANDLER.instance().isEnabled() ^ isShiftPressed()){
try { String encodedMessage;
Cipher encryptingCipher = Cipher.getInstance("RSA"); try {
encryptingCipher.init(Cipher.ENCRYPT_MODE, publicKey); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptingCipher.update(message.getBytes(StandardCharsets.UTF_8)); cipher.init(Cipher.ENCRYPT_MODE, Config.HANDLER.instance().getRawKey(), Config.HANDLER.instance().getRawIv());
byte[] encryptedMessage = encryptingCipher.doFinal(); cipher.update(message.getBytes(StandardCharsets.UTF_8));
encodedMessage = new String(encryptedMessage, java.nio.charset.StandardCharsets.UTF_16); byte[] encryptedMessage = cipher.doFinal();
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchPaddingException | encodedMessage = ChatCoder.encodeToBmp(encryptedMessage);
NoSuchAlgorithmException e) { } catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException |
throw new RuntimeException(e); NoSuchAlgorithmException e) {
return "";
}
return '®' + encodedMessage + '®';
}
else {
return message;
} }
return '®' + encodedMessage + '®';
} }
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
ClientLifecycleEvents.CLIENT_STARTED.register(client -> { ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
// Register event listener for ClientTickEvents.END_CLIENT_TICK // Register event listener for ClientTickEvents.END_CLIENT_TICK
initKeys(); Config.HANDLER.load();
ClientReceiveMessageEvents.ALLOW_CHAT.register(this::decryptChatMessage); ClientReceiveMessageEvents.ALLOW_CHAT.register(this::decryptChatMessage);
ClientSendMessageEvents.MODIFY_CHAT.register(this::encryptChatMessage); ClientSendMessageEvents.MODIFY_CHAT.register(this::encryptChatMessage);
}); });
} }
private static boolean isShiftPressed() {
return GLFW.glfwGetKey(MinecraftClient.getInstance().getWindow().getHandle(), GLFW.GLFW_KEY_LEFT_SHIFT) == GLFW.GLFW_PRESS;
}
} }

View File

@@ -17,11 +17,16 @@
"entrypoints": { "entrypoints": {
"client": [ "client": [
"systems.brn.chatencryptor.SecureChat" "systems.brn.chatencryptor.SecureChat"
],
"modmenu": [
"systems.brn.chatencryptor.ModMenuIntegration"
] ]
}, },
"depends": { "depends": {
"fabricloader": ">=${loader_version}", "fabricloader": ">=${loader_version}",
"fabric": "*", "fabric": ">=${fabric_version}",
"minecraft": "${minecraft_version}" "minecraft": ">=${minecraft_version}",
"yet_another_config_lib_v3": ">=${yacl_version}",
"modmenu": ">=${modmenu_version}"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB