mirror of
https://github.com/Melledy/Grasscutter.git
synced 2024-11-25 20:56:28 +00:00
Merge pull request #2341
* enableRandomEncryptSeed * update config version * Merge branch 'Grasscutters:development' into random-seed * make the codes more beautiful * move KahnsSort to algorithms * rename variables * Merge branch 'development' into random-seed
This commit is contained in:
parent
b8f7aea168
commit
fb0c2dbc84
@ -33,9 +33,11 @@ public class ConfigContainer {
|
|||||||
* Lua script require system if performance is a concern.
|
* Lua script require system if performance is a concern.
|
||||||
* Version 12 - 'http.startImmediately' was added to control whether the
|
* Version 12 - 'http.startImmediately' was added to control whether the
|
||||||
* HTTP server should start immediately.
|
* 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() {
|
private static int version() {
|
||||||
return 12;
|
return 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -169,6 +171,9 @@ public class ConfigContainer {
|
|||||||
/* This is the port used in the default region. */
|
/* This is the port used in the default region. */
|
||||||
public int accessPort = 0;
|
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 */
|
/* Entities within a certain range will be loaded for the player */
|
||||||
public int loadEntitiesForPlayerRange = 300;
|
public int loadEntitiesForPlayerRange = 300;
|
||||||
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */
|
/* Start in 'unstable-quests', Lua scripts will be enabled by default. */
|
||||||
|
@ -39,7 +39,7 @@ import emu.grasscutter.server.event.entity.EntityCreationEvent;
|
|||||||
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
import emu.grasscutter.server.event.player.PlayerTeleportEvent;
|
||||||
import emu.grasscutter.server.packet.send.*;
|
import emu.grasscutter.server.packet.send.*;
|
||||||
import emu.grasscutter.server.scheduler.ServerTaskScheduler;
|
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 it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
@ -108,13 +108,7 @@ public class BasePacket {
|
|||||||
this.writeBytes(baos, data);
|
this.writeBytes(baos, data);
|
||||||
this.writeUint16(baos, const2);
|
this.writeUint16(baos, const2);
|
||||||
|
|
||||||
byte[] packet = baos.toByteArray();
|
return baos.toByteArray();
|
||||||
|
|
||||||
if (this.shouldEncrypt) {
|
|
||||||
Crypto.xor(packet, this.useDispatchKey() ? Crypto.DISPATCH_KEY : Crypto.ENCRYPT_KEY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return packet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeUint16(ByteArrayOutputStream baos, int i) {
|
public void writeUint16(ByteArrayOutputStream baos, int i) {
|
||||||
|
@ -23,6 +23,9 @@ public class GameSession implements GameSessionManager.KcpChannel {
|
|||||||
@Getter @Setter private Account account;
|
@Getter @Setter private Account account;
|
||||||
@Getter private Player player;
|
@Getter private Player player;
|
||||||
|
|
||||||
|
@Getter private long encryptSeed = Crypto.ENCRYPT_SEED;
|
||||||
|
private byte[] encryptKey = Crypto.ENCRYPT_KEY;
|
||||||
|
|
||||||
@Setter private boolean useSecretKey;
|
@Setter private boolean useSecretKey;
|
||||||
@Getter @Setter private SessionState state;
|
@Getter @Setter private SessionState state;
|
||||||
|
|
||||||
@ -34,6 +37,11 @@ public class GameSession implements GameSessionManager.KcpChannel {
|
|||||||
this.server = server;
|
this.server = server;
|
||||||
this.state = SessionState.WAITING_FOR_TOKEN;
|
this.state = SessionState.WAITING_FOR_TOKEN;
|
||||||
this.lastPingTime = System.currentTimeMillis();
|
this.lastPingTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (GAME_INFO.useUniquePacketKey) {
|
||||||
|
this.encryptKey = new byte[4096];
|
||||||
|
this.encryptSeed = Crypto.generateEncryptKeyAndSeed(this.encryptKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameServer getServer() {
|
public GameServer getServer() {
|
||||||
@ -133,7 +141,12 @@ public class GameSession implements GameSessionManager.KcpChannel {
|
|||||||
event.call();
|
event.call();
|
||||||
if (!event.isCanceled()) { // If event is not cancelled, continue.
|
if (!event.isCanceled()) { // If event is not cancelled, continue.
|
||||||
try {
|
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) {
|
} catch (Exception ignored) {
|
||||||
Grasscutter.getLogger().debug("Unable to send packet to client.");
|
Grasscutter.getLogger().debug("Unable to send packet to client.");
|
||||||
}
|
}
|
||||||
@ -149,7 +162,7 @@ public class GameSession implements GameSessionManager.KcpChannel {
|
|||||||
@Override
|
@Override
|
||||||
public void handleReceive(byte[] bytes) {
|
public void handleReceive(byte[] bytes) {
|
||||||
// Decrypt and turn back into a packet
|
// 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);
|
ByteBuf packet = Unpooled.wrappedBuffer(bytes);
|
||||||
|
|
||||||
// Log
|
// Log
|
||||||
|
@ -111,32 +111,33 @@ public class HandlerGetPlayerTokenReq extends PacketHandler {
|
|||||||
|
|
||||||
// Only >= 2.7.50 has this
|
// Only >= 2.7.50 has this
|
||||||
if (req.getKeyId() > 0) {
|
if (req.getKeyId() > 0) {
|
||||||
|
var encryptSeed = session.getEncryptSeed();
|
||||||
try {
|
try {
|
||||||
var cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
var cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||||
cipher.init(Cipher.DECRYPT_MODE, Crypto.CUR_SIGNING_KEY);
|
cipher.init(Cipher.DECRYPT_MODE, Crypto.CUR_SIGNING_KEY);
|
||||||
|
|
||||||
var client_seed_encrypted = Utils.base64Decode(req.getClientRandKey());
|
var clientSeedEncrypted = Utils.base64Decode(req.getClientRandKey());
|
||||||
var client_seed = ByteBuffer.wrap(cipher.doFinal(client_seed_encrypted)).getLong();
|
var clientSeed = ByteBuffer.wrap(cipher.doFinal(clientSeedEncrypted)).getLong();
|
||||||
|
|
||||||
var seed_bytes =
|
var seedBytes =
|
||||||
ByteBuffer.wrap(new byte[8]).putLong(Crypto.ENCRYPT_SEED ^ client_seed).array();
|
ByteBuffer.wrap(new byte[8]).putLong(encryptSeed ^ clientSeed).array();
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, Crypto.EncryptionKeys.get(req.getKeyId()));
|
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");
|
var privateSignature = Signature.getInstance("SHA256withRSA");
|
||||||
privateSignature.initSign(Crypto.CUR_SIGNING_KEY);
|
privateSignature.initSign(Crypto.CUR_SIGNING_KEY);
|
||||||
privateSignature.update(seed_bytes);
|
privateSignature.update(seedBytes);
|
||||||
|
|
||||||
session.send(
|
session.send(
|
||||||
new PacketGetPlayerTokenRsp(
|
new PacketGetPlayerTokenRsp(
|
||||||
session,
|
session,
|
||||||
Utils.base64Encode(seed_encrypted),
|
Utils.base64Encode(seedEncrypted),
|
||||||
Utils.base64Encode(privateSignature.sign())));
|
Utils.base64Encode(privateSignature.sign())));
|
||||||
} catch (Exception ignored) {
|
} catch (Exception ignored) {
|
||||||
// Only UA Patch users will have exception
|
// Only UA Patch users will have exception
|
||||||
var clientBytes = Utils.base64Decode(req.getClientRandKey());
|
var clientBytes = Utils.base64Decode(req.getClientRandKey());
|
||||||
var seed = ByteHelper.longToBytes(Crypto.ENCRYPT_SEED);
|
var seed = ByteHelper.longToBytes(encryptSeed);
|
||||||
Crypto.xor(clientBytes, seed);
|
Crypto.xor(clientBytes, seed);
|
||||||
|
|
||||||
var base64str = Utils.base64Encode(clientBytes);
|
var base64str = Utils.base64Encode(clientBytes);
|
||||||
|
@ -20,7 +20,7 @@ public class PacketGetPlayerTokenRsp extends BasePacket {
|
|||||||
.setAccountType(1)
|
.setAccountType(1)
|
||||||
.setIsProficientPlayer(
|
.setIsProficientPlayer(
|
||||||
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
|
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
|
||||||
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
|
.setSecretKeySeed(session.getEncryptSeed())
|
||||||
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
|
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
|
||||||
.setPlatformType(3)
|
.setPlatformType(3)
|
||||||
.setChannelId(1)
|
.setChannelId(1)
|
||||||
@ -66,7 +66,7 @@ public class PacketGetPlayerTokenRsp extends BasePacket {
|
|||||||
.setAccountType(1)
|
.setAccountType(1)
|
||||||
.setIsProficientPlayer(
|
.setIsProficientPlayer(
|
||||||
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
|
session.getPlayer().getAvatars().getAvatarCount() > 0) // Not sure where this goes
|
||||||
.setSecretKeySeed(Crypto.ENCRYPT_SEED)
|
.setSecretKeySeed(session.getEncryptSeed())
|
||||||
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
|
.setSecurityCmdBuffer(ByteString.copyFrom(Crypto.ENCRYPT_SEED_BUFFER))
|
||||||
.setPlatformType(3)
|
.setPlatformType(3)
|
||||||
.setChannelId(1)
|
.setChannelId(1)
|
||||||
|
@ -2,6 +2,8 @@ package emu.grasscutter.utils;
|
|||||||
|
|
||||||
import emu.grasscutter.Grasscutter;
|
import emu.grasscutter.Grasscutter;
|
||||||
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
|
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
|
||||||
|
import emu.grasscutter.utils.algorithms.MersenneTwister64;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
@ -34,9 +36,9 @@ public final class Crypto {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
CUR_SIGNING_KEY =
|
CUR_SIGNING_KEY =
|
||||||
KeyFactory.getInstance("RSA")
|
KeyFactory.getInstance("RSA")
|
||||||
.generatePrivate(
|
.generatePrivate(
|
||||||
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
|
new PKCS8EncodedKeySpec(FileUtils.readResource("/keys/SigningKey.der")));
|
||||||
|
|
||||||
Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der");
|
Pattern pattern = Pattern.compile("([0-9]*)_Pub\\.der");
|
||||||
for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
|
for (Path path : FileUtils.getPathsFromResource("/keys/game_keys")) {
|
||||||
@ -46,8 +48,8 @@ public final class Crypto {
|
|||||||
|
|
||||||
if (m.matches()) {
|
if (m.matches()) {
|
||||||
var key =
|
var key =
|
||||||
KeyFactory.getInstance("RSA")
|
KeyFactory.getInstance("RSA")
|
||||||
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
|
.generatePublic(new X509EncodedKeySpec(FileUtils.read(path)));
|
||||||
|
|
||||||
EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
|
EncryptionKeys.put(Integer.valueOf(m.group(1)), key);
|
||||||
}
|
}
|
||||||
@ -74,8 +76,28 @@ public final class Crypto {
|
|||||||
return bytes;
|
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)
|
public static QueryCurRegionRspJson encryptAndSignRegionData(byte[] regionInfo, String key_id)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
if (key_id == null) {
|
if (key_id == null) {
|
||||||
throw new Exception("Key ID was not set");
|
throw new Exception("Key ID was not set");
|
||||||
}
|
}
|
||||||
@ -93,8 +115,8 @@ public final class Crypto {
|
|||||||
|
|
||||||
for (int i = 0; i < numChunks; i++) {
|
for (int i = 0; i < numChunks; i++) {
|
||||||
byte[] chunk =
|
byte[] chunk =
|
||||||
Arrays.copyOfRange(
|
Arrays.copyOfRange(
|
||||||
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength));
|
regionInfo, i * chunkSize, Math.min((i + 1) * chunkSize, regionInfoLength));
|
||||||
byte[] encryptedChunk = cipher.doFinal(chunk);
|
byte[] encryptedChunk = cipher.doFinal(chunk);
|
||||||
encryptedRegionInfoStream.write(encryptedChunk);
|
encryptedRegionInfoStream.write(encryptedChunk);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package emu.grasscutter.utils.objects;
|
package emu.grasscutter.utils.algorithms;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user