diff --git a/app/src/main/java/com/dano/test1/DatabaseDownloader.kt b/app/src/main/java/com/dano/test1/DatabaseDownloader.kt index 64060ae..107c37f 100644 --- a/app/src/main/java/com/dano/test1/DatabaseDownloader.kt +++ b/app/src/main/java/com/dano/test1/DatabaseDownloader.kt @@ -9,6 +9,9 @@ import okhttp3.OkHttpClient import okhttp3.Request import java.io.File import java.io.FileOutputStream +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec object DatabaseDownloader { @@ -16,6 +19,9 @@ object DatabaseDownloader { private const val API_TOKEN = "MEIN_SUPER_GEHEIMES_TOKEN_12345" private const val SERVER_DOWNLOAD_URL = "http://49.13.157.44/downloadFull.php?token=$API_TOKEN" + // AES-256 Key (muss exakt 32 Bytes lang sein) + private const val AES_KEY = "12345678901234567890123456789012" + private val client = OkHttpClient() fun downloadAndReplaceDatabase(context: Context) { @@ -33,7 +39,8 @@ object DatabaseDownloader { return@launch } - val downloadedFile = File(context.cacheDir, "downloaded_database") + // Zwischenspeichern der verschlüsselten Datei + val downloadedFile = File(context.cacheDir, "downloaded_database.enc") response.body?.byteStream()?.use { input -> FileOutputStream(downloadedFile).use { output -> input.copyTo(output) @@ -41,15 +48,34 @@ object DatabaseDownloader { } Log.d("DOWNLOAD", "Datei gespeichert: ${downloadedFile.absolutePath}") + // Entschlüsselung + val decryptedBytes = decryptFile(downloadedFile) val dbFile = context.getDatabasePath(DB_NAME) if (dbFile.exists()) dbFile.delete() - downloadedFile.copyTo(dbFile, overwrite = true) + FileOutputStream(dbFile).use { fos -> + fos.write(decryptedBytes) + } - Log.d("DOWNLOAD", "Neue DB erfolgreich eingesetzt") + Log.d("DOWNLOAD", "Neue DB erfolgreich entschlüsselt und eingesetzt") } catch (e: Exception) { Log.e("DOWNLOAD", "Fehler beim Download oder Ersetzen der DB", e) } } } + + private fun decryptFile(file: File): ByteArray { + val fileBytes = file.readBytes() + if (fileBytes.size < 16) throw IllegalArgumentException("Datei zu kurz, kein IV vorhanden") + + val iv = fileBytes.copyOfRange(0, 16) + val cipherBytes = fileBytes.copyOfRange(16, fileBytes.size) + + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + val keySpec = SecretKeySpec(AES_KEY.toByteArray(Charsets.UTF_8), "AES") + val ivSpec = IvParameterSpec(iv) + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec) + + return cipher.doFinal(cipherBytes) + } } diff --git a/app/src/main/java/com/dano/test1/DatabaseUploader.kt b/app/src/main/java/com/dano/test1/DatabaseUploader.kt index e928f8a..4650ed9 100644 --- a/app/src/main/java/com/dano/test1/DatabaseUploader.kt +++ b/app/src/main/java/com/dano/test1/DatabaseUploader.kt @@ -9,17 +9,20 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import android.database.Cursor import okhttp3.* +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.asRequestBody import org.json.JSONArray import org.json.JSONObject import java.io.File +import java.io.FileOutputStream import java.io.IOException import kotlin.system.exitProcess object DatabaseUploader { private const val DB_NAME = "questionnaire_database" - // TODO entferne uploadDeltaTest.php - private const val SERVER_DELTA_URL = "http://49.13.157.44/uploadDeltaTest.php" + // TODO entferne uploadDeltaTest2.php + private const val SERVER_DELTA_URL = "http://49.13.157.44/uploadDeltaTest2.php" private const val SERVER_CHECK_URL = "http://49.13.157.44/checkDatabaseExists.php" private const val API_TOKEN = "MEIN_SUPER_GEHEIMES_TOKEN_12345" @@ -57,10 +60,10 @@ object DatabaseUploader { val exists = checkDatabaseExists() if (exists) { Log.d("UPLOAD", "Server-Datenbank vorhanden → Delta-Upload") - uploadPseudoDelta(dbFile) + uploadPseudoDelta(context, dbFile) } else { Log.d("UPLOAD", "Keine Server-Datenbank → Delta-Upload") - uploadPseudoDelta(dbFile) + uploadPseudoDelta(context, dbFile) } } catch (e: Exception) { @@ -95,7 +98,15 @@ object DatabaseUploader { } } - private fun uploadPseudoDelta(file: File) { + /** + * Wichtig: Diese Funktion wurde erweitert, sodass: + * - die DB als JSON in eine temporäre Datei geschrieben wird, + * - diese JSON-Datei AES-verschlüsselt wird (mit AES256Helper.encryptFile), + * - die verschlüsselte Datei als Multipart 'file' an den Server gesendet wird. + * + * (Funktionalität: gleiche Signatur wie vorher behalten) + */ + private fun uploadPseudoDelta(context: Context, file: File) { try { val db = SQLiteDatabase.openDatabase(file.absolutePath, null, SQLiteDatabase.OPEN_READONLY) @@ -115,10 +126,30 @@ object DatabaseUploader { db.close() + // Schreibe JSON in temporäre Datei + val tmpJson = File(context.cacheDir, "payload.json") + tmpJson.writeText(data.toString()) + + // Verschlüssele JSON -> tmpEnc + val tmpEnc = File(context.cacheDir, "payload.enc") + try { + AES256Helper.encryptFile(tmpJson, tmpEnc) + } catch (e: Exception) { + Log.e("UPLOAD", "Fehler bei der Verschlüsselung der JSON-Datei", e) + // cleanup + tmpJson.delete() + return + } + val requestBody = MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("token", API_TOKEN) - .addFormDataPart("data", data.toString()) + // Datei-Feld "file" mit verschlüsselter Payload + .addFormDataPart( + "file", + "payload.enc", + tmpEnc.asRequestBody("application/octet-stream".toMediaType()) + ) .build() val request = Request.Builder() @@ -129,6 +160,9 @@ object DatabaseUploader { client.newCall(request).enqueue(object : Callback { override fun onFailure(call: Call, e: IOException) { Log.e("UPLOAD", "Delta-Upload fehlgeschlagen: ${e.message}") + // cleanup + tmpJson.delete() + tmpEnc.delete() } override fun onResponse(call: Call, response: Response) { @@ -150,9 +184,14 @@ object DatabaseUploader { if (journalFile.exists() && journalFile.delete()) { Log.d("UPLOAD", "Journal-Datei gelöscht.") } + // cleanup temp files + tmpJson.delete() + tmpEnc.delete() exitProcess(0) } else { Log.e("UPLOAD", "Delta-Upload fehlgeschlagen: ${response.code} $body") + tmpJson.delete() + tmpEnc.delete() } } })