Added encryption for the case, that no database on the server exists and decryption.

This commit is contained in:
oxidiert
2025-08-16 23:36:01 +02:00
parent e6c2526529
commit 4a22f90269
2 changed files with 74 additions and 9 deletions

View File

@ -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)
}
}

View File

@ -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()
}
}
})