Files
plates-server/docs/ios-encryption-guide.md
2025-08-13 15:17:33 +08:00

6.1 KiB
Raw Blame History

iOS端加密对接指南

概述

本文档描述了如何在iOS端实现与服务端兼容的AES-256-GCM加解密功能。

加密规格

  • 算法: AES-256-GCM
  • 密钥长度: 256位 (32字节)
  • IV长度: 96位 (12字节)
  • 认证标签长度: 128位 (16字节)
  • 数据格式: Base64编码的 IV + AuthTag + Ciphertext

环境变量配置

在服务端设置环境变量 ENCRYPTION_KEY,确保密钥安全:

export ENCRYPTION_KEY="your-32-character-secret-key-here"

iOS Swift 实现

1. 创建加密工具类

import Foundation
import CryptoKit

class EncryptionHelper {
    private static let keyLength = 32
    private static let ivLength = 12
    private static let tagLength = 16
    private static let additionalData = "additional-data".data(using: .utf8)!

    // 从配置或钥匙串获取密钥
    private static let encryptionKey: SymmetricKey = {
        let keyString = "your-32-character-secret-key-here" // 应从安全存储获取
        let keyData = keyString.prefix(keyLength).padding(toLength: keyLength, withPad: "0", startingAt: 0).data(using: .utf8)!
        return SymmetricKey(data: keyData)
    }()

    /// 加密数据
    /// - Parameter plaintext: 要加密的明文字符串
    /// - Returns: Base64编码的加密数据格式iv + tag + ciphertext
    static func encrypt(_ plaintext: String) throws -> String {
        guard let data = plaintext.data(using: .utf8) else {
            throw EncryptionError.invalidInput
        }

        // 生成随机IV
        let iv = Data((0..<ivLength).map { _ in UInt8.random(in: 0...255) })

        // 使用AES-GCM加密
        let sealedBox = try AES.GCM.seal(data, using: encryptionKey, nonce: AES.GCM.Nonce(data: iv), authenticating: additionalData)

        // 组合数据IV + Tag + Ciphertext
        var combined = Data()
        combined.append(iv)
        combined.append(sealedBox.tag)
        combined.append(sealedBox.ciphertext)

        return combined.base64EncodedString()
    }

    /// 解密数据
    /// - Parameter encryptedData: Base64编码的加密数据
    /// - Returns: 解密后的明文字符串
    static func decrypt(_ encryptedData: String) throws -> String {
        guard let combined = Data(base64Encoded: encryptedData) else {
            throw EncryptionError.invalidInput
        }

        guard combined.count >= ivLength + tagLength else {
            throw EncryptionError.invalidDataLength
        }

        // 提取各部分
        let iv = combined.prefix(ivLength)
        let tag = combined.dropFirst(ivLength).prefix(tagLength)
        let ciphertext = combined.dropFirst(ivLength + tagLength)

        // 创建密封盒进行解密
        let sealedBox = try AES.GCM.SealedBox(nonce: AES.GCM.Nonce(data: iv), ciphertext: ciphertext, tag: tag)
        let decryptedData = try AES.GCM.open(sealedBox, using: encryptionKey, authenticating: additionalData)

        guard let plaintext = String(data: decryptedData, encoding: .utf8) else {
            throw EncryptionError.decodingFailed
        }

        return plaintext
    }
}

enum EncryptionError: Error {
    case invalidInput
    case invalidDataLength
    case decodingFailed
}

2. 使用示例

// 创建用户数据
struct CreateUserRequest: Codable {
    let id: String
    let name: String
    let mail: String
}

// 加密请求体
struct EncryptedRequest: Codable {
    let encryptedData: String
}

// 加密响应体
struct EncryptedResponse: Codable {
    let success: Bool
    let message: String
    let encryptedData: String?
}

// 使用示例
func createUserWithEncryption() async {
    do {
        // 1. 准备用户数据
        let userData = CreateUserRequest(
            id: "1234567890",
            name: "张三",
            mail: "zhangsan@example.com"
        )

        // 2. 序列化为JSON
        let jsonData = try JSONEncoder().encode(userData)
        let jsonString = String(data: jsonData, encoding: .utf8)!

        // 3. 加密数据
        let encryptedData = try EncryptionHelper.encrypt(jsonString)

        // 4. 创建请求体
        let request = EncryptedRequest(encryptedData: encryptedData)

        // 5. 发送请求
        let response = try await sendEncryptedRequest(request)

        // 6. 解密响应(如果有加密数据)
        if let encryptedResponseData = response.encryptedData {
            let decryptedResponse = try EncryptionHelper.decrypt(encryptedResponseData)
            print("解密后的响应: \(decryptedResponse)")
        }

    } catch {
        print("加密通信失败: \(error)")
    }
}

func sendEncryptedRequest(_ request: EncryptedRequest) async throws -> EncryptedResponse {
    let url = URL(string: "https://your-server.com/users/encrypted")!
    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

    let requestData = try JSONEncoder().encode(request)
    urlRequest.httpBody = requestData

    let (data, _) = try await URLSession.shared.data(for: urlRequest)
    return try JSONDecoder().decode(EncryptedResponse.self, from: data)
}

API 接口

加密版创建用户接口

端点: POST /users/encrypted

请求体:

{
  "encryptedData": "base64编码的加密数据"
}

响应体:

{
  "success": true,
  "message": "用户创建成功",
  "encryptedData": "base64编码的加密响应数据"
}

安全建议

  1. 密钥管理:

    • 生产环境必须使用环境变量或安全密钥管理服务
    • iOS端应将密钥存储在钥匙串中
    • 定期轮换密钥
  2. 传输安全:

    • 始终使用HTTPS
    • 考虑添加请求签名验证
  3. 错误处理:

    • 不要在错误信息中暴露加密细节
    • 记录加密失败日志用于监控
  4. 测试:

    • 确保iOS端和服务端使用相同的密钥
    • 测试各种边界情况和错误场景

故障排除

  1. 解密失败: 检查密钥是否一致
  2. 格式错误: 确认Base64编码格式正确
  3. 数据长度错误: 验证IV和Tag长度是否正确