Skip to content

01 - iOS 安全全栈 ​

目录 ​

  1. iOS 安全架构
  2. Keychain 安全存储
  3. 生物识别认证
  4. App Transport Security (ATS)
  5. 数据加密
  6. 代码保护
  7. 权限管理
  8. 安全最佳实践
  9. 安全与 Android 对比
  10. 面试考点汇总

1. iOS 安全架构 ​

1.1 iOS 安全层级 ​

iOS 安全体系分层:

┌─────────────────────────────────────────────────────────┐
│  应用层安全                                          │
│  (Keychain / 生物识别 / App 签名)                     │
├─────────────────────────────────────────────────────────┤
│  数据层安全                                          │
│  (File System Encryption / 数据保护 API)              │
├─────────────────────────────────────────────────────────┤
│  网络层安全                                          │
│  (ATS / TLS / SSL Pinning / 证书校验)                 │
├─────────────────────────────────────────────────────────┤
│  系统层安全                                          │
│  (App Sandbox / Code Signing / Gatekeeper / SIP)      │
├─────────────────────────────────────────────────────────┤
│  硬件层安全                                          │
│  (Secure Enclave / TEE / 硬件级加密)                  │
└─────────────────────────────────────────────────────────┘

1.2 关键安全组件 ​

组件功能安全等级
Secure Enclave硬件级加密密钥/生物识别⭐⭐⭐⭐⭐
Keychain安全密钥存储⭐⭐⭐⭐⭐
Code Signing代码完整性验证⭐⭐⭐⭐
App Sandbox应用隔离⭐⭐⭐⭐
Data Protection文件系统加密⭐⭐⭐⭐
App Transport Security网络传输安全⭐⭐⭐

2. Keychain 安全存储 ​

2.1 Keychain 架构 ​

Keychain 存储原理:

┌──────────────────────────────────────────────────────┐
│                    Keychain                            │
├──────────────────────────────────────────────────────┤
│  Secure Enclave (硬件加密)                             │
│  ↓                                                    │
│  Keychain Services (API)                               │
│  ↓                                                    │
│  Keychain Item (数据项)                                │
│  - kSecClassGenericPassword                          │
│  - kSecClassInternetPassword                         │
│  - kSecClassCertificate                              │
│  - kSecClassKey                                      │
└──────────────────────────────────────────────────────┘

Keychain 特点:
• 数据存储在加密数据库中,受设备唯一密钥保护
• 即使文件系统被破解,数据也无法读取
• 支持 App Group 共享 Keychain 数据
• 可设置访问权限(认证要求/过期时间/设备类型)

2.2 Keychain 读写 ​

swift
import Security

class KeychainService {
    
    private let service: String
    
    init(service: String) {
        self.service = service
    }
    
    // MARK: - 保存数据
    
    func save(_ data: Data, forKey key: String) -> Bool {
        // 先尝试删除已有数据
        delete(forKey: key)
        
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecValueData as String: data,
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
            kSecUseDataProtectionKeychain as String: true
        ]
        
        let status = SecItemAdd(query as CFDictionary, nil)
        return status == errSecSuccess
    }
    
    // MARK: - 读取数据
    
    func load(forKey key: String) -> Data? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        
        var result: AnyObject?
        let status = SecItemCopyMatching(query as CFDictionary, &result)
        
        guard status == errSecSuccess, let data = result as? Data else {
            return nil
        }
        return data
    }
    
    // MARK: - 删除数据
    
    func delete(forKey key: String) {
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: key
        ]
        SecItemDelete(query as CFDictionary)
    }
}

// 访问权限选项
// kSecAttrAccessibleWhenUnlocked     - 解锁时可用
// kSecAttrAccessibleAfterFirstUnlock - 首次解锁后可用
// kSecAttrAccessibleAlways           - 始终可用(不安全)
// kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly - 需面容/指纹
// kSecAttrAccessibleWhenUnlockedThisDeviceOnly - 解锁时仅限本机

