feat: Update logging for JS compatibility in MessageCrypto to reflect Desktop behavior for Latin1 and UTF-8 conversions

This commit is contained in:
k1ngsterr1
2026-01-11 04:13:26 +05:00
parent d46920675b
commit f9411e8419
2 changed files with 56 additions and 37 deletions

View File

@@ -69,31 +69,40 @@ object MessageCrypto {
* XChaCha20-Poly1305 encrypt implementation
*/
private fun xchacha20Poly1305Encrypt(plaintext: ByteArray, key: ByteArray, nonce: ByteArray): ByteArray {
android.util.Log.d("MessageCrypto", "🔐 XChaCha20-Poly1305 Encrypt:")
android.util.Log.d("MessageCrypto", " Key hex: ${key.toHex()}")
android.util.Log.d("MessageCrypto", " Nonce hex: ${nonce.toHex()}")
android.util.Log.d("MessageCrypto", " Plaintext hex: ${plaintext.toHex()}")
// Step 1: Derive subkey using HChaCha20
val subkey = hchacha20(key, nonce.copyOfRange(0, 16))
android.util.Log.d("MessageCrypto", " Subkey hex: ${subkey.toHex()}")
// Step 2: Create ChaCha20 nonce (4 zeros + last 8 bytes of original nonce)
val chacha20Nonce = ByteArray(12)
// First 4 bytes are 0
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
android.util.Log.d("MessageCrypto", " ChaCha20 nonce hex: ${chacha20Nonce.toHex()}")
// Step 3: Encrypt with ChaCha20
// Step 3: Initialize ChaCha20 engine ONCE
val engine = ChaCha7539Engine()
engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
// Step 4: Generate Poly1305 key from first 64 bytes of keystream (counter=0)
val poly1305KeyBlock = ByteArray(64)
engine.processBytes(ByteArray(64), 0, 64, poly1305KeyBlock, 0)
android.util.Log.d("MessageCrypto", " Poly1305 key hex: ${poly1305KeyBlock.copyOfRange(0, 32).toHex()}")
// Step 5: Encrypt plaintext (engine continues from counter=1 automatically)
val ciphertext = ByteArray(plaintext.size)
engine.processBytes(plaintext, 0, plaintext.size, ciphertext, 0)
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
// Step 4: Generate Poly1305 tag
// For AEAD, we need to compute Poly1305 over AAD + ciphertext with proper padding
val poly1305Key = ByteArray(32)
val zeros = ByteArray(32)
val polyKeyEngine = ChaCha7539Engine()
polyKeyEngine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
polyKeyEngine.processBytes(zeros, 0, 32, poly1305Key, 0)
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
// Step 6: Generate Poly1305 tag
val mac = Poly1305()
mac.init(KeyParameter(poly1305Key))
mac.init(KeyParameter(poly1305KeyBlock.copyOfRange(0, 32)))
// No AAD in our case, just process ciphertext
mac.update(ciphertext, 0, ciphertext.size)
@@ -234,15 +243,17 @@ object MessageCrypto {
val chacha20Nonce = ByteArray(12)
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
// Step 3: Verify Poly1305 tag
val poly1305Key = ByteArray(32)
val zeros = ByteArray(32)
val polyKeyEngine = ChaCha7539Engine()
polyKeyEngine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
polyKeyEngine.processBytes(zeros, 0, 32, poly1305Key, 0)
// Step 3: Initialize ChaCha20 engine ONCE
val engine = ChaCha7539Engine()
engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
// Step 4: Generate Poly1305 key from first 64 bytes of keystream (counter=0)
val poly1305KeyBlock = ByteArray(64)
engine.processBytes(ByteArray(64), 0, 64, poly1305KeyBlock, 0)
// Step 5: Verify Poly1305 tag
val mac = Poly1305()
mac.init(KeyParameter(poly1305Key))
mac.init(KeyParameter(poly1305KeyBlock.copyOfRange(0, 32)))
mac.update(ciphertext, 0, ciphertext.size)
val padding = (16 - (ciphertext.size % 16)) % 16
@@ -261,12 +272,19 @@ object MessageCrypto {
throw SecurityException("Authentication failed")
}
// Step 4: Decrypt with ChaCha20
val engine = ChaCha7539Engine()
engine.init(false, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
// Step 6: Decrypt with ChaCha20 (engine continues from where it left off)
// Note: We already consumed 64 bytes for Poly1305 key, now decrypt ciphertext
// BUT: We need a fresh engine for decryption OR reset it
// Actually, for decryption we need to re-init the engine
val decryptEngine = ChaCha7539Engine()
decryptEngine.init(false, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
// Skip first 64 bytes (Poly1305 key block)
val skipBlock = ByteArray(64)
decryptEngine.processBytes(ByteArray(64), 0, 64, skipBlock, 0)
val plaintext = ByteArray(ciphertext.size)
engine.processBytes(ciphertext, 0, ciphertext.size, plaintext, 0)
decryptEngine.processBytes(ciphertext, 0, ciphertext.size, plaintext, 0)
return plaintext
}
@@ -352,16 +370,20 @@ object MessageCrypto {
val ivHex = iv.toHex()
android.util.Log.d("MessageCrypto", " ✓ IV: $ivHex")
// ⚠️ КРИТИЧНО: Эмулируем поведение JS!
android.util.Log.d("MessageCrypto", "\n📍 Step 5: Latin1 → UTF-8 conversion (JS compatibility)")
// ⚠️ КРИТИЧНО: Эмулируем поведение React Native + crypto-js!
// React Native: Buffer.toString('binary') → строка с символами (включая > 127)
// crypto-js: AES.encrypt(string, ...) → кодирует строку в UTF-8 перед шифрованием
// Итого: байты > 127 превращаются в многобайтовые UTF-8 последовательности
android.util.Log.d("MessageCrypto", "\n📍 Step 5: Latin1 → UTF-8 (crypto-js compatibility)")
android.util.Log.d("MessageCrypto", " • Input key+nonce bytes: ${keyAndNonce.size}")
android.util.Log.d("MessageCrypto", " • Input hex: ${keyAndNonce.toHex()}")
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1) // bytes → Latin1 string
// Шаг 1: Байты → Latin1 строка (как Buffer.toString('binary'))
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1)
android.util.Log.d("MessageCrypto", " • Latin1 string length: ${latin1String.length} chars")
android.util.Log.d("MessageCrypto", " • Latin1 char codes (first 20): ${latin1String.take(20).map { it.code }}")
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8) // Latin1 → UTF-8 bytes
// Шаг 2: Latin1 строка → UTF-8 байты (как crypto-js делает внутри)
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8)
android.util.Log.d("MessageCrypto", " ✓ UTF-8 bytes length: ${utf8Bytes.size}")
android.util.Log.d("MessageCrypto", " ✓ UTF-8 hex: ${utf8Bytes.toHex()}")
@@ -374,7 +396,7 @@ object MessageCrypto {
val aesKey = SecretKeySpec(sharedSecret, "AES")
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
cipher.init(Cipher.ENCRYPT_MODE, aesKey, IvParameterSpec(iv))
val encryptedKey = cipher.doFinal(utf8Bytes) // Шифруем UTF-8 bytes
val encryptedKey = cipher.doFinal(utf8Bytes)
val encryptedKeyHex = encryptedKey.toHex()
android.util.Log.d("MessageCrypto", " ✓ CIPHERTEXT: $encryptedKeyHex")
@@ -491,19 +513,14 @@ object MessageCrypto {
android.util.Log.d("MessageCrypto", " ✓ Decrypted UTF-8 bytes: ${decryptedUtf8Bytes.size}")
android.util.Log.d("MessageCrypto", " ✓ Decrypted hex: ${decryptedUtf8Bytes.toHex()}")
// ⚠️ КРИТИЧНО: Обратная конвертация как в JS!
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 conversion (JS compatibility)")
// JS делает:
// 1. AES decrypt → UTF-8 bytes
// 2. decrypted.toString(crypto.enc.Utf8) → UTF-8 decode → string
// 3. Buffer.from(string, 'binary') → берёт charCode каждого символа как байт
//
// Kotlin эквивалент:
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8) // UTF-8 decode
// ⚠️ КРИТИЧНО: Обратная конвертация UTF-8 → Latin1!
// Desktop: decrypted.toString(crypto.enc.Utf8) → Buffer.from(str, 'binary')
// Это декодирует UTF-8 в строку, потом берёт charCode каждого символа
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 (crypto-js compatibility)")
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8)
android.util.Log.d("MessageCrypto", " • UTF-8 string length: ${utf8String.length} chars")
android.util.Log.d("MessageCrypto", " • UTF-8 char codes (first 20): ${utf8String.take(20).map { it.code }}")
val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1) // charCode → bytes
val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1)
android.util.Log.d("MessageCrypto", " ✓ RESULT bytes: ${originalBytes.size}")
android.util.Log.d("MessageCrypto", " ✓ Result hex: ${originalBytes.toHex()}")