 闽公网安备 35020302035485号
                
                闽公网安备 35020302035485号
                // 天真版本1:直接硬编码
let apiKey = "sk_test_abcdefghijklmnopqrstuvwxyz123456"
// 堆代码 duidaima.com
// 天真版本2:以为放在类里面就安全了
class APIManager {
    static let shared = APIManager()
    private let apiKey = "sk_test_abcdefghijklmnopqrstuvwxyz123456"
}
还有更可爱的做法,我之前真的用过:// 天真版本3:改个变量名,觉得黑客就找不到了 let x837dhg = "sk_test_abcdefghijklmnopqrstuvwxyz123456" // 这名字够随机了吧?嘿嘿现在回想起来都有点脸红。事实是,任何直接硬编码在应用中的密钥,都可以被反编译出来。我用 Hopper 试过,简直不要太简单,几分钟就能把所有字符串都导出来...好了,吐槽完自己的黑历史,来看看正经解决方案。

API_KEY = sk_test_abcdefghijklmnopqrstuvwxyz123456第二步:在 Configuration 中引用它

<key>APIKey</key> <string>$(API_KEY)</string>

if let apiKey = Bundle.main.infoDictionary?["APIKey"] as? String {
    // 开始愉快地调用API
}
这种方法最大的好处就是简单,连我这种懒人都能轻松实现。不过坦白说,这招只能防君子不防小人 —— 虽然源代码中看不到密钥,但编译后的应用里,Info.plist中的密钥是明文保存的,用工具很容易就能提取出来。{
  "MyServiceX": "sk_test_abcdefghijklmnopqrstuvwxyz123456",
  "MyServiceY": "其他密钥"
}

enum KeyConstants {
staticfunc loadAPIKeys() async throws {
    let request = NSBundleResourceRequest(tags: ["APIKeys"])
    try await request.beginAccessingResources()
    
    let url = Bundle.main.url(forResource: "Secrets", withExtension: "json")!
    let data = tryData(contentsOf: url)
    APIKeys.storage = tryJSONDecoder().decode([String: String].self, from: data)
    
    request.endAccessingResources()
  }
enum APIKeys {
    staticfileprivate(set) var storage = [String: String]()
    staticvar myServiceXKey: String { storage["MyServiceX"] ?? "" }
    staticvar myServiceYKey: String { storage["MyServiceY"] ?? "" }
  }
}
在 SwiftUI 应用中这么调用:@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .task {
                    do {
                        try await KeyConstants.loadAPIKeys()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
        }
    }
}
这个方案的安全级别比方案一要高,因为资源不会一开始就打包到应用里,而是按需下载。但说实话,如果有人真的很想破解,这种方法也挡不住,只是增加了一点难度。// 客户端代码
struct APIService {
    func fetchData() async throws -> SomeData {
        // 请求打到自己的服务器,而不是直接去第三方
        let url = URL(string: "https://your-backend-server.com/api/proxy-request")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        // 堆代码 duidaima.com
        // 用自己的认证系统,而不是第三方的API密钥
        request.addValue("Bearer \(userToken)", forHTTPHeaderField: "Authorization")
        
        let (data, _) = try await URLSession.shared.data(for: request)
        returntryJSONDecoder().decode(SomeData.self, from: data)
    }
}
服务器收到请求后,会验证用户身份,然后才使用保存在安全位置的API密钥调用第三方服务。这种方案是我心目中的最佳实践,但说实话,对于小团队来说确实有些重(我自己的side project就舍不得用这个方案,维护成本太高了)。它需要额外维护一个后端服务,增加了不少工作量和成本。不过对于严肃的商业应用,特别是金融类App,这绝对是值得的投入。import CloudKit
import KeychainAccess
class APIKeyManager {
    privatelet container = CKContainer.default()
    privatelet keychain = Keychain(service: "com.yourapp.service")
    func retrieveAPIKey() async {
        do {
            let publicDB = container.publicCloudDatabase
            let predicate = NSPredicate(value: true)
            let query = CKQuery(recordType: "APIKeys", predicate: predicate)
            
            let (records, _) = try await publicDB.records(matching: query)
            iflet record = records.first?.1,
               let encryptedKey = record["encryptedKey"] as? String {
                let apiKey = decryptKey(encryptedApiKey: encryptedKey)
                keychain["apiKey"] = apiKey
            }
        } catch {
            print("Failed to retrieve API key: \(error)")
        }
    }
    
    privatefunc decryptKey(encryptedApiKey: String) -> String {
        // 这里需要实现解密逻辑
        // 我通常用CryptoKit实现,不过代码有点长,这里就省略了
        return"decrypted_key"
    }
}
你需要先在CloudKit控制台创建一个记录类型来存储加密后的API密钥。App启动时,先从CloudKit拉取密钥,解密后存到Keychain里。这种方法挺巧妙的,我个人很喜欢,算是找到了一个不用自建后端又能相对安全的平衡点。缺点是需要用户有iCloud账号,如果做国际化可能会有些问题。// 使用GYB工具在构建时生成混淆密钥
enum APIKeys {
    // 下面这部分每次构建都会自动生成,源代码里看不到真正的密钥
    staticlet obfuscatedKey: String = "\x68\x65\x6c\x6c\x6f"
    staticlet salt: [UInt8] = [0xae, 0xbf, 0x20, ...] // 每次构建不同
    
    staticvar apiKey: String {
        let obfuscatedBytes = [UInt8](obfuscatedKey.utf8)
        let decodedBytes = zip(obfuscatedBytes, salt).map { $0 ^ $1 }
        returnString(bytes: decodedBytes, encoding: .utf8) ?? ""
    }
}
这个方法需要额外设置一个构建脚本,我第一次配置时搞了半天才成功。原理是在构建时用混淆工具自动生成上面这段代码,而不是手写。老实说,这种方法也不是绝对安全,只是增加了破解的难度。但对于黑客来说,如果真想搞定你,花点时间还是能破解的。我一个朋友就是安全专家,他告诉我这种混淆对专业人士其实没太大用,但至少能阻挡住那些随便用工具提取字符串的人。4.独立开发者/小团队的正式产品: 方案四是个不错的折衷,没有后端也能做到相对安全
	
4.监控异常使用情况