2.3 Keychain Access Groups(共享) ​

swift
// App Group Keychain 共享
// 1. 在 Apple Developer Portal 创建相同 App ID Prefix 的 Bundle ID
// 2. 在 Xcode 中开启相同 App Group
// 3. Keychain Access Groups 添加 Group ID

func sharedKeychain(key: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecAttrAccessGroup as String: "ABCD1234.com.example.group",
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    
    var result: AnyObject?
    SecItemCopyMatching(query as CFDictionary, &result)
    return (result as? Data)?.utf8String
}

3. 生物识别认证 ​

3.1 LocalAuthentication 框架 ​

生物识别认证流程:

┌───────┐     ┌──────────────┐     ┌───────────┐
│ 检查可用性 │──→│ 展示认证 UI   │──→│ 认证成功  │
│ (canEvaluate)│     │ (LAContext)  │     │ 返回 token │
└───────┘     └──────────────┘     └───────────┘

支持类型:
• LAContext.deviceOwnerType == .unknown    → 未设置
• LAContext.deviceOwnerType == .faceID     → 面容 ID
• LAContext.deviceOwnerType == .touchID    → 指纹 ID
• LAContext.deviceOwnerType == .notAvailable → 不支持

3.2 生物识别实现 ​

swift
import LocalAuthentication

class BiometricAuthenticator {
    
    enum AuthenticationResult {
        case success(token: String)
        case failed(Error)
        case unavailable
    }
    
    static func authenticate(
        reason: String = "需要身份验证",
        completion: @escaping (AuthenticationResult) -> Void
    ) {
        let context = LAContext()
        var error: NSError?
        
        // 检查是否支持
        guard context.canEvaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            error: &error
        ) else {
            completion(.unavailable)
            return
        }
        
        context.evaluatePolicy(
            .deviceOwnerAuthenticationWithBiometrics,
            localizedReason: reason
        ) { success, evaluationError in
            DispatchQueue.main.async {
                if success {
                    // 生成访问 token
                    let token = Self.generateToken()
                    completion(.success(token: token))
                } else {
                    completion(.failed(evaluationError ?? NSError()))
                }
            }
        }
    }
    
    static func generateToken() -> String {
        // 认证成功后可安全访问敏感数据
        return UUID().uuidString
    }
}

// 使用
BiometricAuthenticator.authenticate(reason: "验证身份以访问数据") { result in
    switch result {
    case .success(let token):
        // 使用 token 访问 Keychain 数据
    case .failed(let error):
        print("认证失败: \(error.localizedDescription)")
    case .unavailable:
        // 显示其他登录方式
    }
}

3.3 Face ID / Touch ID 配置 ​

Info.plist 配置:
├── NSFaceIDUsageDescription
│   "需要使用面容 ID 验证您的身份"
├── NSTouchIDUsageDescription
│   "需要使用指纹 ID 验证您的身份"
└── LSSupportsSheetAppearance
    → true (macOS)

4. App Transport Security (ATS) ​

4.1 ATS 默认安全策略 ​

ATS 默认要求:
• 必须使用 TLS 1.2 或更高版本
• 证书必须使用 SHA-256 哈希
• 公钥:RSA ≥ 2048 位,ECC ≥ 256 位
• 协议:TLS 1.2+,不支持 SSL
• 不支持自签名证书
• 不支持 HTTP 明文传输

4.2 ATS 配置 ​

xml
<!-- Info.plist 中配置 ATS -->
<key>NSAppTransportSecurity</key>
<dict>
    <!-- 全局允许 HTTP(不推荐) -->
    <key>NSAllowsArbitraryLoads</key>
    <false/>
    
    <!-- 为特定域名设置例外 -->
    <key>NSExceptionDomains</key>
    <dict>
        <key>example.com</key>
        <dict>
            <!-- 允许 HTTP -->
            <key>NSExceptionAllowsInsecureHTTPLoads</key>
            <true/>
            <!-- 包含子域名 -->
            <key>NSIncludesSubdomains</key>
            <true/>
            <!-- 最小 TLS 版本 -->
            <key>NSTemporaryExceptionMinimumTLSVersion</key>
            <string>TLSv1.2</string>
            <!-- 允许弱加密套件 -->
            <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
            <true/>
        </dict>
    </dict>
