Encrypt each packet with its own EAX cipher

TS3 derives a packet's key and nonce from its id, so resending a packet
encrypts it under the same pair again. BouncyCastle has since learned to
refuse that on a cipher that has already used the pair ("cannot reuse
nonce for EAX encryption"), which broke resends on the current version
the Android build uses. A fresh cipher per packet keeps them working.

Moves to bcprov-jdk18on 1.86, the maintained line, so every platform runs
the same BouncyCastle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 08:42:48 +00:00
co-authored by Claude Opus 5.5
parent 3625cc24cf
commit 5a5fb6baaf
3 changed files with 17 additions and 15 deletions
@@ -24,7 +24,7 @@ public class PacketTransformation {
public PacketTransformation(byte[] ivStruct, byte[] fakeMac) {
this.ivStruct = ivStruct;
this.cipher = new EAXBlockCipher(new AESEngine());
this.cipher = new EAXBlockCipher(AESEngine.newInstance());
this.fakeMac = fakeMac;
}
@@ -86,20 +86,18 @@ public class PacketTransformation {
int dataLen = packet.getBody().getSize();
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
byte[] result;
int len;
// TS3 derives key and nonce from the packet id, so a resend encrypts under the same pair
// again. BouncyCastle refuses that on a cipher that has seen it, hence one per packet.
EAXBlockCipher cipher = new EAXBlockCipher(AESEngine.newInstance());
cipher.init(true, cipherParameters);
synchronized (cipher) {
cipher.init(true, cipherParameters);
byte[] result = new byte[cipher.getOutputSize(dataLen)];
int len = cipher.processBytes(packetBuffer.array(), 0, dataLen, result, 0);
result = new byte[cipher.getOutputSize(dataLen)];
len = cipher.processBytes(packetBuffer.array(), 0, dataLen, result, 0);
try {
len += cipher.doFinal(result, len);
} catch (InvalidCipherTextException e) {
throw new RuntimeException(e);
}
try {
len += cipher.doFinal(result, len);
} catch (InvalidCipherTextException e) {
throw new RuntimeException(e);
}
ByteBuffer outputBuffer = ByteBuffer.allocate(MAC_LEN + headerWithoutMac.length + (len - MAC_LEN));
@@ -53,6 +53,10 @@ public class EncryptionTest extends TestCase {
byte[] packetBytes = transformation.encrypt(packet).array();
transformation.decrypt(header, ByteBuffer.wrap(packetBytes), packet.getBody().getSize());
// A resend encrypts the same packet again, under the same key and nonce.
byte[] resent = transformation.encrypt(packet).array();
assertTrue(java.util.Arrays.equals(packetBytes, resent));
}
}