feat: Enhance chat functionality by updating dialog handling and adding ChatsListViewModel for database integration
This commit is contained in:
@@ -274,83 +274,89 @@ object MessageCrypto {
|
||||
/**
|
||||
* ECDH шифрование ключа для получателя
|
||||
* Использует secp256k1 + AES как в RN версии
|
||||
* Формат: Base64(iv:ciphertext:ephemeralPrivateKeyHex)
|
||||
*/
|
||||
fun encryptKeyForRecipient(keyAndNonce: ByteArray, recipientPublicKeyHex: String): String {
|
||||
val secureRandom = SecureRandom()
|
||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
|
||||
// Генерируем эфемерный приватный ключ
|
||||
// Генерируем эфемерный приватный ключ (32 байта)
|
||||
val ephemeralPrivateKeyBytes = ByteArray(32)
|
||||
secureRandom.nextBytes(ephemeralPrivateKeyBytes)
|
||||
val ephemeralPrivateKey = BigInteger(1, ephemeralPrivateKeyBytes)
|
||||
val ephemeralPrivateKeyHex = ephemeralPrivateKeyBytes.toHex()
|
||||
|
||||
// Получаем эфемерный публичный ключ
|
||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||
val ephemeralPublicKeyHex = ephemeralPublicKey.getEncoded(false).toHex()
|
||||
|
||||
// Парсим публичный ключ получателя
|
||||
val recipientPublicKeyBytes = recipientPublicKeyHex.hexToBytes()
|
||||
val recipientPublicKey = ecSpec.curve.decodePoint(recipientPublicKeyBytes)
|
||||
|
||||
// ECDH: получаем общий секрет
|
||||
// ECDH: ephemeralPrivate * recipientPublic = sharedSecret
|
||||
val sharedPoint = recipientPublicKey.multiply(ephemeralPrivateKey)
|
||||
val sharedSecret = sharedPoint.normalize().xCoord.encoded
|
||||
val sharedSecretHex = sharedSecret.toHex()
|
||||
|
||||
// Derive AES key from shared secret
|
||||
val aesKey = MessageDigest.getInstance("SHA-256").digest(sharedSecret)
|
||||
|
||||
// Генерируем IV для AES
|
||||
// Генерируем IV для AES (16 байт)
|
||||
val iv = ByteArray(16)
|
||||
secureRandom.nextBytes(iv)
|
||||
val ivHex = iv.toHex()
|
||||
|
||||
// Шифруем keyAndNonce с AES-CBC
|
||||
val secretKey = SecretKeySpec(aesKey, "AES")
|
||||
// Шифруем keyAndNonce с AES-CBC используя sharedSecret как ключ
|
||||
// ВАЖНО: Используем сам sharedSecret напрямую (32 байта) без SHA-256!
|
||||
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, IvParameterSpec(iv))
|
||||
val encryptedKey = cipher.doFinal(keyAndNonce)
|
||||
val encryptedKeyHex = encryptedKey.toHex()
|
||||
|
||||
// Формат: iv:ciphertext:ephemeralPublicKey (Base64)
|
||||
val result = ByteArray(iv.size + encryptedKey.size + recipientPublicKeyBytes.size)
|
||||
System.arraycopy(iv, 0, result, 0, iv.size)
|
||||
System.arraycopy(encryptedKey, 0, result, iv.size, encryptedKey.size)
|
||||
// Формат как в RN: btoa(ivHex:encryptedHex:ephemeralPrivateHex)
|
||||
val combined = "$ivHex:$encryptedKeyHex:$ephemeralPrivateKeyHex"
|
||||
|
||||
// Возвращаем как Base64 с ephemeral public key в конце
|
||||
return Base64.encodeToString(iv, Base64.NO_WRAP) + ":" +
|
||||
Base64.encodeToString(encryptedKey, Base64.NO_WRAP) + ":" +
|
||||
ephemeralPublicKeyHex
|
||||
ProtocolManager.addLog("🔐 ECDH Encrypt:")
|
||||
ProtocolManager.addLog(" - Shared secret: ${sharedSecretHex.take(40)}...")
|
||||
ProtocolManager.addLog(" - IV: ${ivHex.take(32)}...")
|
||||
ProtocolManager.addLog(" - Ephemeral private: ${ephemeralPrivateKeyHex.take(40)}...")
|
||||
ProtocolManager.addLog(" - Recipient public: ${recipientPublicKeyHex.take(40)}...")
|
||||
|
||||
return Base64.encodeToString(combined.toByteArray(), Base64.NO_WRAP)
|
||||
}
|
||||
|
||||
/**
|
||||
* ECDH расшифровка ключа
|
||||
* Формат: Base64(ivHex:encryptedHex:ephemeralPrivateHex)
|
||||
*/
|
||||
fun decryptKeyFromSender(encryptedKeyBase64: String, myPrivateKeyHex: String): ByteArray {
|
||||
val parts = encryptedKeyBase64.split(":")
|
||||
if (parts.size != 3) throw IllegalArgumentException("Invalid encrypted key format")
|
||||
val combined = String(Base64.decode(encryptedKeyBase64, Base64.NO_WRAP))
|
||||
val parts = combined.split(":")
|
||||
if (parts.size != 3) throw IllegalArgumentException("Invalid encrypted key format: expected 3 parts, got ${parts.size}")
|
||||
|
||||
val iv = Base64.decode(parts[0], Base64.NO_WRAP)
|
||||
val encryptedKey = Base64.decode(parts[1], Base64.NO_WRAP)
|
||||
val ephemeralPublicKeyHex = parts[2]
|
||||
val ivHex = parts[0]
|
||||
val encryptedKeyHex = parts[1]
|
||||
val ephemeralPrivateKeyHex = parts[2]
|
||||
|
||||
val iv = ivHex.hexToBytes()
|
||||
val encryptedKey = encryptedKeyHex.hexToBytes()
|
||||
|
||||
val ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1")
|
||||
|
||||
// Парсим эфемерный приватный ключ
|
||||
val ephemeralPrivateKey = BigInteger(ephemeralPrivateKeyHex, 16)
|
||||
val ephemeralPublicKey = ecSpec.g.multiply(ephemeralPrivateKey)
|
||||
|
||||
// Парсим мой приватный ключ
|
||||
val myPrivateKey = BigInteger(myPrivateKeyHex, 16)
|
||||
val myPublicKey = ecSpec.g.multiply(myPrivateKey)
|
||||
|
||||
// Парсим эфемерный публичный ключ отправителя
|
||||
val ephemeralPublicKeyBytes = ephemeralPublicKeyHex.hexToBytes()
|
||||
val ephemeralPublicKey = ecSpec.curve.decodePoint(ephemeralPublicKeyBytes)
|
||||
|
||||
// ECDH: получаем общий секрет
|
||||
val sharedPoint = ephemeralPublicKey.multiply(myPrivateKey)
|
||||
// ECDH: ephemeralPrivate * myPublic = sharedSecret
|
||||
val sharedPoint = myPublicKey.multiply(ephemeralPrivateKey)
|
||||
val sharedSecret = sharedPoint.normalize().xCoord.encoded
|
||||
|
||||
// Derive AES key from shared secret
|
||||
val aesKey = MessageDigest.getInstance("SHA-256").digest(sharedSecret)
|
||||
|
||||
// Расшифровываем
|
||||
val secretKey = SecretKeySpec(aesKey, "AES")
|
||||
// Расшифровываем используя sharedSecret как AES ключ
|
||||
val aesKey = SecretKeySpec(sharedSecret, "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, IvParameterSpec(iv))
|
||||
cipher.init(Cipher.DECRYPT_MODE, aesKey, IvParameterSpec(iv))
|
||||
|
||||
return cipher.doFinal(encryptedKey)
|
||||
}
|
||||
@@ -359,15 +365,31 @@ object MessageCrypto {
|
||||
* Полное шифрование сообщения для отправки
|
||||
*/
|
||||
fun encryptForSending(plaintext: String, recipientPublicKey: String): Pair<String, String> {
|
||||
android.util.Log.d("MessageCrypto", "🔐 === START ENCRYPTION ===")
|
||||
android.util.Log.d("MessageCrypto", "📝 Plaintext: '$plaintext'")
|
||||
android.util.Log.d("MessageCrypto", "📝 Plaintext length: ${plaintext.length}")
|
||||
android.util.Log.d("MessageCrypto", "🔑 Recipient public key: ${recipientPublicKey.take(20)}...")
|
||||
|
||||
// 1. Шифруем текст
|
||||
android.util.Log.d("MessageCrypto", "⚡ Step 1: Encrypting message with XChaCha20-Poly1305...")
|
||||
val encrypted = encryptMessage(plaintext)
|
||||
android.util.Log.d("MessageCrypto", "✅ Ciphertext: ${encrypted.ciphertext.take(40)}...")
|
||||
android.util.Log.d("MessageCrypto", "✅ Ciphertext length: ${encrypted.ciphertext.length}")
|
||||
android.util.Log.d("MessageCrypto", "✅ Key: ${encrypted.key.take(20)}...")
|
||||
android.util.Log.d("MessageCrypto", "✅ Nonce: ${encrypted.nonce.take(20)}...")
|
||||
|
||||
// 2. Собираем key + nonce
|
||||
android.util.Log.d("MessageCrypto", "⚡ Step 2: Combining key + nonce...")
|
||||
val keyAndNonce = encrypted.key.hexToBytes() + encrypted.nonce.hexToBytes()
|
||||
android.util.Log.d("MessageCrypto", "✅ Combined keyAndNonce length: ${keyAndNonce.size} bytes")
|
||||
|
||||
// 3. Шифруем ключ для получателя
|
||||
android.util.Log.d("MessageCrypto", "⚡ Step 3: Encrypting key with ECDH + AES...")
|
||||
val encryptedKey = encryptKeyForRecipient(keyAndNonce, recipientPublicKey)
|
||||
android.util.Log.d("MessageCrypto", "✅ Encrypted key: ${encryptedKey.take(40)}...")
|
||||
android.util.Log.d("MessageCrypto", "✅ Encrypted key length: ${encryptedKey.length}")
|
||||
|
||||
android.util.Log.d("MessageCrypto", "🔐 === ENCRYPTION COMPLETE ===")
|
||||
return Pair(encrypted.ciphertext, encryptedKey)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user