</dict>

4.3 SSL Pinning ​

swift
// SSL Pinning - 证书绑定
class SSLPinningSession: NSObject, URLSessionDelegate {
    
    private let session: URLSession
    
    init() {
        let delegate = self
        self.session = URLSession(configuration: .default,
                                  delegate: delegate,
                                  delegateQueue: nil)
    }
    
    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition,
                                                   URLCredential?) -> Void) {
        guard let serverTrust = challenge.protectionSpace.serverTrust,
              let cert = SecTrustCopyCertificateChain(serverTrust)?.first
        else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        // 绑定预期证书
        if isPinnedCertificate(cert) {
            let credential = URLCredential(trust: serverTrust)
            completionHandler(.useCredential, credential)
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
    
    private func isPinnedCertificate(_ cert: SecCertificate) -> Bool {
        // 加载本地绑定的证书
        guard let pinPath = Bundle.main.path(
            forResource: "server", ofType: "der"),
            let pinData = FileManager.default.contents(atPath: pinPath),
            let pinCert = SecCertificateCreateWithData(nil, pinData as CFData)
        else { return false }
        
        // 比较证书
        let serverData = SecCertificateCopyData(cert) as Data
        let pinData = SecCertificateCopyData(pinCert) as Data
        return serverData.isEqual(pinData)
    }
}

5. 数据加密 ​

5.1 数据保护 API ​

文件数据保护级别:

┌─────────────────────────────────────────────┐
│  数据保护级别                                │
├─────────────────────────────────────────────┤
│ NSFileProtectionComplete                     │
│   设备锁定后不可访问                         │
│   (默认,推荐用于敏感数据)                    │
├─────────────────────────────────────────────┤
│ NSFileProtectionCompleteUnlessOpen           │
│   文件打开时可访问,锁定后下次打开不可访问     │
├─────────────────────────────────────────────┤
│ NSFileProtectionCompleteUntilFirstUserAuth   │
│   首次用户认证后可访问                         │
├─────────────────────────────────────────────┤
│ NSFileProtectionNone                         │
│   无保护(不推荐用于敏感数据)                │
└─────────────────────────────────────────────┘

应用层:
NSFileProtectionComplete → 文件级保护
UISceneConfiguration → 场景级保护

5.2 CryptoKit 加密 ​

swift
import CryptoKit

// AES-GCM 对称加密
func encrypt(plaintext: String, key: SymmetricKey) -> (ciphertext: Data, sealedBox: SealedBox)? {
    guard let data = plaintext.data(using: .utf8) else { return nil }
    
    let sealedBox = try? SymmetricKey(size: .bits256)
        .sealed(to: data)
    return (sealedBox.combined, sealedBox)
}

func decrypt(sealedBox: SealedBox, key: SymmetricKey) -> String? {
    guard let decrypted = try? sealedBox.open(using: key) else { return nil }
    return String(data: decrypted, encoding: .utf8)
}

// 生成密钥并存储到 Keychain
let key = SymmetricKey(size: .bits256)
let keyData = key.withUnsafeBytes { Data($0) }
// 将 keyData 存入 Keychain

// SHA-256 哈希
let message = "hello world"
let hash = SHA256.hash(data: message.utf8)
let hexHash = hash.compactMap { String(format: "%02x", $0) }.joined()

// HMAC
let hmac = HMAC<SHA256>.authenticationCode(
    for: message.utf8,
    using: key
)

5.3 密码哈希 ​

swift
// 密码存储不应使用可逆加密,应使用哈希
func hashPassword(_ password: String) -> String {
    // 推荐使用 Argon2 或 bcrypt
    // iOS 可使用 CommonCrypto
    // 生产环境推荐第三方库
    return "使用 Argon2/bcrypt 哈希密码"
}

6. 代码保护 ​

6.1 代码签名与验证 ​

代码签名流程:

┌────────┐    ┌────────┐    ┌────────┐    ┌──────┐
│ 开发者  │──→│ 签名证书 │──→│ App 打包 │──→│ Gate │
│ 证书    │    │ 签名     │    │ .app    │    │ keeper │
└────────┘    └────────┘    └───────┘    └──────┘

Gatekeeper 验证:
1. 检查代码签名签名
2. 验证签名者身份(Apple 认证)
3. 检查 App Store 公证(Notarization)
4. 验证 App Sandbox 配置

6.2 Jitter / Obfuscation ​

代码混淆策略:

┌───────────┬───────────────────────────┐
│ 混淆类型    │ 方法                       │
├───────────┼───────────────────────────┤
│ 类名混淆   │ 使用外部工具(如 Obfuscator-LLVM)│
│ 字符串混淆 │ Runtime 解密字符串             │
│ 控制流混淆 │ 插入无意义分支                   │
│ 函数调用   │ 符号化/非符号化                  │
│ 调试检测   │ 反调试代码                     │
└───────────┴───────────────────────────┘

6.3 防调试检测 ​

swift
// 调试检测
func isDebuggerAttached() -> Bool {
    var info = kinfo_ctl()
    var size = MemoryLayout<kinfo_proc>.size
    let name = "sys.procinfo"
    var nameBytes = name.utf8 + [0]
    let result = sysctl(&nameBytes, 2, &info, &size, nil, 0)
    guard result == 0 else { return false }
    
    let pids = UnsafeMutablePointer<kinfo_proc>.allocate(capacity: 1)
    pids.pointee = info
    let pid = pids.pointee.kp_proc.p_flag & P_TRACED
    pids.deallocate()
    return pid != 0
}

// 反调试
func antiDebug() {
    if isDebuggerAttached() {
        // 终止 app 或上报
        exit(1)
    }
}

7. 权限管理 ​

7.1 iOS 权限类型 ​

iOS 权限分类:

┌──────────────────────────────────────────┐
│              权限类型                        │
├──────────────────────────────────────────┤
│ 隐私权限(Info.plist 声明)                  │
│ • Camera         - 相机                    │
│ • Photos         - 照片库                   │
│ • Location       - 定位                     │
│ • Microphone     - 麦克风                    │
│ • Bluetooth      - 蓝牙                    │
│ • Notification   - 通知                    │
│ • Calendar       - 日历                    │
│ • Contacts       - 通讯录                   │
│ • Health         - 健康                    │
│ • HomeKit        - HomeKit                  │
├──────────────────────────────────────────┤
│ 框架权限(运行时请求)                        │
│ • AVAudioSession.requestPermission()     │
│ • PHPhotoLibrary.requestAuthorization() │
│ • CLLocationManager.requestWhenInUseAuthorization() │
│ • CNContactStore.requestAccess(for:)     │
└─────────────────────────────────────────┘

7.2 权限请求 ​

swift
// 相机权限
func requestCameraPermission() {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        startCamera()
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            if granted { startCamera() }
        }
    case .denied, .restricted:
        showSettingsAlert()
    @unknown default:
        break
    }
}

