feat: Enhance compression and decompression methods for compatibility with pako library by using RAW deflate/inflate
This commit is contained in:
@@ -216,8 +216,17 @@ object CryptoManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAW Deflate сжатие (без zlib header)
|
||||||
|
*
|
||||||
|
* ⚠️ ВАЖНО: nowrap=true для совместимости с pako.deflate() в JS!
|
||||||
|
* - pako.deflate() создаёт RAW deflate поток (без 2-byte zlib header)
|
||||||
|
* - Java Deflater() по умолчанию создаёт zlib поток (с header 78 9C)
|
||||||
|
* - Поэтому используем Deflater(level, true) где true = nowrap
|
||||||
|
*/
|
||||||
private fun compress(data: ByteArray): ByteArray {
|
private fun compress(data: ByteArray): ByteArray {
|
||||||
val deflater = Deflater()
|
// nowrap=true = RAW deflate (совместимо с pako.deflate)
|
||||||
|
val deflater = Deflater(Deflater.DEFAULT_COMPRESSION, true)
|
||||||
deflater.setInput(data)
|
deflater.setInput(data)
|
||||||
deflater.finish()
|
deflater.finish()
|
||||||
|
|
||||||
@@ -227,12 +236,22 @@ object CryptoManager {
|
|||||||
val count = deflater.deflate(buffer)
|
val count = deflater.deflate(buffer)
|
||||||
outputStream.write(buffer, 0, count)
|
outputStream.write(buffer, 0, count)
|
||||||
}
|
}
|
||||||
|
deflater.end() // Освобождаем ресурсы
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
return outputStream.toByteArray()
|
return outputStream.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RAW Inflate декомпрессия (без zlib header)
|
||||||
|
*
|
||||||
|
* ⚠️ ВАЖНО: nowrap=true для совместимости с pako.inflate() в JS!
|
||||||
|
* - pako.inflate() ожидает RAW deflate поток
|
||||||
|
* - Java Inflater() по умолчанию ожидает zlib поток (с header)
|
||||||
|
* - Поэтому используем Inflater(true) где true = nowrap
|
||||||
|
*/
|
||||||
private fun decompress(data: ByteArray): ByteArray {
|
private fun decompress(data: ByteArray): ByteArray {
|
||||||
val inflater = Inflater()
|
// nowrap=true = RAW inflate (совместимо с pako.inflate)
|
||||||
|
val inflater = Inflater(true)
|
||||||
inflater.setInput(data)
|
inflater.setInput(data)
|
||||||
|
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
@@ -241,6 +260,7 @@ object CryptoManager {
|
|||||||
val count = inflater.inflate(buffer)
|
val count = inflater.inflate(buffer)
|
||||||
outputStream.write(buffer, 0, count)
|
outputStream.write(buffer, 0, count)
|
||||||
}
|
}
|
||||||
|
inflater.end() // Освобождаем ресурсы
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
return outputStream.toByteArray()
|
return outputStream.toByteArray()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,112 +275,241 @@ object MessageCrypto {
|
|||||||
* ECDH шифрование ключа для получателя
|
* ECDH шифрование ключа для получателя
|
||||||
* Использует secp256k1 + AES как в RN версии
|
* Использует secp256k1 + AES как в RN версии
|
||||||
* Формат: Base64(iv:ciphertext:ephemeralPrivateKeyHex)
|
* Формат: Base64(iv:ciphertext:ephemeralPrivateKeyHex)
|
||||||
|
*
|
||||||
|
* JS эквивалент:
|
||||||
|
* const key = Buffer.concat([keyBytes, nonceBytes]);
|
||||||
|
* const encryptedKey = await encrypt(key.toString('binary'), publicKey);
|
||||||
|
*
|
||||||
|
* КРИТИЧНО: ephemeralKey.getPrivate('hex') в JS может быть БЕЗ ведущих нулей!
|
||||||
*/
|
*/
|
||||||
fun encryptKeyForRecipient(keyAndNonce: ByteArray, recipientPublicKeyHex: String): String {
|
fun encryptKeyForRecipient(keyAndNonce: ByteArray, recipientPublicKeyHex: String): String {
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "━".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "🔐 ECDH ENCRYPT KEY FOR RECIPIENT")
|
||||||
|
android.util.Log.d("MessageCrypto", "━".repeat(80))
|
||||||
|
|
||||||
val secureRandom = SecureRandom()
|
val secureRandom = SecureRandom()
|
||||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||||
|
|
||||||
// Генерируем эфемерный приватный ключ (32 байта)
|
// Генерируем эфемерный приватный ключ (32 байта)
|
||||||
|
android.util.Log.d("MessageCrypto", "📍 Step 1: Generate ephemeral key pair")
|
||||||
val ephemeralPrivateKeyBytes = ByteArray(32)
|
val ephemeralPrivateKeyBytes = ByteArray(32)
|
||||||
secureRandom.nextBytes(ephemeralPrivateKeyBytes)
|
secureRandom.nextBytes(ephemeralPrivateKeyBytes)
|
||||||
val ephemeralPrivateKey = BigInteger(1, ephemeralPrivateKeyBytes)
|
val ephemeralPrivateKey = BigInteger(1, ephemeralPrivateKeyBytes)
|
||||||
val ephemeralPrivateKeyHex = ephemeralPrivateKeyBytes.toHex()
|
|
||||||
|
// ⚠️ КРИТИЧНО: JS elliptic.js может вернуть hex БЕЗ ведущих нулей!
|
||||||
|
// ephemeralKey.getPrivate('hex') - это BigInteger.toString(16)
|
||||||
|
// Не добавляет ведущие нули если первый байт < 0x10
|
||||||
|
val ephemeralPrivateKeyHex = ephemeralPrivateKey.toString(16).let {
|
||||||
|
// Но если нечётная длина, добавим ведущий 0 для правильного парсинга
|
||||||
|
if (it.length % 2 != 0) {
|
||||||
|
android.util.Log.d("MessageCrypto", "⚠️ Padded ephemeral private key (was odd length)")
|
||||||
|
"0$it"
|
||||||
|
} else it
|
||||||
|
}
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private key: $ephemeralPrivateKeyHex")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private length: ${ephemeralPrivateKeyHex.length} hex chars")
|
||||||
|
|
||||||
// Получаем эфемерный публичный ключ
|
// Получаем эфемерный публичный ключ
|
||||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||||
|
val ephemeralPublicKeyHex = ephemeralPublicKey.getEncoded(false).toHex()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ephemeral public key: $ephemeralPublicKeyHex")
|
||||||
|
|
||||||
// Парсим публичный ключ получателя
|
// Парсим публичный ключ получателя
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 2: Parse recipient public key")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Recipient public key: $recipientPublicKeyHex")
|
||||||
val recipientPublicKeyBytes = recipientPublicKeyHex.hexToBytes()
|
val recipientPublicKeyBytes = recipientPublicKeyHex.hexToBytes()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Recipient key bytes: ${recipientPublicKeyBytes.size}")
|
||||||
val recipientPublicKey = ecSpec.curve.decodePoint(recipientPublicKeyBytes)
|
val recipientPublicKey = ecSpec.curve.decodePoint(recipientPublicKeyBytes)
|
||||||
|
|
||||||
// ECDH: ephemeralPrivate * recipientPublic = sharedSecret
|
// ECDH: ephemeralPrivate * recipientPublic = sharedSecret
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 3: Compute ECDH shared secret")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Computing: ephemeral_private × recipient_public")
|
||||||
val sharedPoint = recipientPublicKey.multiply(ephemeralPrivateKey)
|
val sharedPoint = recipientPublicKey.multiply(ephemeralPrivateKey)
|
||||||
val sharedSecret = sharedPoint.normalize().xCoord.encoded
|
|
||||||
val sharedSecretHex = sharedSecret.toHex()
|
// ⚠️ КРИТИЧНО: Эмулируем JS поведение!
|
||||||
|
// JS: BN.toString(16) НЕ добавляет ведущие нули
|
||||||
|
// crypto.enc.Hex.parse(hex) парсит как есть
|
||||||
|
// Если X coordinate = 0x00abc..., JS получит "abc..." (меньше 32 байт)
|
||||||
|
val xCoordBigInt = sharedPoint.normalize().xCoord.toBigInteger()
|
||||||
|
android.util.Log.d("MessageCrypto", " • X coordinate (BigInt): $xCoordBigInt")
|
||||||
|
var sharedSecretHex = xCoordBigInt.toString(16)
|
||||||
|
android.util.Log.d("MessageCrypto", " • toString(16) result: $sharedSecretHex (${sharedSecretHex.length} chars)")
|
||||||
|
|
||||||
|
// JS: если hex нечётной длины, crypto.enc.Hex.parse добавит ведущий 0
|
||||||
|
if (sharedSecretHex.length % 2 != 0) {
|
||||||
|
android.util.Log.d("MessageCrypto", " ⚠️ Padding to even length for hex parsing")
|
||||||
|
sharedSecretHex = "0$sharedSecretHex"
|
||||||
|
}
|
||||||
|
val sharedSecret = sharedSecretHex.hexToBytes()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ SHARED SECRET hex: $sharedSecretHex")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Shared secret bytes: ${sharedSecret.size} bytes")
|
||||||
|
|
||||||
// Генерируем IV для AES (16 байт)
|
// Генерируем IV для AES (16 байт)
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 4: Generate AES IV")
|
||||||
val iv = ByteArray(16)
|
val iv = ByteArray(16)
|
||||||
secureRandom.nextBytes(iv)
|
secureRandom.nextBytes(iv)
|
||||||
val ivHex = iv.toHex()
|
val ivHex = iv.toHex()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ IV: $ivHex")
|
||||||
|
|
||||||
// КРИТИЧНО: Эмулируем поведение crypto-js!
|
// ⚠️ КРИТИЧНО: Эмулируем поведение JS!
|
||||||
// React Native делает: key.toString('binary') → crypto.AES.encrypt(string, ...)
|
android.util.Log.d("MessageCrypto", "\n📍 Step 5: Latin1 → UTF-8 conversion (JS compatibility)")
|
||||||
// crypto-js интерпретирует string как UTF-8!
|
android.util.Log.d("MessageCrypto", " • Input key+nonce bytes: ${keyAndNonce.size}")
|
||||||
// Поэтому: ByteArray → Latin1 String → UTF-8 bytes → encrypt
|
android.util.Log.d("MessageCrypto", " • Input hex: ${keyAndNonce.toHex()}")
|
||||||
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1)
|
|
||||||
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8)
|
|
||||||
|
|
||||||
android.util.Log.d("MessageCrypto", "📝 Key encoding:")
|
val latin1String = String(keyAndNonce, Charsets.ISO_8859_1) // bytes → Latin1 string
|
||||||
android.util.Log.d("MessageCrypto", " - Original bytes: ${keyAndNonce.size}")
|
android.util.Log.d("MessageCrypto", " • Latin1 string length: ${latin1String.length} chars")
|
||||||
android.util.Log.d("MessageCrypto", " - Latin1 string chars: ${latin1String.length}")
|
android.util.Log.d("MessageCrypto", " • Latin1 char codes (first 20): ${latin1String.take(20).map { it.code }}")
|
||||||
android.util.Log.d("MessageCrypto", " - UTF-8 bytes: ${utf8Bytes.size}")
|
|
||||||
|
val utf8Bytes = latin1String.toByteArray(Charsets.UTF_8) // Latin1 → UTF-8 bytes
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ UTF-8 bytes length: ${utf8Bytes.size}")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ UTF-8 hex: ${utf8Bytes.toHex()}")
|
||||||
|
|
||||||
|
// AES шифрование
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 6: AES-CBC encryption")
|
||||||
|
android.util.Log.d("MessageCrypto", " • AES key (shared secret): ${sharedSecret.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", " • IV: ${iv.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Plaintext: ${utf8Bytes.size} bytes")
|
||||||
|
|
||||||
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)
|
val encryptedKey = cipher.doFinal(utf8Bytes) // Шифруем UTF-8 bytes
|
||||||
val encryptedKeyHex = encryptedKey.toHex()
|
val encryptedKeyHex = encryptedKey.toHex()
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ CIPHERTEXT: $encryptedKeyHex")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ciphertext length: ${encryptedKey.size} bytes")
|
||||||
|
|
||||||
// Формат как в RN: btoa(ivHex:encryptedHex:ephemeralPrivateHex)
|
// Формат как в RN: btoa(ivHex:encryptedHex:ephemeralPrivateHex)
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 7: Format as iv:ciphertext:ephemeralPrivate")
|
||||||
val combined = "$ivHex:$encryptedKeyHex:$ephemeralPrivateKeyHex"
|
val combined = "$ivHex:$encryptedKeyHex:$ephemeralPrivateKeyHex"
|
||||||
|
android.util.Log.d("MessageCrypto", " • Combined string: ${combined.take(160)}...")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Combined length: ${combined.length} chars")
|
||||||
|
|
||||||
android.util.Log.d("MessageCrypto", "🔐 ECDH Encrypt:")
|
val result = Base64.encodeToString(combined.toByteArray(), Base64.NO_WRAP)
|
||||||
android.util.Log.d("MessageCrypto", " - Shared secret: ${sharedSecretHex.take(40)}...")
|
android.util.Log.d("MessageCrypto", "\n ✅ FINAL ENCRYPTED KEY (Base64): $result")
|
||||||
android.util.Log.d("MessageCrypto", " - IV: ${ivHex.take(32)}...")
|
android.util.Log.d("MessageCrypto", " ✅ Base64 length: ${result.length} chars")
|
||||||
android.util.Log.d("MessageCrypto", " - Ephemeral private: ${ephemeralPrivateKeyHex.take(40)}...")
|
android.util.Log.d("MessageCrypto", "━".repeat(80) + "\n")
|
||||||
android.util.Log.d("MessageCrypto", " - Recipient public: ${recipientPublicKeyHex.take(40)}...")
|
return result
|
||||||
|
|
||||||
return Base64.encodeToString(combined.toByteArray(), Base64.NO_WRAP)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ECDH расшифровка ключа
|
* ECDH расшифровка ключа
|
||||||
* Формат: Base64(ivHex:encryptedHex:ephemeralPrivateHex)
|
* Формат: Base64(ivHex:encryptedHex:ephemeralPrivateHex)
|
||||||
|
*
|
||||||
|
* КРИТИЧНО: ephemeralPrivateKeyHex может иметь нечётную длину!
|
||||||
*/
|
*/
|
||||||
fun decryptKeyFromSender(encryptedKeyBase64: String, myPrivateKeyHex: String): ByteArray {
|
fun decryptKeyFromSender(encryptedKeyBase64: String, myPrivateKeyHex: String): ByteArray {
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "━".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓 ECDH DECRYPT KEY FROM SENDER")
|
||||||
|
android.util.Log.d("MessageCrypto", "━".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "📥 Encrypted key (Base64): $encryptedKeyBase64")
|
||||||
|
android.util.Log.d("MessageCrypto", "📥 Base64 length: ${encryptedKeyBase64.length} chars")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 My private key: $myPrivateKeyHex")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 1: Decode Base64 and parse format")
|
||||||
val combined = String(Base64.decode(encryptedKeyBase64, Base64.NO_WRAP))
|
val combined = String(Base64.decode(encryptedKeyBase64, Base64.NO_WRAP))
|
||||||
|
android.util.Log.d("MessageCrypto", " • Decoded string: ${combined.take(160)}...")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Decoded length: ${combined.length} chars")
|
||||||
|
|
||||||
val parts = combined.split(":")
|
val parts = combined.split(":")
|
||||||
if (parts.size != 3) throw IllegalArgumentException("Invalid encrypted key format: expected 3 parts, got ${parts.size}")
|
if (parts.size != 3) {
|
||||||
|
android.util.Log.e("MessageCrypto", "❌ Invalid format: expected 3 parts, got ${parts.size}")
|
||||||
|
throw IllegalArgumentException("Invalid encrypted key format: expected 3 parts, got ${parts.size}")
|
||||||
|
}
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Parsed into 3 parts")
|
||||||
|
|
||||||
val ivHex = parts[0]
|
val ivHex = parts[0]
|
||||||
val encryptedKeyHex = parts[1]
|
val encryptedKeyHex = parts[1]
|
||||||
val ephemeralPrivateKeyHex = parts[2]
|
var ephemeralPrivateKeyHex = parts[2]
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", " • Part 1 (IV): $ivHex (${ivHex.length} chars)")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Part 2 (ciphertext): ${encryptedKeyHex.take(80)}... (${encryptedKeyHex.length} chars)")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Part 3 (ephemeral private): $ephemeralPrivateKeyHex")
|
||||||
|
|
||||||
|
// ⚠️ КРИТИЧНО: JS toString(16) может вернуть hex нечётной длины!
|
||||||
|
// Добавляем ведущий 0 если нужно для правильного парсинга
|
||||||
|
if (ephemeralPrivateKeyHex.length % 2 != 0) {
|
||||||
|
android.util.Log.d("MessageCrypto", " ⚠️ Padding ephemeral key from ${ephemeralPrivateKeyHex.length} to ${ephemeralPrivateKeyHex.length + 1}")
|
||||||
|
ephemeralPrivateKeyHex = "0$ephemeralPrivateKeyHex"
|
||||||
|
}
|
||||||
|
|
||||||
val iv = ivHex.hexToBytes()
|
val iv = ivHex.hexToBytes()
|
||||||
val encryptedKey = encryptedKeyHex.hexToBytes()
|
val encryptedKey = encryptedKeyHex.hexToBytes()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ IV bytes: ${iv.size}")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ciphertext bytes: ${encryptedKey.size}")
|
||||||
|
|
||||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||||
|
|
||||||
// Парсим эфемерный приватный ключ
|
// Парсим эфемерный приватный ключ
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 2: Parse ephemeral key pair")
|
||||||
val ephemeralPrivateKey = BigInteger(ephemeralPrivateKeyHex, 16)
|
val ephemeralPrivateKey = BigInteger(ephemeralPrivateKeyHex, 16)
|
||||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||||
|
val ephemeralPublicKeyHex = ephemeralPublicKey.getEncoded(false).toHex()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ephemeral private: $ephemeralPrivateKeyHex")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Ephemeral public: $ephemeralPublicKeyHex")
|
||||||
|
|
||||||
// Парсим мой приватный ключ
|
// Парсим мой приватный ключ
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 3: Parse my key pair")
|
||||||
val myPrivateKey = BigInteger(myPrivateKeyHex, 16)
|
val myPrivateKey = BigInteger(myPrivateKeyHex, 16)
|
||||||
val myPublicKey = ecSpec.g.multiply(myPrivateKey)
|
val myPublicKey = ecSpec.g.multiply(myPrivateKey)
|
||||||
|
val myPublicKeyHex = myPublicKey.getEncoded(false).toHex()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ My private: ${myPrivateKeyHex.take(32)}...")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ My public: $myPublicKeyHex")
|
||||||
|
|
||||||
// ECDH: ephemeralPrivate * myPublic = sharedSecret
|
// ECDH: ephemeralPrivate * myPublic = sharedSecret
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 4: Compute ECDH shared secret")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Computing: ephemeral_private × my_public")
|
||||||
val sharedPoint = myPublicKey.multiply(ephemeralPrivateKey)
|
val sharedPoint = myPublicKey.multiply(ephemeralPrivateKey)
|
||||||
val sharedSecret = sharedPoint.normalize().xCoord.encoded
|
|
||||||
|
// ⚠️ КРИТИЧНО: Эмулируем JS поведение!
|
||||||
|
// JS: BN.toString(16) НЕ добавляет ведущие нули
|
||||||
|
val xCoordBigInt = sharedPoint.normalize().xCoord.toBigInteger()
|
||||||
|
android.util.Log.d("MessageCrypto", " • X coordinate (BigInt): $xCoordBigInt")
|
||||||
|
var sharedSecretHex = xCoordBigInt.toString(16)
|
||||||
|
android.util.Log.d("MessageCrypto", " • toString(16) result: $sharedSecretHex (${sharedSecretHex.length} chars)")
|
||||||
|
|
||||||
|
if (sharedSecretHex.length % 2 != 0) {
|
||||||
|
android.util.Log.d("MessageCrypto", " ⚠️ Padding to even length")
|
||||||
|
sharedSecretHex = "0$sharedSecretHex"
|
||||||
|
}
|
||||||
|
val sharedSecret = sharedSecretHex.hexToBytes()
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ SHARED SECRET hex: $sharedSecretHex")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Shared secret bytes: ${sharedSecret.size} bytes")
|
||||||
|
|
||||||
// Расшифровываем используя sharedSecret как AES ключ
|
// Расшифровываем используя sharedSecret как AES ключ
|
||||||
|
android.util.Log.d("MessageCrypto", "\n📍 Step 5: AES-CBC decryption")
|
||||||
|
android.util.Log.d("MessageCrypto", " • AES key (shared secret): ${sharedSecret.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", " • IV: ${iv.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Ciphertext: ${encryptedKey.size} bytes")
|
||||||
|
|
||||||
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.DECRYPT_MODE, aesKey, IvParameterSpec(iv))
|
cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(iv))
|
||||||
|
|
||||||
val decryptedUtf8Bytes = cipher.doFinal(encryptedKey)
|
val decryptedUtf8Bytes = cipher.doFinal(encryptedKey)
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Decrypted UTF-8 bytes: ${decryptedUtf8Bytes.size}")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✓ Decrypted hex: ${decryptedUtf8Bytes.toHex()}")
|
||||||
|
|
||||||
// КРИТИЧНО: Обратная конвертация как в crypto-js!
|
// ⚠️ КРИТИЧНО: Обратная конвертация как в JS!
|
||||||
// crypto-js делает: decrypt → WordArray → .toString(Utf8) → string
|
android.util.Log.d("MessageCrypto", "\n📍 Step 6: UTF-8 → Latin1 conversion (JS compatibility)")
|
||||||
// Потом: Buffer.from(string, 'binary') → bytes
|
// JS делает:
|
||||||
// Эквивалент: UTF-8 bytes → UTF-8 string → Latin1 bytes
|
// 1. AES decrypt → UTF-8 bytes
|
||||||
val utf8String = String(decryptedUtf8Bytes, Charsets.UTF_8)
|
// 2. decrypted.toString(crypto.enc.Utf8) → UTF-8 decode → string
|
||||||
val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1)
|
// 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 char codes (first 20): ${utf8String.take(20).map { it.code }}")
|
||||||
|
|
||||||
android.util.Log.d("MessageCrypto", "📝 Key decoding:")
|
val originalBytes = utf8String.toByteArray(Charsets.ISO_8859_1) // charCode → bytes
|
||||||
android.util.Log.d("MessageCrypto", " - Decrypted UTF-8 bytes: ${decryptedUtf8Bytes.size}")
|
android.util.Log.d("MessageCrypto", " ✓ RESULT bytes: ${originalBytes.size}")
|
||||||
android.util.Log.d("MessageCrypto", " - UTF-8 string chars: ${utf8String.length}")
|
android.util.Log.d("MessageCrypto", " ✓ Result hex: ${originalBytes.toHex()}")
|
||||||
android.util.Log.d("MessageCrypto", " - Original bytes: ${originalBytes.size}")
|
|
||||||
|
android.util.Log.d("MessageCrypto", "\n ✅ DECRYPTION COMPLETE")
|
||||||
|
android.util.Log.d("MessageCrypto", " ✅ Returned ${originalBytes.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "━".repeat(80) + "\n")
|
||||||
|
|
||||||
return originalBytes
|
return originalBytes
|
||||||
}
|
}
|
||||||
@@ -389,31 +518,52 @@ object MessageCrypto {
|
|||||||
* Полное шифрование сообщения для отправки
|
* Полное шифрование сообщения для отправки
|
||||||
*/
|
*/
|
||||||
fun encryptForSending(plaintext: String, recipientPublicKey: String): Pair<String, String> {
|
fun encryptForSending(plaintext: String, recipientPublicKey: String): Pair<String, String> {
|
||||||
android.util.Log.d("MessageCrypto", "🔐 === START ENCRYPTION ===")
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
android.util.Log.d("MessageCrypto", "📝 Plaintext: '$plaintext'")
|
android.util.Log.d("MessageCrypto", "🚀🚀🚀 START ENCRYPTION FOR SENDING 🚀🚀🚀")
|
||||||
android.util.Log.d("MessageCrypto", "📝 Plaintext length: ${plaintext.length}")
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
android.util.Log.d("MessageCrypto", "🔑 Recipient public key: ${recipientPublicKey.take(20)}...")
|
android.util.Log.d("MessageCrypto", "📝 PLAINTEXT: '$plaintext'")
|
||||||
|
android.util.Log.d("MessageCrypto", "📝 Plaintext length: ${plaintext.length} chars")
|
||||||
|
android.util.Log.d("MessageCrypto", "📝 Plaintext bytes: ${plaintext.toByteArray().size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "📝 Plaintext hex (first 128): ${plaintext.toByteArray().take(64).toByteArray().toHex()}")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 RECIPIENT PUBLIC KEY: $recipientPublicKey")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 Public key length: ${recipientPublicKey.length} chars")
|
||||||
|
|
||||||
// 1. Шифруем текст
|
// 1. Шифруем текст
|
||||||
android.util.Log.d("MessageCrypto", "⚡ Step 1: Encrypting message with XChaCha20-Poly1305...")
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 1: XCHACHA20-POLY1305 ENCRYPTION")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
val encrypted = encryptMessage(plaintext)
|
val encrypted = encryptMessage(plaintext)
|
||||||
android.util.Log.d("MessageCrypto", "✅ Ciphertext: ${encrypted.ciphertext.take(40)}...")
|
android.util.Log.d("MessageCrypto", "✅ CIPHERTEXT (hex): ${encrypted.ciphertext}")
|
||||||
android.util.Log.d("MessageCrypto", "✅ Ciphertext length: ${encrypted.ciphertext.length}")
|
android.util.Log.d("MessageCrypto", "✅ Ciphertext length: ${encrypted.ciphertext.length} chars")
|
||||||
android.util.Log.d("MessageCrypto", "✅ Key: ${encrypted.key.take(20)}...")
|
android.util.Log.d("MessageCrypto", "✅ KEY (hex): ${encrypted.key}")
|
||||||
android.util.Log.d("MessageCrypto", "✅ Nonce: ${encrypted.nonce.take(20)}...")
|
android.util.Log.d("MessageCrypto", "✅ Key length: ${encrypted.key.length} chars (should be 64)")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ NONCE (hex): ${encrypted.nonce}")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Nonce length: ${encrypted.nonce.length} chars (should be 48)")
|
||||||
|
|
||||||
// 2. Собираем key + nonce
|
// 2. Собираем key + nonce
|
||||||
android.util.Log.d("MessageCrypto", "⚡ Step 2: Combining key + nonce...")
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 2: COMBINE KEY + NONCE")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
val keyAndNonce = encrypted.key.hexToBytes() + encrypted.nonce.hexToBytes()
|
val keyAndNonce = encrypted.key.hexToBytes() + encrypted.nonce.hexToBytes()
|
||||||
android.util.Log.d("MessageCrypto", "✅ Combined keyAndNonce length: ${keyAndNonce.size} bytes")
|
android.util.Log.d("MessageCrypto", "✅ KEY+NONCE combined: ${keyAndNonce.size} bytes (should be 56)")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Combined hex: ${keyAndNonce.toHex()}")
|
||||||
|
|
||||||
// 3. Шифруем ключ для получателя
|
// 3. Шифруем ключ для получателя
|
||||||
android.util.Log.d("MessageCrypto", "⚡ Step 3: Encrypting key with ECDH + AES...")
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 3: ECDH + AES ENCRYPTION OF KEY")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
val encryptedKey = encryptKeyForRecipient(keyAndNonce, recipientPublicKey)
|
val encryptedKey = encryptKeyForRecipient(keyAndNonce, recipientPublicKey)
|
||||||
android.util.Log.d("MessageCrypto", "✅ Encrypted key: ${encryptedKey.take(40)}...")
|
android.util.Log.d("MessageCrypto", "✅ ENCRYPTED KEY (Base64): $encryptedKey")
|
||||||
android.util.Log.d("MessageCrypto", "✅ Encrypted key length: ${encryptedKey.length}")
|
android.util.Log.d("MessageCrypto", "✅ Encrypted key length: ${encryptedKey.length} chars")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "✅✅✅ ENCRYPTION COMPLETE ✅✅✅")
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "FINAL OUTPUTS:")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Ciphertext: ${encrypted.ciphertext.take(60)}... (${encrypted.ciphertext.length} chars)")
|
||||||
|
android.util.Log.d("MessageCrypto", " • Encrypted key: ${encryptedKey.take(60)}... (${encryptedKey.length} chars)")
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100) + "\n")
|
||||||
|
|
||||||
android.util.Log.d("MessageCrypto", "🔐 === ENCRYPTION COMPLETE ===")
|
|
||||||
return kotlin.Pair(encrypted.ciphertext, encryptedKey)
|
return kotlin.Pair(encrypted.ciphertext, encryptedKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,15 +575,50 @@ object MessageCrypto {
|
|||||||
encryptedKey: String,
|
encryptedKey: String,
|
||||||
myPrivateKey: String
|
myPrivateKey: String
|
||||||
): String {
|
): String {
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "🔓🔓🔓 START DECRYPTION OF INCOMING MESSAGE 🔓🔓🔓")
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "📩 CIPHERTEXT (hex): ${ciphertext.take(100)}...")
|
||||||
|
android.util.Log.d("MessageCrypto", "📩 Ciphertext length: ${ciphertext.length} chars")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔐 ENCRYPTED KEY: ${encryptedKey.take(100)}...")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔐 Encrypted key length: ${encryptedKey.length} chars")
|
||||||
|
android.util.Log.d("MessageCrypto", "🔑 MY PRIVATE KEY: ${myPrivateKey.take(32)}... (${myPrivateKey.length} chars)")
|
||||||
|
|
||||||
// 1. Расшифровываем ключ
|
// 1. Расшифровываем ключ
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 1: ECDH + AES DECRYPTION OF KEY")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
val keyAndNonce = decryptKeyFromSender(encryptedKey, myPrivateKey)
|
val keyAndNonce = decryptKeyFromSender(encryptedKey, myPrivateKey)
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ DECRYPTED KEY+NONCE: ${keyAndNonce.size} bytes (should be 56)")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Key+Nonce hex: ${keyAndNonce.toHex()}")
|
||||||
|
|
||||||
// 2. Разделяем key и nonce
|
// 2. Разделяем key и nonce
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 2: SPLIT KEY AND NONCE")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
val key = keyAndNonce.slice(0 until 32).toByteArray()
|
val key = keyAndNonce.slice(0 until 32).toByteArray()
|
||||||
val nonce = keyAndNonce.slice(32 until keyAndNonce.size).toByteArray()
|
val nonce = keyAndNonce.slice(32 until keyAndNonce.size).toByteArray()
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ KEY (32 bytes): ${key.toHex()}")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ NONCE (24 bytes): ${nonce.toHex()}")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Key length: ${key.size} bytes")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Nonce length: ${nonce.size} bytes")
|
||||||
|
|
||||||
// 3. Расшифровываем сообщение
|
// 3. Расшифровываем сообщение
|
||||||
return decryptMessage(ciphertext, key.toHex(), nonce.toHex())
|
android.util.Log.d("MessageCrypto", "\n" + "─".repeat(80))
|
||||||
|
android.util.Log.d("MessageCrypto", "⚡ STEP 3: XCHACHA20-POLY1305 DECRYPTION")
|
||||||
|
android.util.Log.d("MessageCrypto", "─".repeat(80))
|
||||||
|
val plaintext = decryptMessage(ciphertext, key.toHex(), nonce.toHex())
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ PLAINTEXT: '$plaintext'")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Plaintext length: ${plaintext.length} chars")
|
||||||
|
android.util.Log.d("MessageCrypto", "✅ Plaintext bytes: ${plaintext.toByteArray().size} bytes")
|
||||||
|
|
||||||
|
android.util.Log.d("MessageCrypto", "\n" + "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "✅✅✅ DECRYPTION COMPLETE ✅✅✅")
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100))
|
||||||
|
android.util.Log.d("MessageCrypto", "FINAL OUTPUT: '$plaintext'")
|
||||||
|
android.util.Log.d("MessageCrypto", "=".repeat(100) + "\n")
|
||||||
|
|
||||||
|
return plaintext
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user