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:
@@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk18on</artifactId>
|
||||||
<version>1.67</version>
|
<version>1.86</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
+6
-8
@@ -24,7 +24,7 @@ public class PacketTransformation {
|
|||||||
|
|
||||||
public PacketTransformation(byte[] ivStruct, byte[] fakeMac) {
|
public PacketTransformation(byte[] ivStruct, byte[] fakeMac) {
|
||||||
this.ivStruct = ivStruct;
|
this.ivStruct = ivStruct;
|
||||||
this.cipher = new EAXBlockCipher(new AESEngine());
|
this.cipher = new EAXBlockCipher(AESEngine.newInstance());
|
||||||
this.fakeMac = fakeMac;
|
this.fakeMac = fakeMac;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,21 +86,19 @@ public class PacketTransformation {
|
|||||||
int dataLen = packet.getBody().getSize();
|
int dataLen = packet.getBody().getSize();
|
||||||
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
|
ByteBuffer packetBuffer = packet.writeBody(ByteBuffer.allocate(dataLen).order(ByteOrder.BIG_ENDIAN));
|
||||||
|
|
||||||
byte[] result;
|
// TS3 derives key and nonce from the packet id, so a resend encrypts under the same pair
|
||||||
int len;
|
// again. BouncyCastle refuses that on a cipher that has seen it, hence one per packet.
|
||||||
|
EAXBlockCipher cipher = new EAXBlockCipher(AESEngine.newInstance());
|
||||||
synchronized (cipher) {
|
|
||||||
cipher.init(true, cipherParameters);
|
cipher.init(true, cipherParameters);
|
||||||
|
|
||||||
result = new byte[cipher.getOutputSize(dataLen)];
|
byte[] result = new byte[cipher.getOutputSize(dataLen)];
|
||||||
len = cipher.processBytes(packetBuffer.array(), 0, dataLen, result, 0);
|
int len = cipher.processBytes(packetBuffer.array(), 0, dataLen, result, 0);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
len += cipher.doFinal(result, len);
|
len += cipher.doFinal(result, len);
|
||||||
} catch (InvalidCipherTextException e) {
|
} catch (InvalidCipherTextException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer outputBuffer = ByteBuffer.allocate(MAC_LEN + headerWithoutMac.length + (len - MAC_LEN));
|
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();
|
byte[] packetBytes = transformation.encrypt(packet).array();
|
||||||
transformation.decrypt(header, ByteBuffer.wrap(packetBytes), packet.getBody().getSize());
|
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