Logo
Published on

第一次學 Kotlin Koog AI 就上手 Day 27:AI 的安全必修課:保護資料與用戶隱私

在前一篇文章中,我們學習了如何使用 Parallel Node 來同時執行多個節點,大幅提升 AI 應用的效能。今天我們要探討一個重要的主題:安全性。當 AI 應用開始處理真實的用戶資料時,保護相關資料和用戶隱私就變得至關重要

想像一下,如果你的聊天機器人意外洩露了用戶的信用卡號碼,或者儲存的用戶對話紀錄被未經授權的人員存取,會造成多大的損失?今天我們將學會兩個核心的安全實踐,讓你的 AI 應用更加安全可靠

為什麼安全性這麼重要?

在開始之前,讓我們先了解 AI 應用面臨的主要安全風險

常見的安全問題

儲存資料洩露

  • 用戶資料以明文形式儲存(最危險!)
  • 敏感資訊未經加密保護
  • 記憶體檔案可被直接讀取

用戶隱私洩露

  • 信用卡號碼、電子郵件等敏感資訊
  • 對話歷史被不當存取
  • 敏感資料傳送到 LLM 服務商

這些問題可能導致嚴重後果:資料外洩、法律問題、用戶信任度下降。但好消息是,Koog 框架為我們提供了完整的解決方案

自定義加密記憶體儲存

還記得 Day 14 我們學習的 Agent 記憶體系統嗎?我們使用 LocalFileMemoryProvider 讓 AI 能夠記住使用者資訊,提供個人化服務。但當時的記憶體內容是以明文 JSON 格式儲存在檔案中,任何人都可以直接讀取

在真實的應用環境中,記憶體中可能包含使用者的個人資訊、偏好設定,甚至是敏感的業務資料。今天我們要學習如何在 Koog 的記憶體系統基礎上,自定義一個安全的加密儲存解決方案

建立安全的加密記憶體管理器

我們將建立一個 SecureStorageManager,它能夠

  • 自動生成安全的 AES-256 加密金鑰
  • 建立加密的記憶體提供者
  • 確保所有儲存的記憶體內容都經過加密保護
class SecureStorageManager {
    /**
     * 生成安全的加密金鑰
     */
    fun generateSecureKey(): String {
        val keyGenerator = KeyGenerator.getInstance("AES")
        keyGenerator.init(256, SecureRandom())
        val secretKey = keyGenerator.generateKey()
        return Base64.getEncoder().encodeToString(secretKey.encoded)
    }

    /**
     * 建立加密的記憶體提供者
     */
    fun createSecureMemoryProvider(): AgentMemoryProvider {

        // 生成安全的加密金鑰
        val encryptionKey = generateSecureKey()

        // 使用 AES-256-GCM 加密演算法
        val encryption = Aes256GCMEncryptor(encryptionKey)

        // 建立加密儲存
        val encryptedStorage = EncryptedStorage(
            fs = JVMFileSystemProvider.ReadWrite,
            encryption = encryption
        )

        // 返回安全的記憶體提供者
        return LocalFileMemoryProvider(
            config = LocalMemoryConfig("secure-memory"),
            storage = encryptedStorage,
            fs = JVMFileSystemProvider.ReadWrite,
            root = Path("secure/data")
        )
    }
}

這個 SecureStorageManager 與 Day 14 的一般 LocalFileMemoryProvider 有以下關鍵差異

一般記憶體儲存(Day 14)

// 明文儲存,任何人都可以讀取檔案內容
val memoryProvider = LocalFileMemoryProvider(
    config = LocalMemoryConfig("personalized-greeter"),
    storage = SimpleStorage(JVMFileSystemProvider.ReadWrite), // 明文儲存
    fs = JVMFileSystemProvider.ReadWrite,
    root = Path("memory/user-data")
)

加密記憶體儲存(今天重點)

// 加密儲存,檔案內容完全加密
val encryptedStorage = EncryptedStorage(
    fs = JVMFileSystemProvider.ReadWrite,
    encryption = Aes256GCMEncryptor(encryptionKey) // AES-256 加密
)
val secureMemoryProvider = LocalFileMemoryProvider(
    // ... 其他參數相同
    storage = encryptedStorage, // 使用加密儲存
    // ...
)

