feat: 初始化项目
This commit is contained in:
223
docs/ios-encryption-guide.md
Normal file
223
docs/ios-encryption-guide.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# 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长度是否正确
|
||||
Reference in New Issue
Block a user