Files
secure-device-demo/README.md

254 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔐 安全设备 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 KeystoreStrongBox 优先) │
│ ├── 私钥:永不导出,仅签名/解密 │
│ ├── 公钥:上传服务端,用于加密下发 │
│ └── 销毁:恢复出厂时自动清除 │
│ │
└─────────────────────────────────────────────────────────┘
```
---
## 🔄 完整流程
### 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` | 健康检查 |