安全性提升

  • 金鑰安全:加密金鑰由系統自動生成,不會寫死在程式碼
  • 記憶體加密:所有儲存的記憶體內容都經過 AES-256-GCM 加密
  • 檔案保護:即使攻擊者獲得儲存檔案,也無法讀取內容
  • API 相容:使用方式與一般記憶體提供者完全相同

實際使用範例

現在讓我們看看如何使用這個加密記憶體提供者。這個範例與 Day 14 的使用方式完全相同,唯一的差別是底層的儲存已經加密了

suspend fun main() {

    val secureStorageManager = SecureStorageManager()

    println("=== 加密儲存的 memory ===")

    // 建立安全的記憶體提供者
    val secureMemoryProvider = secureStorageManager.createSecureMemoryProvider()

    // 定義使用者資訊概念
    val userInfoConcept = Concept(
        "user-info",
        "使用者的基本資訊,包含姓名和偏好",
        FactType.SINGLE
    )

    // 使用者記憶體主題
    val userSubject = object : MemorySubject() {
        override val name: String = "user"
        override val promptDescription: String = "使用者的個人資訊和偏好設定"
        override val priorityLevel: Int = 1
    }

    val name = "cash"
    println("把使用者的姓名 $name 儲存到 memory")

    secureMemoryProvider.save(
        fact = SingleFact(
            concept = userInfoConcept,
            value = name,
            timestamp = System.currentTimeMillis()
        ),
        subject = userSubject,
        scope = MemoryScope.Product("secure-chat")
    )

    val userMemories = secureMemoryProvider.load(
        concept = userInfoConcept,
        subject = userSubject,
        scope = MemoryScope.Product("secure-chat")
    )

    val nameFromMemory = userMemories.firstOrNull()?.let { memory ->
        when (memory) {
            is SingleFact -> memory.value
            else -> null
        }
    }

    println("從 memory 載入使用者的姓名: $nameFromMemory")
}

執行結果

從使用者的角度來看,加密記憶體的使用體驗與一般記憶體完全相同

=== 加密儲存的 memory ===
把使用者的姓名 cash 儲存到 memory
從 memory 載入使用者的姓名: cash

重要觀察

  • API 使用方式與 Day 14 完全相同
  • 儲存和讀取操作透明進行
  • 使用者感受不到任何差異
  • 但底層檔案已經完全加密

檔案內容對比:安全性的顯著提升

讓我們對比一下使用加密儲存前後的檔案內容差異

Day 14 的明文儲存(不安全)

{
  "user-info": [
    {
      "type": "ai.koog.agents.memory.model.SingleFact",
      "concept": {
        "keyword": "user-info",
        "description": "使用者的基本資訊,包含姓名和偏好",
        "factType": "SINGLE"
      },
      "timestamp": 1755224556246,
      "value": "Cash"  ← 使用者姓名明文可見
    }
  ]
}

現在的加密儲存(安全)

0QNPoCLdQ1BUiwbRAdKex69PxQGf70/rZb0jILHq5rFkO9mm+0TkxkMyQl59BDx5xiPaJGD+DPTsMyjNWJZ7dRmNs7n9+FzRK1Dg8Ti0zmZ8D0DXSGH8U/KOL6h60sAm4dG/M89L7k0E/9N0kQsLeKXvAdu+fG0aHf+sbF5Fu8FbHLEAeH0DNyRH3yBAh78cuW4NsB9J1fntrkog6D1LwmJPFnTLWc1JTVuPB7G4gqqIkXHHb2DA3wrxoZA7qtPMvwUjOPsflJ9G0YiemjS4RmHldXkj0mq4JeKOv0zrKzO2dFZysIAGq69DWkt9LT0lZqY1mIpAIvM5yp7Vc8nfpinqeJTqM6bzmK6cocxiiG0okTnEKY2AuRzGJ119+OsgxU7P7lyS11QdcWFXOynRJtDzmFxcaGZuv4N6mG3V+HmtqCWxGG64EjnP4ZWQrPtJwJAHSX6tE7bMV2ctR3OSkNShNUEZRVoz4KJVTzb3CGAnuIPQqsVPncoutphlUCOIhL6zSsMGaqz8PnLDqWgFqNxeiqkLQw==