// 照片权限(iOS 14+ 分級別)
func requestPhotoPermission() {
    PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
        switch status {
        case .limited, .authorized:
            // 有权限
        case .denied, .restricted:
            // 无权限
        default:
            break
        }
    }
}

// 健康数据
func requestHealthPermission() {
    let healthKitTypes: Set<HKType> = [
        HKObjectType.workoutType(),
        HKObjectType.quantityType(forIdentifier: .activeEnergyBurned)!,
    ]
    
    HKHealthStore().requestSharePermission(for: healthKitTypes) { granted, error in
        // 结果处理
    }
}

8. 安全最佳实践 ​

8.1 安全检查清单 ​

iOS 安全检查清单:

□ App 签名正确(非开发模式)
□ ATS 配置正确(不随意关闭)
□ 敏感数据存 Keychain(不存 UserDefaults)
□ Keychain 数据设置正确访问权限
□ 密码使用哈希(非明文/可逆加密)
□ SSL Pinning 用于关键 API
□ 敏感数据不在内存中残留(使用 SecureString)
□ 生物识别需要 Face ID/Touch ID
□ 不硬编码密钥/证书
□ 不保存用户密码
□ Info.plist 权限声明完整
□ 日志中不输出敏感信息
□ HTTPS 验证证书
□ 不存储敏感数据到 UserDefaults
□ 使用 UISecureTextField 输入密码
□ 禁用自动补全(autocorrection = false)
□ 剪贴板清除(粘贴后清除敏感数据)
□ App Group 中 Keychain 共享安全
□ 定期更新依赖库安全补丁

