安全与加密¶
Happy Coder 使用端到端加密,即使是我们自己也看不到你的代码。
简要概览¶
- 代码在离开设备前就已加密
- 只有你持有密钥 —— 主密钥永远不会离开你的手机
- 中继服务器无法读取任何内容 —— 它看到的只是加密数据块
- 开源 —— 你可以自己审计代码
加密架构¶
以下图表详细解释 Happy Coder 加密模型的各层结构。
图例与密钥类型¶
══════════════════════════════════════════════════════════════════════════════
HAPPY CODER ENCRYPTION MODEL
══════════════════════════════════════════════════════════════════════════════
┌──────────────────────────────────────────────────────────────────────────────┐
│ LEGEND │
├──────────────────────────────────────────────────────────────────────────────┤
│ │
│ KEY TYPES │
│ ───────── │
│ ╔════════╗ Secret key - Must be protected, enables decryption │
│ ╚════════╝ │
│ │
│ ┌────────┐ Public key - Safe to share, only enables encryption │
│ └────────┘ │
│ │
│ ┌┄┄┄┄┄┄┄┄┐ Ephemeral key - Temporary, discarded after use │
│ └┄┄┄┄┄┄┄┄┘ │
│ │
│ «────────» Data Encryption Key (DEK) - Symmetric, encrypts actual content │
│ │
│ ARROWS │
│ ────── │
│ ───────> Key/data movement │
│ ┄┄┄┄┄┄┄> Encrypted data │
│ │
│ LIFECYCLE │
│ ───────── │
│ [ONCE/ACCOUNT] Created once per account, lives forever │
│ [ONCE/MACHINE] Created once per CLI machine │
│ [ONCE/SESSION] Created once per coding session │
│ [ONCE/AUTH] Created for each QR auth, discarded immediately │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
密钥层级¶
密钥分为四层,从根密钥逐层派生,最终保护实际内容。
══════════════════════════════════════════════════════════════════════════════
KEY HIERARCHY
══════════════════════════════════════════════════════════════════════════════
LAYER 1: ROOT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
╔═══════════════════════════════════════════════════════════════════════════╗
║ MASTER SECRET (32 bytes) [MOBILE] ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ║
║ Lifecycle: [ONCE/ACCOUNT] - created at signup ║
║ Stored: Mobile secure storage only ║
║ Backup: XXXXX-XXXXX-XXXXX-... (base32) ║
║ ║
║ ❌ Never leaves device ❌ Never sent anywhere ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════╝
│
│ HKDF derive
▼
LAYER 2: CONTENT KEY PAIR
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
╔══════════════════════════════════════╗ ┌────────────────────────────────┐
║ CONTENT SECRET KEY ║ │ CONTENT PUBLIC KEY │
║ (32 bytes) [MOBILE] ║ │ (32 bytes) [MOBILE+CLI]│
╠══════════════════════════════════════╣ ├────────────────────────────────┤
║ ║ │ │
║ Lifecycle: [ONCE/ACCOUNT] ║ │ Lifecycle: [ONCE/ACCOUNT] │
║ derived from master ║ │ derived from secret│
║ ║ │ │
║ Purpose: ║ │ Purpose: │
║ • Decrypt DEKs from server ║ │ • Encrypt DEKs for storage │
║ • Sign messages to CLI ║ │ • Sent to CLI during auth │
║ ║ │ │
║ ❌ Never leaves mobile ║ │ ✓ Shared with all CLIs │
║ ║ │ │
╚══════════════════════════════════════╝ └────────────────────────────────┘
│ │
│ decrypts │ encrypts
▼ ▼
LAYER 3: DATA ENCRYPTION KEYS (DEKs)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
«═══════════════════════════════» «═══════════════════════════════»
║ SESSION DEK ║ ║ MACHINE DEK ║
║ (32 bytes, random AES-256) ║ ║ (32 bytes, random AES-256) ║
╠═══════════════════════════════╣ ╠═══════════════════════════════╣
║ ║ ║ ║
║ Lifecycle: [ONCE/SESSION] ║ ║ Lifecycle: [ONCE/MACHINE] ║
║ random per session║ ║ random per CLI ║
║ ║ ║ ║
║ Stored: Server (encrypted ║ ║ Stored: Server (encrypted ║
║ with content pubkey) ║ ║ with content pubkey) ║
║ ║ ║ ║
║ Purpose: ║ ║ Purpose: ║
║ • Encrypt session messages ║ ║ • Encrypt machine metadata ║
║ • Encrypt session metadata ║ ║ • Encrypt machine state ║
║ ║ ║ ║
║ Enables future sharing: ║ ║ ║
║ Re-encrypt DEK for ║ ║ ║
║ friend's public key ║ ║ ║
║ ║ ║ ║
«═══════════════════════════════» «═══════════════════════════════»
│ │
│ encrypts │ encrypts
▼ ▼
LAYER 4: ACTUAL CONTENT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
┆ Session Content (encrypted) ┆ ┆ Machine Data (encrypted) ┆
┆ ───────────────────────── ┆ ┆ ─────────────────────── ┆
┆ • Claude messages ┆ ┆ • Machine name, status ┆
┆ • Tool calls & results ┆ ┆ • Active session info ┆
┆ • File contents ┆ ┆ • Machine preferences ┆
┆ • Terminal output ┆ ┆ ┆
┆ • Session metadata ┆ ┆ ┆
┆ ┆ ┆ ┆
┆ Stored on server, encrypted ┆ ┆ Stored on server, encrypted ┆
┆ with session DEK ┆ ┆ with machine DEK ┆
└┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘ └┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┘
认证完成后的密钥状态¶
认证后,各方持有的密钥清单——服务器无法解密任何内容。
══════════════════════════════════════════════════════════════════════════════
KEY STATE AFTER AUTHENTICATION
══════════════════════════════════════════════════════════════════════════════
MOBILE SERVER CLI
│ │ │
│ │ │
┌───────┴───────────────────────┐ ┌──────┴──────────────┐ ┌───────────┴───────────────────┐
│ │ │ │ │ │
│ ╔═════════════════════════╗ │ │ Encrypted storage │ │ ┌───────────────────────┐ │
│ ║ master_secret ║ │ │ only: │ │ │ content_public_key │ │
│ ╚═════════════════════════╝ │ │ │ │ └───────────────────────┘ │
│ │ │ │ • Session DEKs │ │ │
│ derives │ │ (encrypted) │ │ ╔═════════════════════════╗ │
│ ▼ │ │ │ │ ║ machine_key (local) ║ │
│ ╔═════════════════════════╗ │ │ • Machine DEKs │ │ ╚═════════════════════════╝ │
│ ║ content_secret_key ║ │ │ (encrypted) │ │ │
│ ╚═════════════════════════╝ │ │ │ │ «═════════════════════════» │
│ │ │ │ • Session content │ │ ║ session DEKs ║ │
│ decrypts │ │ (encrypted) │ │ ║ (decrypted in memory) ║ │
│ ▼ │ │ │ │ «═════════════════════════» │
│ «═════════════════════════» │ │ • Machine data │ │ │
│ ║ All DEKs (decrypted) ║ │ │ (encrypted) │ │ ❌ No master_secret │
│ «═════════════════════════» │ │ │ │ ❌ No content_secret_key │
│ │ │ Cannot decrypt │ │ ❌ Cannot decrypt other │
│ ┌─────────────────────────┐ │ │ anything │ │ sessions' DEKs │
│ │ content_public_key │ │ │ │ │ │
│ └─────────────────────────┘ │ │ │ │ │
│ │ │ │ │ │
└───────────────────────────────┘ └─────────────────────┘ └───────────────────────────────┘
会话同步流程¶
CLI 创建会话时生成随机 DEK,用公钥加密后存到服务器;手机端用私钥解密 DEK 后才能读取内容。
══════════════════════════════════════════════════════════════════════════════
SESSION SYNC FLOW
══════════════════════════════════════════════════════════════════════════════
MOBILE SERVER CLI
│ │ │
│ │ New session created │
│ │ │ │
│ │ ▼ │
│ │ «═══════════════════════════════»
│ │ ║ Generate random SESSION DEK ║
│ │ ║ (AES-256, 32 bytes) ║
│ │ «═══════════════════════════════»
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────────┐
│ │ │ Encrypt DEK with │
│ │ │ content_public_key │
│ │ └─────────────────────────────────┘
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────────┐
│ │ │ Encrypt session content with │
│ │ │ SESSION DEK (AES-256) │
│ │ └─────────────────────────────────┘
│ │ │ │
│ │◄┄┄┄┄┄┄┄┄┄┄┄┄┄┼┄┄┄┄┄┄┄┄┄┄┄┄┄┄│
│ │ encrypted │ │
│ │ DEK + │ │
│ │ encrypted │ │
│ │ content │ │
│ │ │ │
│◄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄│ │ │
│ │ │ │
▼ │ │ │
╔════════════════════════════════╗ │ │ │
║ 1. Decrypt DEK with ║ │ │ │
║ content_secret_key ║ │ │ │
║ ║ │ │ │
║ 2. Decrypt content with DEK ║ │ │ │
╚════════════════════════════════╝ │ │ │
│ │ │ │
▼ │ │ │
┌──────────────────┐ │ │ │
│ Display session │ │ │ │
│ in mobile app │ │ │ │
└──────────────────┘ │ │ │
│ │ │ │
│ User sends command │ │ │
│ │ │ │ │
▼ │ │ │ │
┌─────────────────────────────────┐ │ │ │
│ Encrypt with SESSION DEK │ │ │ │
└─────────────────────────────────┘ │ │ │
│ │ │ │
│┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄>│ │ │
│ │┄┄┄┄┄┄┄┄┄┄┄┄┄>│ │
│ │ ▼ │
│ │ ┌─────────────────────────────────┐
│ │ │ Decrypt with SESSION DEK │
│ │ │ (CLI has DEK in memory) │
│ │ └─────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
未来:与好友共享会话¶
共享时只需用好友的公钥重新加密 32 字节的 DEK,无需重新加密全部会话内容。
══════════════════════════════════════════════════════════════════════════════
FUTURE: SHARING WITH FRIENDS
══════════════════════════════════════════════════════════════════════════════
To share a session with a friend:
YOUR MOBILE SERVER FRIEND'S MOBILE
│ │ │
│ │ │
╔════════════════════════════╗ │ │
║ 1. Get friend's ║ │ │
║ content_public_key ║◄────────│─────────────────────────────│
║ ║ │ (friend's public key │
║ 2. Decrypt session DEK ║ │ is on their profile) │
║ with YOUR secret key ║ │ │
║ ║ │ │
║ 3. Re-encrypt session DEK ║ │ │
║ with FRIEND's pub key ║ │ │
╚════════════════════════════╝ │ │
│ │ │
│┄┄┄┄ share record ┄┄┄┄┄┄┄┄┄┄┄┄┄┄>│ │
│ (DEK encrypted for friend) │ │
│ │ │
│ │┄┄┄┄ share notification ┄┄┄┄>│
│ │ │
│ │ ╔════════════════════════════════╗
│ │ ║ Friend decrypts DEK with ║
│ │ ║ THEIR content_secret_key ║
│ │ ║ ║
│ │ ║ Friend can now decrypt ║
│ │ ║ session content with DEK ║
│ │ ╚════════════════════════════════╝
│ │ │
▼ ▼ ▼
KEY INSIGHT: Session content never re-encrypted!
Only the small DEK (32 bytes) is re-encrypted for friend.
Original encrypted content stays exactly the same on server.
密钥用途总结¶
| 密钥 | 生命周期 | 用途 |
|---|---|---|
| MASTER SECRET | ONCE/ACCOUNT | 根密钥,派生一切。备份码就是它的 base32 编码 |
| CONTENT KEY PAIR (公钥 + 私钥) | ONCE/ACCOUNT | 加密/解密 DEK;支持共享时无需重新加密内容 |
| SESSION DEK (AES-256) | ONCE/SESSION | 加密会话消息和元数据;可重新加密给好友 |
| MACHINE DEK (AES-256) | ONCE/MACHINE | 加密机器级数据;可按机器撤销 |
| MACHINE KEY (local) | ONCE/MACHINE | 本地 CLI 缓存加密;从不发送到任何地方 |
| EPHEMERAL KEYPAIR | ONCE/AUTH | QR 认证时的安全密钥交换;用完即弃 |
安全属性¶
不同泄露场景下的影响分析——即使 CLI 机器被入侵,攻击者也无法获取主密钥或其他会话。
| 泄露场景 | 攻击者能获取 | 攻击者无法获取 |
|---|---|---|
| CLI 机器被入侵 | content_public_key、machine_key (local)、当前会话的 session DEK(内存中)、本机器的会话内容 | master_secret、content_secret_key、其他会话的 DEK、好友的会话 |
| 服务器被入侵 | 加密的 DEK、加密的内容、元数据 | 解密后的 DEK、任何明文内容、密钥 |
| 网络窃听 | 加密流量 | 任何有用信息 |
| 恢复场景 | 解决方案 |
|---|---|
| CLI 机器丢失/被盗 | 从账户中移除——session DEK 只在内存中,machine DEK 被撤销 |
| 手机丢失 + 有备份码 | 恢复 master_secret → 重新派生 content keys → 可再次解密所有 DEK |
| 手机丢失 + 无备份码 | 账户不可恢复(设计如此) |
为什么这样设计¶
你的主密钥永远不会离开手机。 每台 CLI 机器只获得一个派生的公钥用于加密。这意味着:
- CLI 机器被入侵时,攻击者无法获取你的备份码或影响其他机器
- 服务器只存储它无法读取的加密数据块
- 与好友共享会话时效率很高——只需重新加密 32 字节的密钥,而非全部内容
自建服务器¶
不信任我们的中继服务器?自己搭一个。详见 自建指南。