// // EncryptionService.swift // oAI // // Secure encryption for sensitive data (API keys) // Uses CryptoKit with machine-specific key derivation // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen // // This file is part of oAI. // // oAI is licensed under the PolyForm Noncommercial License 1.0.0. // You may use, study, modify, and share it for any noncommercial // purpose. Commercial use — including selling oAI or any part of // it, standalone or bundled into another product or service — // requires a separate commercial license from the copyright holder. // // See the LICENSE file or // for // the full license text. For commercial licensing, contact Rune // Olsen via . import Foundation import CryptoKit import IOKit class EncryptionService { nonisolated static let shared = EncryptionService() private let encryptionKey: SymmetricKey private init() { self.encryptionKey = Self.deriveEncryptionKey() } // MARK: - Public Interface /// Encrypt a string value nonisolated func encrypt(_ value: String) throws -> String { guard let data = value.data(using: .utf8) else { throw EncryptionError.invalidInput } let sealedBox = try AES.GCM.seal(data, using: encryptionKey) guard let combined = sealedBox.combined else { throw EncryptionError.encryptionFailed } return combined.base64EncodedString() } /// Decrypt a string value nonisolated func decrypt(_ encryptedValue: String) throws -> String { guard let data = Data(base64Encoded: encryptedValue) else { throw EncryptionError.invalidInput } let sealedBox = try AES.GCM.SealedBox(combined: data) let decryptedData = try AES.GCM.open(sealedBox, using: encryptionKey) guard let decryptedString = String(data: decryptedData, encoding: .utf8) else { throw EncryptionError.decryptionFailed } return decryptedString } // MARK: - Key Derivation /// Derive encryption key from machine-specific data private static func deriveEncryptionKey() -> SymmetricKey { let machineUUID = getMachineUUID() let bundleID = Bundle.main.bundleIdentifier ?? "com.oai.oAI" let salt = "oAI-secure-storage-v1" let keyMaterial = "\(machineUUID)-\(bundleID)-\(salt)" let hash = SHA256.hash(data: Data(keyMaterial.utf8)) return SymmetricKey(data: hash) } /// Get machine-specific UUID (IOPlatformUUID) private static func getMachineUUID() -> String { // Get IOPlatformUUID from IOKit let platformExpert = IOServiceGetMatchingService( kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice") ) guard platformExpert != 0 else { // Fallback to a stable identifier if IOKit unavailable return "oai-fallback-uuid" } defer { IOObjectRelease(platformExpert) } guard let uuidData = IORegistryEntryCreateCFProperty( platformExpert, "IOPlatformUUID" as CFString, kCFAllocatorDefault, 0 ) else { return "oai-fallback-uuid" } return (uuidData.takeRetainedValue() as? String) ?? "oai-fallback-uuid" } // MARK: - Errors enum EncryptionError: LocalizedError { case invalidInput case encryptionFailed case decryptionFailed var errorDescription: String? { switch self { case .invalidInput: return "Invalid input data for encryption/decryption" case .encryptionFailed: return "Failed to encrypt data" case .decryptionFailed: return "Failed to decrypt data" } } } }