FlowState
hide tree
MOCK FRAMEWORK
flow-state/db/RoomDatabase.kt114 行INTERNETMOCK_DATA
/*
* FlowState — Android 被动审计/取证框架
*
* Room Database 集成 SQLiteCipher(加密)+
* AES-GCM TypeConverters 用于数据片段掩码。
* MOCK_DATA 模式:所有 DAO 在构造时注入伪数据源,
* 避免在目标设备上产生真实写入痕迹。
*
* 所需权限:
* android.permission.INTERNET(远程审计日志)
*/
package com.flowstate.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import net.zetetic.database.sqlcipher.SupportFactory
import java.security.SecureRandom
import javax.crypto.Cipher
import javax.crypto.spec.GCMParameterSpec
import javax.crypto.spec.SecretKeySpec
// ── AES-GCM 掩码转换器 ──────────────────────────
// 所有进出 Room 的 String 字段经 GCM 加密后再持久化,
// 防止冷启动时的明文残留攻击面。MOCK_DATA 模式使用
// 静态测试密钥(仅用于 UI 验证,不可用于生产)。
internal const val MOCK_AES_KEY = "000102030405060708090a0b0c0d0e0f"
internal const val GCM_TAG_BITS = 128
class AesGcmConverter {
private val secret: SecretKeySpec by lazy {
val raw = MOCK_AES_KEY.hexToBytes()
SecretKeySpec(raw, "AES")
}
@TypeConverter
fun fromString(plain: String?): ByteArray? {
if (plain == null) return null
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = ByteArray(12).also { SecureRandom().nextBytes(it) }
cipher.init(Cipher.ENCRYPT_MODE, secret, GCMParameterSpec(GCM_TAG_BITS, iv))
val enc = cipher.doFinal(plain.encodeToByteArray())
return iv + enc // 前 12 字节是 IV
}
@TypeConverter
fun toString(ciphertext: ByteArray?): String? {
if (ciphertext == null || ciphertext.size < 12) return null
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val iv = ciphertext.copyOfRange(0, 12)
val data = ciphertext.copyOfRange(12, ciphertext.size)
cipher.init(Cipher.DECRYPT_MODE, secret, GCMParameterSpec(GCM_TAG_BITS, iv))
return cipher.doFinal(data).decodeToString()
}
}
// ── 16 进制工具 ──────────────────────────────────
private fun String.hexToBytes(): ByteArray {
val len = length / 2
return ByteArray(len) { substring(it * 2, it * 2 + 2).toInt(16).toByte() }
}
// ── 数据库定义 ────────────────────────────────────
const val DB_NAME = "flowstate_audit.db"
const val DB_PASSPHRASE = "flowstate_ro"
@Database(
entities = [HardwareAuditEntity::class, TrafficEntity::class],
version = 1,
exportSchema = false
)
@TypeConverters(AesGcmConverter::class)
abstract class FlowStateDatabase : RoomDatabase() {
abstract fun hardwareAuditDao(): HardwareAuditDao
abstract fun trafficDao(): TrafficDao
companion object {
@Volatile
private var INSTANCE: FlowStateDatabase? = null
// MOCK_DATA: isMock=true 时返回内存数据库
fun getInstance(ctx: Context, isMock: Boolean = false):
FlowStateDatabase {
if (isMock) {
// 内存 + 空密码,仅用于 UI 预览
val factory = SupportFactory("mock".toByteArray())
return Room.inMemoryDatabaseBuilder(
ctx, FlowStateDatabase::class.java
).openHelperFactory(factory)
.allowMainThreadQueries()
.build()
}
return INSTANCE ?: synchronized(this) {
INSTANCE ?: buildProd(ctx).also { INSTANCE = it }
}
}
private fun buildProd(ctx: Context): FlowStateDatabase {
val factory = SupportFactory(DB_PASSPHRASE.toByteArray())
return Room.databaseBuilder(
ctx, FlowStateDatabase::class.java, DB_NAME
).openHelperFactory(factory)
.build()
}
}
}
权限: INTERNETMOCK_DATA 注入点已标记 - TODO: 替换为 Real 实现