Files
mobile-android/app/src/androidTest/java/com/rosetta/messenger/crypto/CryptoCompatibilityTest.kt

230 lines
9.0 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'));
}
}