11 KiB
11 KiB
🔐 安全设备 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
🚀 快速启动
后端
cd springboot-server
mvn spring-boot:run
测试
cd springboot-server
mvn test
Android 端集成
将 DeviceCrypto.java 复制到 Android 项目的对应包路径下,
在 Application 或 MainActivity 中初始化:
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 归属 | 多层校验 |
| 旧设备残留 | 已停用 | 重新注册时停用旧设备 |
安全原则
- ✅ 设备零信任 — 设备只持有签名密钥,不持有解密密钥
- ✅ 前向安全 — 每次恢复生成新密钥对
- ✅ 信封加密 — DEK 永不明文存库
- ✅ 短信 + SN 双因子 — 恢复必须两者同时通过
- ✅ 一次性令牌 — 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 |
健康检查 |