\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..931b96c
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/invincible/jedishare/data/chat/BluetoothDataTransferService.kt b/app/src/main/java/com/invincible/jedishare/data/chat/BluetoothDataTransferService.kt
index 94dd59e..d070e40 100644
--- a/app/src/main/java/com/invincible/jedishare/data/chat/BluetoothDataTransferService.kt
+++ b/app/src/main/java/com/invincible/jedishare/data/chat/BluetoothDataTransferService.kt
@@ -6,6 +6,8 @@ import com.invincible.jedishare.domain.chat.BluetoothMessage
import com.invincible.jedishare.domain.chat.TransferFailedException
import com.invincible.jedishare.presentation.BluetoothViewModel
import com.invincible.jedishare.presentation.components.CustomProgressIndicator
+import com.invincible.jedishare.utils.CryptoUtils
+import com.invincible.jedishare.utils.KeyUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@@ -14,17 +16,43 @@ import kotlinx.coroutines.withContext
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.nio.ByteBuffer
+import javax.crypto.SecretKey
+import java.security.KeyPair
+import java.security.PublicKey
const val BUFFER_SIZE = 990 // Adjust the buffer size as needed
const val FILE_DELIMITER = "----FILE_DELIMITER----"
val repeatedString = FILE_DELIMITER.repeat(40)
class BluetoothDataTransferService(
- private val socket: BluetoothSocket
+ private val socket: BluetoothSocket,
+ private var aesKey: SecretKey? = null
) {
private val incomingDataStream = ByteArrayOutputStream()
+ // Start the Diffie-Hellman Key Exchange
+ fun startKeyExchange(): ByteArray {
+ val keyPair: KeyPair = KeyUtils.generateDHKeyPair()
+ val publicKeyBytes = KeyUtils.serializePublicKey(keyPair.public)
+ // Send the public key bytes to the other device
+ // Store the private key for computing the shared secret later
+ dhPrivateKey = keyPair.private
+ return publicKeyBytes
+ }
+
+ // Complete the Key Exchange and generate the AES key
+ fun completeKeyExchange(otherPublicKeyBytes: ByteArray) {
+ if (dhPrivateKey == null) {
+ Log.e("BluetoothDataTransfer", "Private key is null. Key exchange failed!")
+ throw IllegalStateException("Key exchange must be completed before data transfer.")
+ }
+
+ val otherPublicKey: PublicKey = KeyUtils.deserializePublicKey(otherPublicKeyBytes)
+ val sharedSecret: ByteArray = KeyUtils.generateSharedSecret(dhPrivateKey!!, otherPublicKey)
+ aesKey = KeyUtils.generateAESKeyFromSharedSecret(sharedSecret)
+ }
+
fun listenForIncomingMessages(viewModel: BluetoothViewModel): Flow {
return flow {
if (!socket.isConnected) {
@@ -37,24 +65,36 @@ class BluetoothDataTransferService(
} catch (e: IOException) {
throw TransferFailedException()
}
- var bufferRed = buffer.copyOfRange(0, byteCount)
- Log.e("HELLOME", "Received: " + bufferRed.size.toString())
+ val encryptedChunk = buffer.copyOfRange(0, byteCount)
-// processIncomingData(bufferRed)
-// checkForFiles(viewModel)?.let { fileBytes ->
-// emit(fileBytes)
-// }
- Log.e("MYTAG", "Bytes Read : " + bufferRed.size)
+ // Read the hash digest (for integrity check)
+ val hashBuffer = ByteArray(32)
+ socket.inputStream.read(hashBuffer)
+ val receivedHash = hashBuffer.copyOfRange(0, hashBuffer.size)
- if(bufferRed.size == 880){
- viewModel?.isFirst = true
- }else{
- emit(bufferRed)
+ // Verify the hash to ensure data integrity
+ val computedHash = CryptoUtils.generateSHA256Hash(encryptedChunk)
+ if (!computedHash.contentEquals(receivedHash)) {
+ throw TransferFailedException()
}
-// emit(
-// bufferRed
-// )
+ // Ensure AES key is ready before decrypting
+ if (aesKey == null) {
+ throw TransferFailedException()
+ }
+
+ // Decrypt the chunk using AES
+ val decryptedData = CryptoUtils.decrypt(encryptedChunk, aesKey!!)
+
+ Log.e("BluetoothDataTransfer", "Received and decrypted: ${decryptedData.size} bytes")
+
+ processIncomingData(decryptedData)
+
+ if (decryptedData.size == 880) {
+ viewModel?.isFirst = true
+ } else {
+ emit(decryptedData)
+ }
}
}.flowOn(Dispatchers.IO)
}
@@ -63,58 +103,70 @@ class BluetoothDataTransferService(
incomingDataStream.write(data)
}
+
private fun checkForFiles(viewModel: BluetoothViewModel?): ByteArray? {
val data = incomingDataStream.toByteArray()
- val delimiterIndex = findIndexOfSubArray(incomingDataStream.toByteArray(), repeatedString.toByteArray())
-
-
-// Log.e("MYTAG","delimiter size" + FILE_DELIMITER.toByteArray().size.toString())
-// Log.e("MYTAG","byte array size" + data.size)
-
-// val delimiterIndex = data.indexOf(FILE_DELIMITER.toByteArray())
- if (delimiterIndex == 0) {
-// val fileBytes = data.copyOfRange(0, delimiterIndex)
-// incomingDataStream.reset()
-// Log.e("MYTAG","END OF FILE" + delimiterIndex)
-// Log.e("MYTAG","main array size" + incomingDataStream.toByteArray().size.toString())
-// Log.e("MYTAG","delimiter size" + repeatedString.toByteArray().size.toString())
+ val delimiterIndex = findIndexOfSubArray(data, repeatedString.toByteArray())
+
+ if (delimiterIndex >= 0) {
+ // File boundary found, extract the file data
+ val fileBytes = data.copyOfRange(0, delimiterIndex)
+ incomingDataStream.reset() // Reset the stream after processing the file
+ // Write the remaining bytes back into the stream (after the delimiter)
+ if (delimiterIndex + repeatedString.toByteArray().size < data.size) {
+ incomingDataStream.write(
+ data,
+ delimiterIndex + repeatedString.toByteArray().size,
+ data.size - (delimiterIndex + repeatedString.toByteArray().size)
+ )
+ }
viewModel?.isFirst = true
-// incomingDataStream.write(data, delimiterIndex + FILE_DELIMITER.length, data.size - (delimiterIndex + FILE_DELIMITER.length))
-// return fileBytes
- incomingDataStream.reset()
- return null
-// return incomingDataStream.toByteArray()
+ return fileBytes
}
-// else if(viewModel?.isFirst == true && data.size == 990){
-// incomingDataStream.reset()
-// return null
-// }
+
incomingDataStream.reset()
- return data
+ return null
}
fun findIndexOfSubArray(mainArray: ByteArray, subArray: ByteArray): Int {
- if(mainArray.size == repeatedString.toByteArray().size)
- return 0
-// for (i in 0 until mainArray.size - subArray.size + 1) {
-// if (mainArray.copyOfRange(i, i + subArray.size).contentEquals(subArray)) {
-// return i
-// }
-// }
- return -1 // Return -1 if subArray is not found in mainArray
+ // Properly find the delimiter in the incoming data
+ for (i in 0 until mainArray.size - subArray.size + 1) {
+ if (mainArray.copyOfRange(i, i + subArray.size).contentEquals(subArray)) {
+ return i
+ }
+ }
+ return -1 // Return -1 if subArray is not found
}
suspend fun sendMessage(bytes: ByteArray): Boolean {
return withContext(Dispatchers.IO) {
- try {
- socket.outputStream.write(bytes)
- Log.e("HELLOME", "Sent: " + bytes.size.toString())
+ try {
+ // Ensure AES key is ready before encrypting
+ if (aesKey == null) {
+ throw IllegalStateException("AES key not initialized!")
+ }
+
+ // Encrypt the data chunk using AES
+ val encryptedData = CryptoUtils.encrypt(bytes, aesKey!!)
+
+ // Generate SHA-256 hash for integrity check
+ val hashDigest = CryptoUtils.generateSHA256Hash(encryptedData)
+
+ // Send the encrypted chunk
+ socket.outputStream.write(encryptedData)
- } catch (e: IOException) {
+ // Send the hash digest for integrity verification
+ socket.outputStream.write(hashDigest)
+
+ Log.e("HELLOME", "Sent encrypted data: ${encryptedData.size} bytes")
+ Log.e("HELLOME", "Sent hash digest: ${hashDigest.size} bytes")
+
+ } catch (e: IOException) {
return@withContext false
- }
+ }
- true
+ true
}
}
-}
+ private var dhPrivateKey: java.security.PrivateKey? = null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/invincible/jedishare/utils/CryptoUtils.kt b/app/src/main/java/com/invincible/jedishare/utils/CryptoUtils.kt
new file mode 100644
index 0000000..674f870
--- /dev/null
+++ b/app/src/main/java/com/invincible/jedishare/utils/CryptoUtils.kt
@@ -0,0 +1,36 @@
+package com.invincible.jedishare.utils
+
+import javax.crypto.Cipher
+import javax.crypto.SecretKey
+import javax.crypto.spec.SecretKeySpec
+import java.security.MessageDigest
+
+object CryptoUtils {
+ private const val AES_TRANSFORMATION = "AES/ECB/PKCS5Padding"
+ private const val AES_ALGORITHM = "AES"
+
+ // Encrypt data using AES
+ fun encrypt(data: ByteArray, key: SecretKey): ByteArray {
+ val cipher = Cipher.getInstance(AES_TRANSFORMATION)
+ cipher.init(Cipher.ENCRYPT_MODE, key)
+ return cipher.doFinal(data)
+ }
+
+ // Decrypt data using AES
+ fun decrypt(data: ByteArray, key: SecretKey): ByteArray {
+ val cipher = Cipher.getInstance(AES_TRANSFORMATION)
+ cipher.init(Cipher.DECRYPT_MODE, key)
+ return cipher.doFinal(data)
+ }
+
+ // Generate SHA-256 hash for integrity check
+ fun generateSHA256Hash(data: ByteArray): ByteArray {
+ val digest = MessageDigest.getInstance("SHA-256")
+ return digest.digest(data)
+ }
+
+ // Generate a SecretKey from a byte array (used for generating AES key)
+ fun generateSecretKey(keyBytes: ByteArray): SecretKey {
+ return SecretKeySpec(keyBytes, AES_ALGORITHM)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/invincible/jedishare/utils/KeyUtils.kt b/app/src/main/java/com/invincible/jedishare/utils/KeyUtils.kt
new file mode 100644
index 0000000..80125b9
--- /dev/null
+++ b/app/src/main/java/com/invincible/jedishare/utils/KeyUtils.kt
@@ -0,0 +1,44 @@
+package com.invincible.jedishare.utils
+
+import java.security.KeyPair
+import java.security.KeyPairGenerator
+import java.security.PublicKey
+import java.security.spec.X509EncodedKeySpec
+import javax.crypto.KeyAgreement
+import javax.crypto.SecretKey
+import javax.crypto.spec.SecretKeySpec
+
+object KeyUtils {
+ // Generate Diffie-Hellman Key Pair
+ fun generateDHKeyPair(): KeyPair {
+ val keyPairGenerator = KeyPairGenerator.getInstance("DH")
+ keyPairGenerator.initialize(2048) // Strong 2048-bit key
+ return keyPairGenerator.generateKeyPair()
+ }
+
+ // Serialize public key to send over the network
+ fun serializePublicKey(publicKey: PublicKey): ByteArray {
+ return publicKey.encoded
+ }
+
+ // Deserialize public key received from the other device
+ fun deserializePublicKey(publicKeyBytes: ByteArray): PublicKey {
+ val keyFactory = java.security.KeyFactory.getInstance("DH")
+ val spec = X509EncodedKeySpec(publicKeyBytes)
+ return keyFactory.generatePublic(spec)
+ }
+
+ // Generate shared secret using Diffie-Hellman
+ fun generateSharedSecret(ownPrivateKey: java.security.PrivateKey, otherPublicKey: PublicKey): ByteArray {
+ val keyAgreement = KeyAgreement.getInstance("DH")
+ keyAgreement.init(ownPrivateKey)
+ keyAgreement.doPhase(otherPublicKey, true)
+ return keyAgreement.generateSecret()
+ }
+
+ // Generate AES key from shared secret
+ fun generateAESKeyFromSharedSecret(sharedSecret: ByteArray): SecretKey {
+ // Use the first 16 bytes of the shared secret for AES-128
+ return SecretKeySpec(sharedSecret.copyOf(16), "AES")
+ }
+}
\ No newline at end of file