Files
oai-swift/oAI/Services/EncryptionService.swift

115 lines
3.3 KiB
Swift

//
// EncryptionService.swift
// oAI
//
// Secure encryption for sensitive data (API keys)
// Uses CryptoKit with machine-specific key derivation
//
import Foundation
import CryptoKit
import IOKit
class EncryptionService {
static let shared = EncryptionService()
private let salt = "oAI-secure-storage-v1" // App-specific salt
private lazy var encryptionKey: SymmetricKey = {
deriveEncryptionKey()
}()
private init() {}
// MARK: - Public Interface
/// Encrypt a string value
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
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 func deriveEncryptionKey() -> SymmetricKey {
// Combine machine UUID + bundle ID + salt for key material
let machineUUID = getMachineUUID()
let bundleID = Bundle.main.bundleIdentifier ?? "com.oai.oAI"
let keyMaterial = "\(machineUUID)-\(bundleID)-\(salt)"
// Hash to create consistent 256-bit key
let hash = SHA256.hash(data: Data(keyMaterial.utf8))
return SymmetricKey(data: hash)
}
/// Get machine-specific UUID (IOPlatformUUID)
private 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"
}
}
}
}