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
|
||||
*/
|
||||
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()}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user