關鍵差異

  • 明文版本:任何人都能直接讀取使用者的姓名和其他資訊
  • 加密版本:即使有人獲得檔案,看到的只是無意義的加密字串
  • 相同體驗:從開發者和使用者的角度,API 使用方式完全相同
  • 透明加密:加密和解密過程完全自動化,無需手動處理

資料遮罩工具

現在我們來實作一個簡單但實用的資料遮罩工具,用來屏蔽用戶輸入中的敏感資訊

在 AI 應用中,用戶經常會在對話中無意間提到敏感資訊,如信用卡號碼、電子郵件地址等。如果這些資訊直接傳送給 LLM 服務商,可能會造成隱私洩露風險。資料遮罩工具可以在資料傳送前自動檢測並屏蔽這些敏感資訊,確保用戶隱私得到保護

注意:DataSanitizer 是自訂實作的資料遮罩工具,並非 Koog 框架官方提供的功能。此工具用於示範如何處理敏感資料,您可以根據實際需求進行調整和擴展

建立 DataSanitizer

class DataSanitizer {
    // 定義敏感資料的正則表達式模式
    private val sensitivePatterns = mapOf(
        "信用卡號" to Regex("\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b"),
        "電子郵件" to Regex("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b")
    )

    /**
     * 檢查並屏蔽敏感資料
     */
    fun sanitize(text: String): SanitizationResult {
        var sanitizedText = text
        val detectedTypes = mutableListOf<String>()

        // 檢查每種敏感資料類型
        sensitivePatterns.forEach { (type, pattern) ->
            if (pattern.containsMatchIn(sanitizedText)) {
                detectedTypes.add(type)
                // 將敏感資料替換為 [已屏蔽]
                sanitizedText = sanitizedText.replace(pattern, "[已屏蔽]")
            }
        }

        return SanitizationResult(
            originalText = text,
            sanitizedText = sanitizedText,
            detectedTypes = detectedTypes,
            hasSensitiveData = detectedTypes.isNotEmpty()
        )
    }

    /**
     * 只檢查是否包含敏感資料,不進行替換
     */
    fun containsSensitiveData(text: String): Boolean {
        return sensitivePatterns.values.any { it.containsMatchIn(text) }
    }
}

// 遮罩結果資料類別
data class SanitizationResult(
    val originalText: String,       // 原始文字
    val sanitizedText: String,      // 遮罩後文字
    val detectedTypes: List<String>, // 檢測到的敏感資料類型
    val hasSensitiveData: Boolean   // 是否包含敏感資料
)

測試 DataSanitizer

讓我們寫一個簡單的測試來看看效果

可以根據自己的需求,決定如何回應給 AI 或使用者

fun main() {
    val sanitizer = DataSanitizer()

    // 測試案例
    val testInputs = listOf(
        "我的信用卡號是 1234-5678-9012-3456",
        "請聯繫我:[email protected]",
        "我的卡號是 1234567890123456,信箱是 [email protected]",
        "今天天氣很好" // 正常文字
    )

    testInputs.forEach { input ->
        val result = sanitizer.sanitize(input)

        println("原始輸入:$input")
        println("遮罩結果:${result.sanitizedText}")
        if (result.hasSensitiveData) {
            println("檢測到:${result.detectedTypes.joinToString(", ")}")
        }
        println("---")
    }
}

執行結果

原始輸入:我的信用卡號是 1234-5678-9012-3456
遮罩結果:我的信用卡號是 [已屏蔽]
檢測到:信用卡號
---
原始輸入:請聯繫我:john.doe@example.com
遮罩結果:請聯繫我:[已屏蔽]
檢測到:電子郵件
---
原始輸入:我的卡號是 1234567890123456,信箱是 test@gmail.com
遮罩結果:我的卡號是 [已屏蔽],信箱是 [已屏蔽]
檢測到:信用卡號, 電子郵件
---
原始輸入:今天天氣很好
遮罩結果:今天天氣很好
---

安全聊天機器人

我們將資料遮罩整合到一個完整的安全聊天機器人中

class SecureChatBot() {

    private val dataSanitizer = DataSanitizer()

