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 2 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
6 changes: 5 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.enableRandomEncryptSeed' was added to control whether the
* encryption use random seed and key.
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
*/
private static int version() {
return 12;
return 13;
}

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

public boolean enableRandomEncryptSeed = true;
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
Simplxss marked this conversation as resolved.
Show resolved Hide resolved

/* 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
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
16 changes: 14 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;
private final byte[] encryptKey = new byte[4096];

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

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

if (GAME_INFO.enableRandomEncryptSeed) {
encryptSeed = Crypto.generateEncryptKeyAndSeed(encryptKey);
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
}
}

public GameServer getServer() {
Expand Down Expand Up @@ -133,7 +140,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 : GAME_INFO.enableRandomEncryptSeed ? encryptKey : Crypto.ENCRYPT_KEY);
}
tunnel.writeData(bytes);
} catch (Exception ignored) {
Grasscutter.getLogger().debug("Unable to send packet to client.");
}
Expand All @@ -149,7 +161,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() ? GAME_INFO.enableRandomEncryptSeed ? encryptKey : Crypto.ENCRYPT_KEY : Crypto.DISPATCH_KEY);
ByteBuf packet = Unpooled.wrappedBuffer(bytes);

// Log
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package emu.grasscutter.server.packet.recv;

import static emu.grasscutter.config.Configuration.ACCOUNT;
import static emu.grasscutter.config.Configuration.GAME_INFO;

import emu.grasscutter.*;
import emu.grasscutter.database.DatabaseHelper;
Expand Down Expand Up @@ -119,7 +120,7 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex
var client_seed = ByteBuffer.wrap(cipher.doFinal(client_seed_encrypted)).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();
ByteBuffer.wrap(new byte[8]).putLong((GAME_INFO.enableRandomEncryptSeed ? session.getEncryptSeed() : Crypto.ENCRYPT_SEED) ^ client_seed).array();

cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(req.getKeyId()));
var seed_encrypted = cipher.doFinal(seed_bytes);
Expand All @@ -136,7 +137,7 @@ public void handle(GameSession session, byte[] header, byte[] payload) throws Ex
} 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(GAME_INFO.enableRandomEncryptSeed ? session.getEncryptSeed() : Crypto.ENCRYPT_SEED);
Crypto.xor(clientBytes, seed);

var base64str = Utils.base64Encode(clientBytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Crypto;

import static emu.grasscutter.config.Configuration.GAME_INFO;

public class PacketGetPlayerTokenRsp extends BasePacket {

public PacketGetPlayerTokenRsp(GameSession session) {
Expand All @@ -20,7 +22,7 @@ public PacketGetPlayerTokenRsp(GameSession session) {
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(GAME_INFO.enableRandomEncryptSeed ? session.getEncryptSeed() : Crypto.ENCRYPT_SEED)
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
.setPlatformType(3)
.setChannelId(1)
Expand Down Expand Up @@ -66,7 +68,7 @@ public PacketGetPlayerTokenRsp(
.setAccountType(1)
.setIsProficientPlayer(
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
.setSecretKeySeed(GAME_INFO.enableRandomEncryptSeed ? session.getEncryptSeed() : Crypto.ENCRYPT_SEED)
.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.algorithm.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
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package emu.grasscutter.utils.algorithm;

public class MersenneTwister64 {
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
// 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
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private int mti; // mti==N+1 means mt[N] is not initialized
private int mti; // mti == N+1 means mt[N] is not initialized


synchronized public void setSeed(long seed) {
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
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
{
Simplxss marked this conversation as resolved.
Show resolved Hide resolved
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;
}
}