package com.example.otp_kommunikator import android.content.Context import android.content.SharedPreferences import androidx.security.crypto.EncryptedSharedPreferences import androidx.security.crypto.MasterKey import net.sqlcipher.database.SQLiteDatabase import java.io.File import java.security.KeyStore class OtpKey(val index: String, val keyMaterial: ByteArray, val checksum: Long) { fun destroy() { keyMaterial.fill(0) } } fun getSecurePrefs(context: Context): SharedPreferences { val masterKey = MasterKey.Builder(context, "OTP_Master_Key") .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build() return EncryptedSharedPreferences.create( context, "OTP_Secure_Config", masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } fun initializeSQLCipher(context: Context) { try { SQLiteDatabase.loadLibs(context) } catch (e: Exception) {} } fun wipeAllData(context: Context) { getSecurePrefs(context).edit().clear().commit() val prefsFile = File(context.applicationInfo.dataDir, "shared_prefs/OTP_Secure_Config.xml") if (prefsFile.exists()) { prefsFile.delete() } context.filesDir.listFiles()?.forEach { file -> file.delete() } try { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) keyStore.deleteEntry("OTP_Master_Key") } catch (e: Exception) { } } // NEU: Dateinamen-Salt für absolute Einzigartigkeit auf dem Gerät fun getObfuscatedId(context: Context, contactName: String, isDecoy: Boolean): String { val prefs = getSecurePrefs(context) val deviceSalt = prefs.getString("local_device_salt", "") ?: "" if (deviceSalt.isEmpty()) { throw SecurityException("Systemfehler: Device Salt fehlt. Initialisierung unvollständig.") } val prefix = if (isDecoy) "DECOY_" else "MASTER_" val hashBytes = CryptoManager.getSHA256((deviceSalt + prefix + contactName).toByteArray(Charsets.UTF_8)) return CryptoManager.byteArrayToHex(hashBytes) } fun getContactList(context: Context, isDecoy: Boolean): List { val prefs = getSecurePrefs(context) val key = if (isDecoy) "index_decoy" else "index_master" val listStr = prefs.getString(key, "") ?: "" if (listStr.isEmpty()) return emptyList() return listStr.split(",") } fun addContactToIndex(context: Context, isDecoy: Boolean, contactName: String) { val prefs = getSecurePrefs(context) val key = if (isDecoy) "index_decoy" else "index_master" val current = getContactList(context, isDecoy).toMutableList() if (!current.contains(contactName)) { current.add(contactName) prefs.edit().putString(key, current.joinToString(",")).commit() } } fun removeContactFromIndex(context: Context, isDecoy: Boolean, contactName: String) { val prefs = getSecurePrefs(context) val key = if (isDecoy) "index_decoy" else "index_master" val current = getContactList(context, isDecoy).toMutableList() if (current.contains(contactName)) { current.remove(contactName) prefs.edit().putString(key, current.joinToString(",")).commit() } } fun openSecureDatabase(dbFile: File, password: CharArray, salt: ByteArray): SQLiteDatabase? { if (!dbFile.exists()) return null val masterKeyBytes = CryptoManager.deriveKeyArgon2(password, salt) val sqlCipherKeyBytes = CryptoManager.deriveHKDF(masterKeyBytes, "SQLCIPHER_KEY") masterKeyBytes.fill(0) return try { val db = SQLiteDatabase.openOrCreateDatabase(dbFile.absolutePath, sqlCipherKeyBytes, null) db.rawQuery("PRAGMA secure_delete = ON;", null).close() db } catch (e: android.database.SQLException) { throw SecurityException("Kryptografischer Fehler oder beschädigte Datenbank.", e) } catch (e: Exception) { throw SecurityException("Unerwarteter Fehler beim Öffnen der Datenbank.", e) } finally { sqlCipherKeyBytes.fill(0) } } fun openSecureDatabase(context: Context, dbFileId: String, password: CharArray, salt: ByteArray): SQLiteDatabase? { val dbFile = File(context.filesDir, "$dbFileId.db") return openSecureDatabase(dbFile, password, salt) } fun openLocalDatabase(context: Context, isDecoy: Boolean, password: CharArray, contactName: String): SQLiteDatabase? { val prefs = getSecurePrefs(context) val obfuscatedId = getObfuscatedId(context, contactName, isDecoy) val saltHex = prefs.getString("${obfuscatedId}_argon_salt", "") ?: "" if (saltHex.isEmpty()) return null val salt = CryptoManager.hexToByteArray(saltHex) return openSecureDatabase(context, obfuscatedId, password, salt) } fun getConfigValue(db: SQLiteDatabase, key: String): String { return try { val cursor = db.rawQuery("SELECT config_value FROM config WHERE config_key = ?", arrayOf(key)) var result = "" if (cursor.moveToFirst()) { result = cursor.getString(0) } cursor.close() result } catch (e: Exception) { "" } } fun getNextKey(db: SQLiteDatabase, direction: String): OtpKey? { return try { val cursor = db.rawQuery("SELECT msg_index, key_material, checksum FROM otp_keys WHERE direction = ? ORDER BY rowid ASC LIMIT 1", arrayOf(direction)) var result: OtpKey? = null if (cursor.moveToFirst()) { result = OtpKey(cursor.getString(0), cursor.getBlob(1), cursor.getLong(2)) } cursor.close() result } catch (e: Exception) { null } } fun getKeyByIndex(db: SQLiteDatabase, direction: String, index: String): OtpKey? { return try { val cursor = db.rawQuery("SELECT msg_index, key_material, checksum FROM otp_keys WHERE direction = ? AND msg_index = ?", arrayOf(direction, index)) var result: OtpKey? = null if (cursor.moveToFirst()) { result = OtpKey(cursor.getString(0), cursor.getBlob(1), cursor.getLong(2)) } cursor.close() result } catch (e: Exception) { null } } fun deleteKey(db: SQLiteDatabase, direction: String, index: String) { val deletedRows = db.delete("otp_keys", "direction = ? AND msg_index = ?", arrayOf(direction, index)) if (deletedRows == 0) { throw SecurityException("KRITISCHER FEHLER: Schlüssel konnte nicht gelöscht werden!") } } fun getRemainingKeyCount(db: SQLiteDatabase, direction: String): Int { var count = 0 try { val cursor = db.rawQuery("SELECT COUNT(*) FROM otp_keys WHERE direction = ?", arrayOf(direction)) if (cursor.moveToFirst()) { count = cursor.getInt(0) } cursor.close() } catch (e: Exception) { } return count }