    val agent = AIAgent(
        executor = simpleOpenAIExecutor(ApiKeyManager.openAIApiKey!!),
        systemPrompt = """
            你是一個專業的 AI 助手
        """.trimIndent(),
        llmModel = OpenAIModels.CostOptimized.GPT4_1Mini
    )

    suspend fun chat(userInput: String): String {
        // 檢查用戶輸入是否包含敏感資料
        val sanitizationResult = dataSanitizer.sanitize(userInput)

        if (sanitizationResult.hasSensitiveData) {
            println("⚠️ 檢測到敏感資料:${sanitizationResult.detectedTypes.joinToString(", ")}")
            println("為了保護您的隱私,敏感資訊已被屏蔽")
        }

        // 使用遮罩後的文字與 AI 對話
        val response = try {
            agent.run(sanitizationResult.sanitizedText)
        } catch (e: Exception) {
            "抱歉,處理您的請求時遇到問題。請稍後再試。"
        }

        return response
    }
}

安全聊天機器人使用範例

suspend fun main() {

    val chatBot = SecureChatBot()

    // 模擬用戶對話
    val userInputs = listOf(
        "你好!我想了解一下你的功能",
        "我的信用卡號是 1234-5678-9012-3456,能幫我查詢餘額嗎?",
        "如果有問題可以聯繫我:[email protected]"
    )

    userInputs.forEach { input ->
        println("\n用戶:$input")
        val response = chatBot.chat(input)
        println("機器人:$response")
    }
}

執行 AI 回應內容

執行這個範例時,你會看到

  • 正常對話:普通問題正常回答
  • 敏感資料警告:自動檢測並屏蔽信用卡號和電子郵件
用戶:你好!我想了解一下你的功能
機器人:你好!很高興為你服務。我是一個由人工智慧技術驅動的助手,可以幫助你完成多種任務,包括但不限於:

1. **回答問題**:提供各種領域的知識解答,如科學、歷史、技術、文化等。
2. **語言翻譯**:支援多種語言的翻譯服務。
3. **寫作輔助**:協助撰寫文章、報告、電子郵件、故事等。
4. **學習輔導**:幫助解題、講解概念、提供學習建議。
5. **創意發想**:協助頭腦風暴、產生想法和靈感。
6. **日常生活建議**:如飲食、旅遊規劃、健康資訊等。
7. **程式碼幫助**:提供程式設計建議、範例代碼、錯誤排除等。

如果你有特定需求或問題,隨時告訴我,我會盡力幫助你!
============================================================

用戶:我的信用卡號是 1234-5678-9012-3456,能幫我查詢餘額嗎?
⚠️ 檢測到敏感資料:信用卡號
為了保護您的隱私,敏感資訊已被屏蔽
機器人:對不起,為了保護您的個人隱私和安全,我無法處理或查詢您的信用卡信息。建議您直接聯繫您的銀行客服或使用官方的網路銀行和手機應用程式查詢餘額。如有其他問題,我很樂意幫助!
============================================================

用戶:如果有問題可以聯繫我:user@example.com
⚠️ 檢測到敏感資料:電子郵件
為了保護您的隱私,敏感資訊已被屏蔽
機器人:好的,如果您有任何問題,隨時告訴我,我會幫助您解答!
============================================================

總結

今天我們學習了兩個核心的安全實踐

  • Koog 加密儲存解決方案

    • 自動生成安全的 AES-256 加密金鑰
    • 使用 AES-256-GCM 加密演算法保護用戶資料
    • 確保 Agent 記憶體中的敏感資訊完全加密
  • 簡單實用的資料遮罩工具

    • 使用正則表達式檢測敏感資料
    • 自動屏蔽信用卡號和電子郵件
    • 提供友善的用戶提示

這些基礎的安全措施能有效保護你的 AI 應用,讓用戶更安心地使用你的服務。安全性不是一次性的工作,而是需要持續關注和改進的過程。下一篇文章,我們將學習測試策略與品質保證,探討如何為 AI 應用建立可靠的測試體系,確保生產環境的穩定性和可靠性

相關資源


支持創作

如果這篇文章對您有幫助,歡迎透過 贊助連結 支持我持續創作優質內容。您的支持是我前進的動力!


圖片來源:AI 產生