Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Randomly generate Encrypt Seed and Key #2341

Merged
merged 7 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/main/java/emu/grasscutter/config/ConfigContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,11 @@ public class ConfigContainer {
* Lua script require system if performance is a concern.
* Version 12 - 'http.startImmediately' was added to control whether the
* HTTP server should start immediately.
* Version 13 - 'game.useUniquePacketKey' was added to control whether the
* encryption key used for packets is a constant or randomly generated.
*/
private static int version() {
return 12;
return 13;
}

/**
Expand Down Expand Up @@ -169,6 +171,9 @@ public static class Game {
/* This is the port used in the default region. */
public int accessPort = 0;

/* Enabling this will generate a unique packet encryption key for each player. */
public boolean useUniquePacketKey = true;

/* Entities within a certain range will be loaded for the player */
public int loadEntitiesForPlayerRange = 300;
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/emu/grasscutter/game/world/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
import emu.grasscutter.utils.objects.KahnsSort;
import emu.grasscutter.utils.algorithms.KahnsSort;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
Expand Down
8 changes: 1 addition & 7 deletions src/main/java/emu/grasscutter/net/packet/BasePacket.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,7 @@ public byte[] build() {
this.writeBytes(baos, data);
this.writeUint16(baos, const2);

byte[] packet = baos.toByteArray();

if (this.shouldEncrypt) {
Crypto.xor(packet, this.useDispatchKey() ? Crypto.DISPATCH_KEY : Crypto.ENCRYPT_KEY);
}

return packet;
return baos.toByteArray();
}

public void writeUint16(ByteArrayOutputStream baos, int i) {
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/emu/grasscutter/server/game/GameSession.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ public class GameSession implements GameSessionManager.KcpChannel {
@Getter @Setter private Account account;
@Getter private Player player;

@Getter private long encryptSeed = Crypto.ENCRYPT_SEED;
private byte[] encryptKey = Crypto.ENCRYPT_KEY;

@Setter private boolean useSecretKey;
@Getter @Setter private SessionState state;

Expand All @@ -34,6 +37,11 @@ public GameSession(GameServer server) {
this.server = server;
this.state = SessionState.WAITING_FOR_TOKEN;
this.lastPingTime = System.currentTimeMillis();

if (GAME_INFO.useUniquePacketKey) {
this.encryptKey = new byte[4096];
this.encryptSeed = Crypto.generateEncryptKeyAndSeed(this.encryptKey);
}
}

public GameServer getServer() {
Expand Down Expand Up @@ -133,7 +141,12 @@ public void send(BasePacket packet) {
event.call();
if (!event.isCanceled()) { // If event is not cancelled, continue.
try {
tunnel.writeData(event.getPacket().build());
packet = event.getPacket();
var bytes = packet.build();
if (packet.shouldEncrypt) {
Crypto.xor(bytes, packet.useDispatchKey() ? Crypto.DISPATCH_KEY : this.encryptKey);
}
tunnel.writeData(bytes);
} catch (Exception ignored) {
Grasscutter.getLogger().debug("Unable to send packet to client.");
}
Expand All @@ -149,7 +162,7 @@ public void onConnected(GameSessionManager.KcpTunnel tunnel) {
@Override
public void handleReceive(byte[] bytes) {
// Decrypt and turn back into a packet
Crypto.xor(bytes, useSecretKey() ? Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
Crypto.xor(bytes, useSecretKey() ? this.encryptKey : Crypto.DISPATCH_KEY);
ByteBuf packet = Unpooled.wrappedBuffer(bytes);

// Log
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,32 +111,33 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex

// Only >= 2.7.50 has this
if (req.getKeyId() > 0) {
var encryptSeed = session.getEncryptSeed();
try {
var cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, Crypto.CUR_SIGNING_KEY);

var client_seed_encrypted = Utils.base64Decode(req.getClientRandKey());
var client_seed = ByteBuffer.wrap(cipher.doFinal(client_seed_encrypted)).getLong();
var clientSeedEncrypted = Utils.base64Decode(req.getClientRandKey());
var clientSeed = ByteBuffer.wrap(cipher.doFinal(clientSeedEncrypted)).getLong();

Simplxss marked this conversation as resolved.
Show resolved Hide resolved
var seed_bytes =
ByteBuffer.wrap(new byte[8]).putLong(Crypto.ENCRYPT_SEED ^ client_seed).array();
var seedBytes =
ByteBuffer.wrap(new byte[8]).putLong(encryptSeed ^ clientSeed).array();

cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(req.getKeyId()));
var seed_encrypted = cipher.doFinal(seed_bytes);
var seedEncrypted = cipher.doFinal(seedBytes);

var privateSignature = Signature.getInstance("SHA256withRSA");
privateSignature.initSign(Crypto.CUR_SIGNING_KEY);
privateSignature.update(seed_bytes);
privateSignature.update(seedBytes);

session.send(
new PacketGetPlayerTokenRsp(
session,
Utils.base64Encode(seed_encrypted),
Utils.base64Encode(seedEncrypted),
Utils.base64Encode(privateSignature.sign())));
} catch (Exception ignored) {
// Only UA Patch users will have exception
var clientBytes = Utils.base64Decode(req.getClientRandKey());
var seed = ByteHelper.longToBytes(Crypto.ENCRYPT_SEED);
var seed = ByteHelper.longToBytes(encryptSeed);
Crypto.xor(clientBytes, seed);

var base64str = Utils.base64Encode(clientBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public PacketGetPlayerTokenRsp(GameSession session) {
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(session.getEncryptSeed())
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
.setPlatformType(3)
.setChannelId(1)
Expand Down Expand Up @@ -66,7 +66,7 @@ public PacketGetPlayerTokenRsp(
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(session.getEncryptSeed())
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
.setPlatformType(3)
.setChannelId(1)
Expand Down
38 changes: 30 additions & 8 deletions src/main/java/emu/grasscutter/utils/Crypto.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import emu.grasscutter.utils.algorithms.MersenneTwister64;

import java.io.ByteArrayOutputStream;
import java.nio.file.Path;
import java.security.*;
Expand Down Expand Up @@ -34,9 +36,9 @@ public static void loadKeys() {

try {
CUR_SIGNING_KEY =
KeyFactory.getInstance("RSA")
.generatePrivate(
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
KeyFactory.getInstance("RSA")
.generatePrivate(
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));

Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der");
for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
Expand All @@ -46,8 +48,8 @@ public static void loadKeys() {

if (m.matches()) {
var key =
KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));

EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
}
Expand All @@ -74,8 +76,28 @@ public static byte[] createSessionKey(int length) {
return bytes;
}

public static long generateEncryptKeyAndSeed(byte[] encryptKey) {
var encryptSeed = secureRandom.nextLong();
var mt = new MersenneTwister64();
mt.setSeed(encryptSeed);
mt.setSeed(mt.nextLong());
mt.nextLong();
for (int i = 0; i < 4096 >> 3; i++) {
var rand = mt.nextLong();
encryptKey[i << 3] = (byte) (rand >> 56);
encryptKey[(i << 3) + 1] = (byte) (rand >> 48);
encryptKey[(i << 3) + 2] = (byte) (rand >> 40);
encryptKey[(i << 3) + 3] = (byte) (rand >> 32);
encryptKey[(i << 3) + 4] = (byte) (rand >> 24);
encryptKey[(i << 3) + 5] = (byte) (rand >> 16);
encryptKey[(i << 3) + 6] = (byte) (rand >> 8);
encryptKey[(i << 3) + 7] = (byte) rand;
}
return encryptSeed;
}

public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo, String key_id)
throws Exception {
throws Exception {
if (key_id == null) {
throw new Exception("Key ID was not set");
}
Expand All @@ -93,8 +115,8 @@ public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo,

for (int i = 0; i < numChunks; i++) {
byte[] chunk =
Arrays.copyOfRange(
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength));
Arrays.copyOfRange(
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength));
byte[] encryptedChunk = cipher.doFinal(chunk);
encryptedRegionInfoStream.write(encryptedChunk);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package emu.grasscutter.utils.objects;
package emu.grasscutter.utils.algorithms;

import java.util.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package emu.grasscutter.utils.algorithms;

public final class MersenneTwister64 {
// Period parameters
private static final int N = 312;
private static final int M = 156;
private static final long MATRIX_A = 0xB5026F5AA96619E9L; // private static final * constant vector a
private static final long UPPER_MASK = 0xFFFFFFFF80000000L; // most significant w-r bits
private static final int LOWER_MASK = 0x7FFFFFFF; // least significant r bits

private final long[] mt = new long[N]; // the array for the state vector
private int mti; // mti == N+1 means mt[N] is not initialized

public synchronized void setSeed(long seed) {
mt[0] = seed;
for (mti = 1; mti < N; mti++) {
mt[mti] = (0x5851F42D4C957F2DL * (mt[mti - 1] ^ (mt[mti - 1] >>> 62)) + mti);
}
}

public synchronized long nextLong() {
int i;
long x;
final long[] mag01 = {0x0L, MATRIX_A};

if (mti >= N) { // generate N words at one time
if (mti == N + 1) {
setSeed(5489L);
}

for (i = 0; i < N - M; i++) {
x = (mt[i] & UPPER_MASK) | (mt[i + 1] & LOWER_MASK);
mt[i] = mt[i + M] ^ (x >>> 1) ^ mag01[(int) (x & 0x1)];
}
for (; i < N - 1; i++) {
x = (mt[i] & UPPER_MASK) | (mt[i + 1] & LOWER_MASK);
mt[i] = mt[i + (M - N)] ^ (x >>> 1) ^ mag01[(int) (x & 0x1)];
}
x = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
mt[N - 1] = mt[M - 1] ^ (x >>> 1) ^ mag01[(int) (x & 0x1)];

mti = 0;
}

x = mt[mti++];
x ^= (x >>> 29) & 0x5555555555555555L;
x ^= (x << 17) & 0x71D67FFFEDA60000L;
x ^= (x << 37) & 0xFFF7EEE000000000L;
x ^= (x >>> 43);

return x;
}
}