package com.rosetta.messenger.crypto import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Test import org.junit.Assert.* import org.junit.Before import org.junit.runner.RunWith /** * 🧪 Тесты кросс-платформенной совместимости шифрования * * Проверяют совместимость между: * - Kotlin (CryptoManager.kt) * - JS/React Native (cryptoJSI.ts) * * Алгоритм: * - PBKDF2-HMAC-SHA1 (1000 iterations, salt="rosetta") * - AES-256-CBC + PKCS7 padding * - zlib compression (deflate/inflate) * - Format: base64(iv):base64(ciphertext) */ @RunWith(AndroidJUnit4::class) class CryptoCompatibilityTest { @Before fun setup() { java.security.Security.addProvider(org.bouncycastle.jce.provider.BouncyCastleProvider()) } // ======================================== // 🔐 Тестовые данные, зашифрованные на JS // ======================================== /** * Эти данные были зашифрованы на JS (cryptoJSI.ts): * * const encrypted = encodeWithPasswordJSI("testPassword123", "Hello World"); * console.log(encrypted); * * Параметры: * - password: "testPassword123" * - plaintext: "Hello World" * - salt: "rosetta" * - iterations: 1000 * - hash: SHA1 */ companion object { // ⚠️ TODO: Вставьте реальные зашифрованные данные из JS консоли // Для генерации выполните в RN/JS: // // import { encodeWithPasswordJSI } from './cryptoJSI'; // console.log('Test 1:', encodeWithPasswordJSI("testPassword123", "Hello World")); // console.log('Test 2:', encodeWithPasswordJSI("mySecretPass", '{"key":"value","number":42}')); // console.log('Test 3:', encodeWithPasswordJSI("password", "")); // console.log('Test 4:', encodeWithPasswordJSI("пароль123", "Привет мир! 🔐")); // Placeholder - замените реальными данными из JS const val JS_ENCRYPTED_HELLO_WORLD = "" // encodeWithPasswordJSI("testPassword123", "Hello World") const val JS_ENCRYPTED_JSON = "" // encodeWithPasswordJSI("mySecretPass", '{"key":"value","number":42}') const val JS_ENCRYPTED_EMPTY = "" // encodeWithPasswordJSI("password", "") const val JS_ENCRYPTED_CYRILLIC = "" // encodeWithPasswordJSI("пароль123", "Привет мир! 🔐") } // ======================================== // ✅ Тесты Kotlin → Kotlin (базовая проверка) // ======================================== @Test fun kotlin_encrypt_decrypt_roundtrip() { val originalData = "Hello, World! This is a secret message." val password = "testPassword123" val encrypted = CryptoManager.encryptWithPassword(originalData, password) val decrypted = CryptoManager.decryptWithPassword(encrypted, password) assertNotNull("Encrypted data should not be null", encrypted) assertTrue("Encrypted should contain ':'", encrypted.contains(":")) assertEquals("Decrypted should match original", originalData, decrypted) } @Test fun kotlin_encrypt_decrypt_empty_string() { val originalData = "" val password = "password" val encrypted = CryptoManager.encryptWithPassword(originalData, password) val decrypted = CryptoManager.decryptWithPassword(encrypted, password) assertEquals("Empty string should roundtrip correctly", originalData, decrypted) } @Test fun kotlin_encrypt_decrypt_cyrillic_and_emoji() { val originalData = "Привет мир! 🔐 Тест кириллицы и эмодзи 🚀" val password = "пароль123" val encrypted = CryptoManager.encryptWithPassword(originalData, password) val decrypted = CryptoManager.decryptWithPassword(encrypted, password) assertEquals("Cyrillic and emoji should roundtrip correctly", originalData, decrypted) } @Test fun kotlin_encrypt_decrypt_json() { val originalData = """{"key":"value","number":42,"nested":{"array":[1,2,3]}}""" val password = "jsonPassword" val encrypted = CryptoManager.encryptWithPassword(originalData, password) val decrypted = CryptoManager.decryptWithPassword(encrypted, password) assertEquals("JSON should roundtrip correctly", originalData, decrypted) } @Test fun kotlin_wrong_password_returns_null() { val originalData = "Secret message" val correctPassword = "correctPassword" val wrongPassword = "wrongPassword" val encrypted = CryptoManager.encryptWithPassword(originalData, correctPassword) val decrypted = CryptoManager.decryptWithPassword(encrypted, wrongPassword) assertNull("Wrong password should return null", decrypted) } // ======================================== // 🌐 Тесты JS → Kotlin (кросс-платформа) // ======================================== @Test fun js_to_kotlin_decrypt_hello_world() { if (JS_ENCRYPTED_HELLO_WORLD.isEmpty()) { println("⚠️ SKIP: JS_ENCRYPTED_HELLO_WORLD not set. Generate from JS first.") return } val decrypted = CryptoManager.decryptWithPassword(JS_ENCRYPTED_HELLO_WORLD, "testPassword123") assertEquals("Should decrypt JS data correctly", "Hello World", decrypted) } @Test fun js_to_kotlin_decrypt_json() { if (JS_ENCRYPTED_JSON.isEmpty()) { println("⚠️ SKIP: JS_ENCRYPTED_JSON not set. Generate from JS first.") return } val decrypted = CryptoManager.decryptWithPassword(JS_ENCRYPTED_JSON, "mySecretPass") assertEquals("Should decrypt JSON from JS", """{"key":"value","number":42}""", decrypted) } @Test fun js_to_kotlin_decrypt_empty() { if (JS_ENCRYPTED_EMPTY.isEmpty()) { println("⚠️ SKIP: JS_ENCRYPTED_EMPTY not set. Generate from JS first.") return } val decrypted = CryptoManager.decryptWithPassword(JS_ENCRYPTED_EMPTY, "password") assertEquals("Should decrypt empty string from JS", "", decrypted) } @Test fun js_to_kotlin_decrypt_cyrillic() { if (JS_ENCRYPTED_CYRILLIC.isEmpty()) { println("⚠️ SKIP: JS_ENCRYPTED_CYRILLIC not set. Generate from JS first.") return } val decrypted = CryptoManager.decryptWithPassword(JS_ENCRYPTED_CYRILLIC, "пароль123") assertEquals("Should decrypt Cyrillic from JS", "Привет мир! 🔐", decrypted) } // ======================================== // 🔧 Тест формата данных // ======================================== @Test fun encrypted_format_is_correct() { val encrypted = CryptoManager.encryptWithPassword("test", "password") val parts = encrypted.split(":") assertEquals("Should have exactly 2 parts (iv:ct)", 2, parts.size) // Проверяем что обе части - валидный Base64 try { val iv = android.util.Base64.decode(parts[0], android.util.Base64.NO_WRAP) val ct = android.util.Base64.decode(parts[1], android.util.Base64.NO_WRAP) assertEquals("IV should be 16 bytes", 16, iv.size) assertTrue("Ciphertext should not be empty", ct.isNotEmpty()) } catch (e: Exception) { fail("Both parts should be valid Base64: ${e.message}") } } // ======================================== // 📊 Тест PBKDF2 ключа (для отладки) // ======================================== @Test fun pbkdf2_key_derivation_is_correct() { // Известный тестовый вектор для PBKDF2-HMAC-SHA1 // password: "testPassword123", salt: "rosetta", iterations: 1000, keyLen: 32 val factory = javax.crypto.SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") val spec = javax.crypto.spec.PBEKeySpec( "testPassword123".toCharArray(), "rosetta".toByteArray(Charsets.UTF_8), 1000, 256 ) val key = factory.generateSecret(spec).encoded // Выводим ключ для сравнения с JS val keyHex = key.joinToString("") { "%02x".format(it) } println("🔑 PBKDF2 Key (hex): $keyHex") assertEquals("Key should be 32 bytes", 32, key.size) // ⚠️ TODO: Сравните этот ключ с JS: // const key = generatePBKDF2KeyJSI("testPassword123"); // console.log("Key (hex):", key.toString('hex')); } }