feat: Update logging for JS compatibility in MessageCrypto to reflect Desktop behavior for Latin1 and UTF-8 conversions
This commit is contained in:
@@ -69,31 +69,40 @@ object MessageCrypto {
|
|||||||
* XChaCha20-Poly1305 encrypt implementation
|
* XChaCha20-Poly1305 encrypt implementation
|
||||||
*/
|
*/
|
||||||
private fun xchacha20Poly1305Encrypt(plaintext: ByteArray, key: ByteArray, nonce: ByteArray): ByteArray {
|
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
|
// Step 1: Derive subkey using HChaCha20
|
||||||
val subkey = hchacha20(key, nonce.copyOfRange(0, 16))
|
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)
|
// Step 2: Create ChaCha20 nonce (4 zeros + last 8 bytes of original nonce)
|
||||||
val chacha20Nonce = ByteArray(12)
|
val chacha20Nonce = ByteArray(12)
|
||||||
// First 4 bytes are 0
|
// First 4 bytes are 0
|
||||||
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
|
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()
|
val engine = ChaCha7539Engine()
|
||||||
engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
|
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)
|
val ciphertext = ByteArray(plaintext.size)
|
||||||
engine.processBytes(plaintext, 0, plaintext.size, ciphertext, 0)
|
engine.processBytes(plaintext, 0, plaintext.size, ciphertext, 0)
|
||||||
|
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
|
||||||
|
|
||||||
// Step 4: Generate Poly1305 tag
|
android.util.Log.d("MessageCrypto", " Ciphertext hex: ${ciphertext.toHex()}")
|
||||||
// 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)
|
|
||||||
|
|
||||||
|
// Step 6: Generate Poly1305 tag
|
||||||
val mac = Poly1305()
|
val mac = Poly1305()
|
||||||
mac.init(KeyParameter(poly1305Key))
|
mac.init(KeyParameter(poly1305KeyBlock.copyOfRange(0, 32)))
|
||||||
|
|
||||||
// No AAD in our case, just process ciphertext
|
// No AAD in our case, just process ciphertext
|
||||||
mac.update(ciphertext, 0, ciphertext.size)
|
mac.update(ciphertext, 0, ciphertext.size)
|
||||||
@@ -234,15 +243,17 @@ object MessageCrypto {
|
|||||||
val chacha20Nonce = ByteArray(12)
|
val chacha20Nonce = ByteArray(12)
|
||||||
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
|
System.arraycopy(nonce, 16, chacha20Nonce, 4, 8)
|
||||||
|
|
||||||
// Step 3: Verify Poly1305 tag
|
// Step 3: Initialize ChaCha20 engine ONCE
|
||||||
val poly1305Key = ByteArray(32)
|
val engine = ChaCha7539Engine()
|
||||||
val zeros = ByteArray(32)
|
engine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
|
||||||
val polyKeyEngine = ChaCha7539Engine()
|
|
||||||
polyKeyEngine.init(true, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
|
|
||||||
polyKeyEngine.processBytes(zeros, 0, 32, poly1305Key, 0)
|
|
||||||
|
|
||||||
|
// 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()
|
val mac = Poly1305()
|
||||||
mac.init(KeyParameter(poly1305Key))
|
mac.init(KeyParameter(poly1305KeyBlock.copyOfRange(0, 32)))
|
||||||
mac.update(ciphertext, 0, ciphertext.size)
|
mac.update(ciphertext, 0, ciphertext.size)
|
||||||
|
|
||||||
val padding = (16 - (ciphertext.size % 16)) % 16
|
val padding = (16 - (ciphertext.size % 16)) % 16
|
||||||
@@ -261,12 +272,19 @@ object MessageCrypto {
|
|||||||
throw SecurityException("Authentication failed")
|
throw SecurityException("Authentication failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Decrypt with ChaCha20
|
// Step 6: Decrypt with ChaCha20 (engine continues from where it left off)
|
||||||
val engine = ChaCha7539Engine()
|
// Note: We already consumed 64 bytes for Poly1305 key, now decrypt ciphertext
|
||||||
engine.init(false, ParametersWithIV(KeyParameter(subkey), chacha20Nonce))
|
// 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)
|
val plaintext = ByteArray(ciphertext.size)
|
||||||
engine.processBytes(ciphertext, 0, ciphertext.size, plaintext, 0)
|
decryptEngine.processBytes(ciphertext, 0, ciphertext.size, plaintext, 0)
|
||||||
|
|
||||||
return plaintext
|
return plaintext
|
||||||
}
|
}
|
||||||
@@ -352,16 +370,20 @@ object MessageCrypto {
|
|||||||
val ivHex = iv.toHex()
|
val ivHex = iv.toHex()
|
||||||
android.util.Log.d("MessageCrypto", " ✓ IV: $ivHex")
|
android.util.Log.d("MessageCrypto", " ✓ IV: $ivHex")
|
||||||
|
|
||||||
// ⚠️ КРИТИЧНО: Эмулируем поведение JS!
|
// ⚠️ КРИТИЧНО: Эмулируем поведение React Native + crypto-js!
|
||||||
android.util.Log.d("MessageCrypto", "\n📍 Step 5: Latin1 → UTF-8 conversion (JS compatibility)")
|
// 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 key+nonce bytes: ${keyAndNonce.size}")
|
||||||
android.util.Log.d("MessageCrypto", " • Input hex: ${keyAndNonce.toHex()}")
|
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 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 bytes length: ${utf8Bytes.size}")
|
||||||
android.util.Log.d("MessageCrypto", " ✓ UTF-8 hex: ${utf8Bytes.toHex()}")
|
android.util.Log.d("MessageCrypto", " ✓ UTF-8 hex: ${utf8Bytes.toHex()}")
|
||||||
|
|
||||||
@@ -374,7 +396,7 @@ object MessageCrypto {
|
|||||||
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
||||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, IvParameterSpec(iv))
|
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()
|
val encryptedKeyHex = encryptedKey.toHex()
|
||||||
|
|
||||||
android.util.Log.d("MessageCrypto", " ✓ CIPHERTEXT: $encryptedKeyHex")
|
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 UTF-8 bytes: ${decryptedUtf8Bytes.size}")
|
||||||
android.util.Log.d("MessageCrypto", " ✓ Decrypted hex: ${decryptedUtf8Bytes.toHex()}")
|
android.util.Log.d("MessageCrypto", " ✓ Decrypted hex: ${decryptedUtf8Bytes.toHex()}")
|
||||||
|
|
||||||
// ⚠️ КРИТИЧНО: Обратная конвертация как в JS!
|
// ⚠️ КРИТИЧНО: Обратная конвертация UTF-8 → Latin1!
|
||||||
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 conversion (JS compatibility)")
|
// Desktop: decrypted.toString(crypto.enc.Utf8) → Buffer.from(str, 'binary')
|
||||||
// JS делает:
|
// Это декодирует UTF-8 в строку, потом берёт charCode каждого символа
|
||||||
// 1. AES decrypt → UTF-8 bytes
|
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 (crypto-js compatibility)")
|
||||||
// 2. decrypted.toString(crypto.enc.Utf8) → UTF-8 decode → string
|
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8)
|
||||||
// 3. Buffer.from(string, 'binary') → берёт charCode каждого символа как байт
|
|
||||||
//
|
|
||||||
// Kotlin эквивалент:
|
|
||||||
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8) // UTF-8 decode
|
|
||||||
android.util.Log.d("MessageCrypto", " • UTF-8 string length: ${utf8String.length} chars")
|
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 bytes: ${originalBytes.size}")
|
||||||
android.util.Log.d("MessageCrypto", " ✓ Result hex: ${originalBytes.toHex()}")
|
android.util.Log.d("MessageCrypto", " ✓ Result hex: ${originalBytes.toHex()}")
|
||||||
|
|
||||||
|
|||||||
@@ -228,6 +228,8 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
ProtocolManager.addLog(" - To: ${recipient.take(20)}...")
|
ProtocolManager.addLog(" - To: ${recipient.take(20)}...")
|
||||||
ProtocolManager.addLog(" - Content: ${encryptedContent.take(40)}...")
|
ProtocolManager.addLog(" - Content: ${encryptedContent.take(40)}...")
|
||||||
ProtocolManager.addLog(" - ChaCha Key: ${encryptedKey.take(40)}...")
|
ProtocolManager.addLog(" - ChaCha Key: ${encryptedKey.take(40)}...")
|
||||||
|
ProtocolManager.addLog("🔍 ChaCha Key char codes (first 20):")
|
||||||
|
ProtocolManager.addLog(" ${encryptedKey.take(20).map { it.code }.joinToString(",")}")
|
||||||
ProtocolManager.addLog(" - Timestamp: $timestamp")
|
ProtocolManager.addLog(" - Timestamp: $timestamp")
|
||||||
ProtocolManager.addLog(" - Message ID: $messageId")
|
ProtocolManager.addLog(" - Message ID: $messageId")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user