254 lines
11 KiB
Markdown
254 lines
11 KiB
Markdown
# 🔐 安全设备 Demo — Android + Spring Boot
|
||
|
||
> **场景**:高权限设备端(无登录)+ 用户端(有登录体系)+ SN 绑定 + 私密照片加密
|
||
> **目标**:设备端 TEE 密钥不可导出、服务端零知识、恢复出厂后可安全恢复
|
||
|
||
---
|
||
|
||
## 📐 架构总览
|
||
|
||
```
|
||
┌─────────────────────┐ HTTPS ┌──────────────────────┐
|
||
│ Android 设备端 │ ◄──────────────────► │ Spring Boot 后端 │
|
||
│ (无登录/高权限) │ │ (用户有登录体系) │
|
||
└─────────────────────┘ └──────────────────────┘
|
||
│ │
|
||
│ ① TEE 密钥对(Keystore) │ ① 用户主密钥(UK)
|
||
│ ② AES-GCM 信封加密照片 │ ② DEK 信封加密存储
|
||
│ ③ ECDSA 签名元数据 │ ③ SN 绑定 + 短信验证
|
||
│ ④ 恢复出厂 → 新密钥对 │ ④ Recovery Token 下发
|
||
│ ⑤ UK 解 DEK → 设备公钥加密下发
|
||
```
|
||
|
||
---
|
||
|
||
## 🔑 密钥分层模型
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ User Key (UK) — 用户主密钥 │
|
||
│ ├── 生成:用户注册时创建(AES-256) │
|
||
│ ├── 存储:服务端加密存储(生产环境用 KMS 托管) │
|
||
│ └── 用途:加密所有 DEK │
|
||
│ │
|
||
│ Data Encryption Key (DEK) — 每照片一个 │
|
||
│ ├── 生成:设备端 SecureRandom(每次随机) │
|
||
│ ├── 加密:UK 加密后存服务端 │
|
||
│ └── 用途:AES-256-GCM 加密照片 │
|
||
│ │
|
||
│ Device Key Pair — TEE 硬件密钥 │
|
||
│ ├── 生成:Android Keystore(StrongBox 优先) │
|
||
│ ├── 私钥:永不导出,仅签名/解密 │
|
||
│ ├── 公钥:上传服务端,用于加密下发 │
|
||
│ └── 销毁:恢复出厂时自动清除 │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 完整流程
|
||
|
||
### Phase 1:设备注册 + 用户绑定
|
||
|
||
```
|
||
设备端 后端 用户端
|
||
│ │ │
|
||
│── 生成 TEE 密钥对 ──────────│ │
|
||
│── POST /api/device/register │ │
|
||
│ {sn, publicKey} ──►│ │
|
||
│ │── 存储 device(sn, pubKey) │
|
||
│◄── {deviceId} ─────────────│ │
|
||
│ │ │
|
||
│ │◄── POST /api/device/bind │
|
||
│ │ {userId, sn} │
|
||
│ │── 绑定 userId ↔ deviceId │
|
||
│ │◄── {ok} ────────────────────│
|
||
```
|
||
|
||
### Phase 2:拍照 → 信封加密 → 上传
|
||
|
||
```
|
||
设备端 后端
|
||
│ │
|
||
│── SecureRandom → DEK (256bit)│
|
||
│── AES-GCM(photo, DEK) → ct │
|
||
│── Sign(metadata, PrivKey) │
|
||
│ │
|
||
│── POST /api/photo/upload │
|
||
│ {sn, ct, iv, dek, sig} ──►│
|
||
│ │── 验签(PubKey)
|
||
│ │── wrapDEK(dek, UK) → encDEK
|
||
│ │── 存储 {ct, iv, encDEK, sig}
|
||
│◄── {photoId} ───────────────│
|
||
```
|
||
|
||
### Phase 3:恢复出厂 → 短信验证 → 恢复
|
||
|
||
```
|
||
设备端(新) 后端 用户端
|
||
│ │ │
|
||
│── 新 TEE 密钥对 │ │
|
||
│── POST /api/device/register │ │
|
||
│ {sn, newPubKey} ──►│(旧设备自动停用) │
|
||
│◄── {newDeviceId} ───────────│ │
|
||
│ │ │
|
||
│ │◄── POST /api/device/sms/send │
|
||
│ │ {phone} │
|
||
│ │── 发送短信 │
|
||
│ │ │
|
||
│ │◄── POST /api/device/recover │
|
||
│ │ {userId, sn, smsCode, │
|
||
│ │ newPubKey} │
|
||
│ │── ① 验证短信 │── 输入验证码
|
||
│ │── ② 确认 SN 归属 │
|
||
│ │── ③ 生成 Recovery Token │
|
||
│ │── ④ RSA(newPubKey, Token) │
|
||
│◄── {encToken, nonce} ────────│ │
|
||
│ │ │
|
||
│── PrivKey 解密 Token │ │
|
||
│── POST /api/photo/recover │ │
|
||
│ {deviceId, token} ──►│ │
|
||
│ │── ① 验证 Token │
|
||
│ │── ② 遍历照片 │
|
||
│ │── ③ UK 解 DEK → PubKey 加密 │
|
||
│◄── [{photoId, encDEK}, ...] ─│ │
|
||
│ │ │
|
||
│── PrivKey 解密每个 DEK │ │
|
||
│── AES-GCM 解密照片 │ │
|
||
│── ✅ 照片恢复完成 │ │
|
||
```
|
||
|
||
---
|
||
|
||
## 📁 项目结构
|
||
|
||
```
|
||
secure-device-demo/
|
||
├── android-device/
|
||
│ └── DeviceCrypto.java # Android 端完整安全模块
|
||
│
|
||
├── springboot-server/
|
||
│ ├── pom.xml
|
||
│ └── src/
|
||
│ ├── main/
|
||
│ │ ├── java/com/secure/demo/
|
||
│ │ │ ├── SecureDemoApplication.java
|
||
│ │ │ ├── controller/
|
||
│ │ │ │ └── DeviceController.java
|
||
│ │ │ ├── service/
|
||
│ │ │ │ ├── KeyManagementService.java
|
||
│ │ │ │ └── DeviceBindingService.java
|
||
│ │ │ ├── model/
|
||
│ │ │ │ ├── User.java
|
||
│ │ │ │ ├── Device.java
|
||
│ │ │ │ └── EncryptedPhoto.java
|
||
│ │ │ └── crypto/
|
||
│ │ │ ├── AesGcmUtil.java
|
||
│ │ │ └── RsaUtil.java
|
||
│ │ └── resources/
|
||
│ │ └── application.properties
|
||
│ └── test/
|
||
│ ├── java/com/secure/demo/
|
||
│ │ └── IntegrationTest.java
|
||
│ └── resources/
|
||
│ └── application-test.properties
|
||
│
|
||
└── README.md
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 快速启动
|
||
|
||
### 后端
|
||
|
||
```bash
|
||
cd springboot-server
|
||
mvn spring-boot:run
|
||
```
|
||
|
||
### 测试
|
||
|
||
```bash
|
||
cd springboot-server
|
||
mvn test
|
||
```
|
||
|
||
### Android 端集成
|
||
|
||
将 `DeviceCrypto.java` 复制到 Android 项目的对应包路径下,
|
||
在 `Application` 或 `MainActivity` 中初始化:
|
||
|
||
```java
|
||
DeviceCrypto crypto = new DeviceCrypto(context);
|
||
|
||
// 注册
|
||
String pubKey = crypto.getPublicKeyBase64();
|
||
String sn = crypto.getDeviceSN();
|
||
// → POST /api/device/register {sn, publicKeyBase64: pubKey}
|
||
|
||
// 拍照加密
|
||
byte[] photo = capturePhoto();
|
||
DeviceCrypto.EncryptedPayload payload = crypto.encryptData(photo);
|
||
String signature = crypto.signMetadata(sn + "|" + timestamp + "|" + photoId);
|
||
// → POST /api/photo/upload {sn, photoId, ciphertextBase64, ivBase64,
|
||
// dekBase64: payload.dekBase64,
|
||
// metadataSignature: signature, metadata}
|
||
```
|
||
|
||
---
|
||
|
||
## 🛡️ 安全分析
|
||
|
||
### 攻击场景 vs 防护
|
||
|
||
| 攻击场景 | 结果 | 原因 |
|
||
|---|---|---|
|
||
| 设备被 root | 拿不到 TEE 私钥 | Keystore 硬件保护 |
|
||
| 设备被盗 | 无法解密历史数据 | 无用户登录态 |
|
||
| 恢复出厂 | 旧密钥销毁 | TEE 安全擦除 |
|
||
| 服务端被拖库 | DEK 被 UK 加密 | 信封加密 |
|
||
| SN 被伪造 | 无法绑定/恢复 | 需短信验证 + SN 归属校验 |
|
||
| 短信被截获 | 仍需 SN 归属 | 多层校验 |
|
||
| 旧设备残留 | 已停用 | 重新注册时停用旧设备 |
|
||
|
||
### 安全原则
|
||
|
||
1. ✅ **设备零信任** — 设备只持有签名密钥,不持有解密密钥
|
||
2. ✅ **前向安全** — 每次恢复生成新密钥对
|
||
3. ✅ **信封加密** — DEK 永不明文存库
|
||
4. ✅ **短信 + SN 双因子** — 恢复必须两者同时通过
|
||
5. ✅ **一次性令牌** — Recovery Token 含 nonce + 时间窗口
|
||
|
||
---
|
||
|
||
## ⚠️ 生产环境注意事项
|
||
|
||
| Demo 简化 | 生产环境应改为 |
|
||
|---|---|
|
||
| UK 直接存 DB | KMS 托管(阿里云 KMS / AWS KMS) |
|
||
| 短信码随机生成 | 对接腾讯云/阿里云短信服务 |
|
||
| 内存 ConcurrentHashMap | JPA + PostgreSQL/MySQL |
|
||
| 明文 DEK 传输 | 设备端用服务端公钥加密 DEK 后传输 |
|
||
| 无验签实现 | 服务端用设备公钥验证 ECDSA 签名 |
|
||
| 无频率限制 | Redis 限流(短信/API) |
|
||
| 无审计日志 | 所有密钥操作写审计表 |
|
||
| Recovery Token 无过期 | 加 5 分钟时间窗口 + Redis 防重放 |
|
||
|
||
---
|
||
|
||
## 📋 API 接口清单
|
||
|
||
| Method | Path | 说明 |
|
||
|---|---|---|
|
||
| POST | `/api/device/register` | 设备注册(SN + 公钥) |
|
||
| POST | `/api/device/bind` | 用户绑定设备 |
|
||
| POST | `/api/device/sms/send` | 发送短信验证码 |
|
||
| POST | `/api/photo/upload` | 上传加密照片 |
|
||
| POST | `/api/device/recover` | 恢复设备(短信验证) |
|
||
| POST | `/api/photo/recover` | 恢复后获取照片 DEK |
|
||
| GET | `/api/photo/{id}/decrypt` | 用户下载并解密照片 |
|
||
| GET | `/api/health` | 健康检查 |
|