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:
+11
-13
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user