import de.maxhenkel.voicechat.voice.common.Secret; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Arrays; import java.util.UUID; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; public class ProtocolCryptoSelfTest { public static void main(String[] args) throws Exception { byte[] key = new byte[16]; for (int i = 0; i < key.length; i++) key[i] = (byte) (i * 13 + 7); UUID legacyKey = uuidFromBytes(key); Secret hybridSecret = Secret.fromBytes(key); byte[] message = "SVC dual-protocol crypto self-test".getBytes("UTF-8"); hybridSecret.setLegacyProtocol(true); for (int i = 0; i < 100; i++) { byte[] oldClientPacket = legacyEncrypt(legacyKey, message); if (!Arrays.equals(message, hybridSecret.decrypt(oldClientPacket))) { throw new AssertionError("Hybrid server failed to decrypt a 2.5 CBC packet"); } byte[] hybridServerPacket = hybridSecret.encrypt(message); if (!Arrays.equals(message, legacyDecrypt(legacyKey, hybridServerPacket))) { throw new AssertionError("2.5 client failed to decrypt a hybrid server CBC packet"); } } hybridSecret.setLegacyProtocol(false); for (int i = 0; i < 100; i++) { byte[] packet = hybridSecret.encrypt(message); if (!Arrays.equals(message, hybridSecret.decrypt(packet))) { throw new AssertionError("Hybrid server AES-GCM round-trip failed"); } } System.out.println("PASS: 100 CBC client/server interop cycles and 100 GCM round-trips"); } private static UUID uuidFromBytes(byte[] bytes) { ByteBuffer buffer = ByteBuffer.wrap(bytes); return new UUID(buffer.getLong(), buffer.getLong()); } private static byte[] legacyEncrypt(UUID secret, byte[] data) throws Exception { byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, key(secret), new IvParameterSpec(iv)); byte[] encrypted = cipher.doFinal(data); byte[] payload = Arrays.copyOf(iv, iv.length + encrypted.length); System.arraycopy(encrypted, 0, payload, iv.length, encrypted.length); return payload; } private static byte[] legacyDecrypt(UUID secret, byte[] payload) throws Exception { byte[] iv = Arrays.copyOfRange(payload, 0, 16); byte[] encrypted = Arrays.copyOfRange(payload, 16, payload.length); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, key(secret), new IvParameterSpec(iv)); return cipher.doFinal(encrypted); } private static SecretKeySpec key(UUID secret) { byte[] key = ByteBuffer.allocate(16) .putLong(secret.getMostSignificantBits()) .putLong(secret.getLeastSignificantBits()) .array(); return new SecretKeySpec(key, "AES"); } }