Compare commits
15 Commits
22a7d78152
...
main
Author | SHA1 | Date | |
---|---|---|---|
b5d74c5dad
|
|||
f756838fab
|
|||
c9c52f2d22
|
|||
63c548a8f1 | |||
3f370438c1 | |||
735d533309 | |||
ec7a70a3cf | |||
2c9f3d0a6e | |||
963640810d | |||
5918002c97 | |||
e59f16c8fb | |||
bd2184006d | |||
1c0dd64566 | |||
1b21099378 | |||
250b36f9ee |
16
build.gradle
16
build.gradle
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'fabric-loom' version '1.6-SNAPSHOT'
|
||||
id 'fabric-loom' version '1.11-SNAPSHOT'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
@@ -12,9 +12,18 @@ repositories {
|
||||
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
|
||||
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
|
||||
// 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 {
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1'
|
||||
// To change the versions see the gradle.properties file
|
||||
minecraft "com.mojang:minecraft:${project.minecraft_version}"
|
||||
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.
|
||||
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 {
|
||||
@@ -33,6 +44,9 @@ processResources {
|
||||
filesMatching("fabric.mod.json") {
|
||||
expand "version": project.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
|
||||
}
|
||||
}
|
||||
|
@@ -1,17 +1,15 @@
|
||||
# Done to increase the memory available to gradle.
|
||||
org.gradle.jvmargs=-Xmx1G
|
||||
|
||||
# Fabric Properties
|
||||
# check these on https://modmuss50.me/fabric.html
|
||||
minecraft_version=1.20.4
|
||||
yarn_mappings=1.20.4+build.3
|
||||
loader_version=0.15.9
|
||||
|
||||
minecraft_version=1.21.8
|
||||
yarn_mappings=1.21.8+build.1
|
||||
loader_version=0.16.14
|
||||
# Fabric API
|
||||
fabric_version=0.129.0+1.21.8
|
||||
# Mod Properties
|
||||
mod_version = 1.0
|
||||
mod_version=0.5.4
|
||||
maven_group=systems.brn
|
||||
archives_base_name=chatencryptor
|
||||
|
||||
# Dependencies
|
||||
# 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
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1 +1 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||
|
24
src/main/java/systems/brn/chatencryptor/ChatCoder.java
Normal file
24
src/main/java/systems/brn/chatencryptor/ChatCoder.java
Normal 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;
|
||||
}
|
||||
}
|
131
src/main/java/systems/brn/chatencryptor/Config.java
Normal file
131
src/main/java/systems/brn/chatencryptor/Config.java
Normal 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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package systems.brn.chatencryptor;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.brigadier.Message;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
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.TranslatableTextContent;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
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.util.Arrays;
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
TranslatableTextContent content = (TranslatableTextContent) message.getContent();
|
||||
String message_content = content.getArg(1).getString();
|
||||
String player_name = content.getArg(0).getString();
|
||||
if(message_content.startsWith("®") && message_content.endsWith("®")){
|
||||
try {
|
||||
String strippedMessage = message_content.replace("®", "");
|
||||
byte[] decodedMessage = strippedMessage.getBytes(java.nio.charset.StandardCharsets.UTF_16);
|
||||
byte[] unpaddedMessage = Arrays.copyOfRange(decodedMessage, 2, decodedMessage.length);
|
||||
Cipher decryptingCipher = Cipher.getInstance("RSA");
|
||||
decryptingCipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
decryptingCipher.update(unpaddedMessage);
|
||||
String decryptedMessage = new String(decryptingCipher.doFinal());
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, Config.HANDLER.instance().getRawKey(), Config.HANDLER.instance().getRawIv());
|
||||
String strippedMessage = message_content.substring(1, message_content.length() - 1);
|
||||
byte[] decodedMessage = ChatCoder.decodeFromBmp(strippedMessage);
|
||||
cipher.update(decodedMessage);
|
||||
String decryptedMessage = new String(cipher.doFinal());
|
||||
String outputMessage = "{" + player_name + "} " + decryptedMessage;
|
||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of(outputMessage));
|
||||
return false;
|
||||
}
|
||||
catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException |
|
||||
catch (IllegalBlockSizeException | BadPaddingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException |
|
||||
InvalidKeyException e){
|
||||
MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(Text.of((Message) e));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -65,27 +54,35 @@ public class SecureChat implements ClientModInitializer {
|
||||
}
|
||||
|
||||
private String encryptChatMessage(String message) {
|
||||
if(Config.HANDLER.instance().isEnabled() ^ isShiftPressed()){
|
||||
String encodedMessage;
|
||||
try {
|
||||
Cipher encryptingCipher = Cipher.getInstance("RSA");
|
||||
encryptingCipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
encryptingCipher.update(message.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] encryptedMessage = encryptingCipher.doFinal();
|
||||
encodedMessage = new String(encryptedMessage, java.nio.charset.StandardCharsets.UTF_16);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchPaddingException |
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, Config.HANDLER.instance().getRawKey(), Config.HANDLER.instance().getRawIv());
|
||||
cipher.update(message.getBytes(StandardCharsets.UTF_8));
|
||||
byte[] encryptedMessage = cipher.doFinal();
|
||||
encodedMessage = ChatCoder.encodeToBmp(encryptedMessage);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException |
|
||||
NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
return "";
|
||||
}
|
||||
return '®' + encodedMessage + '®';
|
||||
}
|
||||
else {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
ClientLifecycleEvents.CLIENT_STARTED.register(client -> {
|
||||
// Register event listener for ClientTickEvents.END_CLIENT_TICK
|
||||
initKeys();
|
||||
Config.HANDLER.load();
|
||||
ClientReceiveMessageEvents.ALLOW_CHAT.register(this::decryptChatMessage);
|
||||
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;
|
||||
}
|
||||
}
|
@@ -17,11 +17,16 @@
|
||||
"entrypoints": {
|
||||
"client": [
|
||||
"systems.brn.chatencryptor.SecureChat"
|
||||
],
|
||||
"modmenu": [
|
||||
"systems.brn.chatencryptor.ModMenuIntegration"
|
||||
]
|
||||
},
|
||||
"depends": {
|
||||
"fabricloader": ">=${loader_version}",
|
||||
"fabric": "*",
|
||||
"minecraft": "${minecraft_version}"
|
||||
"fabric": ">=${fabric_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 |
Reference in New Issue
Block a user