224 lines
6.1 KiB
Markdown
224 lines
6.1 KiB
Markdown
# iOS端加密对接指南
|
||
|
||
## 概述
|
||
|
||
本文档描述了如何在iOS端实现与服务端兼容的AES-256-GCM加解密功能。
|
||
|
||
## 加密规格
|
||
|
||
- **算法**: AES-256-GCM
|
||
- **密钥长度**: 256位 (32字节)
|
||
- **IV长度**: 96位 (12字节)
|
||
- **认证标签长度**: 128位 (16字节)
|
||
- **数据格式**: Base64编码的 `IV + AuthTag + Ciphertext`
|
||
|
||
## 环境变量配置
|
||
|
||
在服务端设置环境变量 `ENCRYPTION_KEY`,确保密钥安全:
|
||
|
||
```bash
|
||
export ENCRYPTION_KEY="your-32-character-secret-key-here"
|
||
```
|
||
|
||
## iOS Swift 实现
|
||
|
||
### 1. 创建加密工具类
|
||
|
||
```swift
|
||
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. 使用示例
|
||
|
||
```swift
|
||
// 创建用户数据
|
||
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`
|
||
|
||
**请求体**:
|
||
|
||
```json
|
||
{
|
||
"encryptedData": "base64编码的加密数据"
|
||
}
|
||
```
|
||
|
||
**响应体**:
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "用户创建成功",
|
||
"encryptedData": "base64编码的加密响应数据"
|
||
}
|
||
```
|
||
|
||
## 安全建议
|
||
|
||
1. **密钥管理**:
|
||
|
||
- 生产环境必须使用环境变量或安全密钥管理服务
|
||
- iOS端应将密钥存储在钥匙串中
|
||
- 定期轮换密钥
|
||
|
||
2. **传输安全**:
|
||
|
||
- 始终使用HTTPS
|
||
- 考虑添加请求签名验证
|
||
|
||
3. **错误处理**:
|
||
|
||
- 不要在错误信息中暴露加密细节
|
||
- 记录加密失败日志用于监控
|
||
|
||
4. **测试**:
|
||
- 确保iOS端和服务端使用相同的密钥
|
||
- 测试各种边界情况和错误场景
|
||
|
||
## 故障排除
|
||
|
||
1. **解密失败**: 检查密钥是否一致
|
||
2. **格式错误**: 确认Base64编码格式正确
|
||
3. **数据长度错误**: 验证IV和Tag长度是否正确
|