8.2 安全编码规则 ​

规则说明示例
不硬编码密钥密钥放 Keychain 或服务器❌ "let apiKey = "xxx""
不存密码到本地使用 Keychain❌ UserDefaults.standard.set(pwd, forKey: "pwd")
使用 HTTPS传输加密ATS 强制 HTTPS
验证服务器SSL Pinning证书绑定
最小权限只请求需要的权限按需请求
输入验证防止注入攻击URL/SQL/命令验证
日志脱敏不记录敏感信息替换 Token/密码
内存清理使用后清除memset 敏感内存

9. 安全与 Android 对比 ​

概念iOSAndroid/Kotlin
密钥存储KeychainKeystore
生物识别LocalAuthenticationBiometricPrompt
应用沙盒App SandboxApp Sandbox (UID/GID)
代码签名强制签名签名(APK/AAB)
传输安全ATSNetwork Security Config
加密CryptoKitAndroidKeystore + Jetpack Security
权限Info.plist 声明 + 运行时AndroidManifest + runtime
系统级安全Secure EnclaveTrusted Execution Environment (TEE)
反调试sysctl / ptraceptrace / /proc/self
代码混淆Obfuscator-LLVMProGuard/R8

10. 面试考点汇总 ​

高频面试题 ​

  1. Keychain 的安全性如何保证?

    • 存储在系统加密数据库中,受设备唯一密钥保护
    • 数据加密存储在 Secure Enclave
    • 即使文件系统被破解,数据也无法读取
    • 支持设置访问权限(认证要求/过期时间)
  2. 生物识别的实现流程?

    • LocalAuthentication 框架
    • 检查设备是否支持
    • 展示认证界面
    • 成功后可安全访问 Keychain 数据
  3. ATS 的作用和安全配置?

    • 强制 HTTPS 传输
    • TLS 1.2+ 要求
    • 通过 Info.plist 配置例外
    • 不推荐关闭 ATS
  4. SSL Pinning 的原理?

    • 绑定服务器证书公钥
    • 在 URLSessionDelegate 中验证
    • 防止中间人攻击
    • 证书不匹配时断开连接
  5. iOS 数据加密有哪些方式?

    • CryptoKit(AES-GCM, SHA-256, HMAC)
    • 数据保护 API(文件级)
    • Keychain(密钥/凭证)
    • CommonCrypto(C API)
  6. 安全最佳实践?

    • 敏感数据存 Keychain
    • 密码用哈希
    • ATS 强制 HTTPS
    • 按需请求权限
    • 代码混淆/反调试

面试回答模板 ​

"iOS 安全核心是 Keychain 存储敏感数据,Secure Enclave 硬件加密。生物识别用 LocalAuthentication,ATS 强制网络加密。数据用 CryptoKit 加密,密码用哈希。安全最佳实践:不硬编码密钥、不存密码到 UserDefaults、最小权限原则、定期更新依赖。"


本文档对标 Android 25_Security 的深度