Happy Docs 中文文档

安全与加密

Happy Coder 使用端到端加密,即使是我们自己也看不到你的代码。

简要概览

  1. 代码在离开设备前就已加密
  2. 只有你持有密钥 —— 主密钥永远不会离开你的手机
  3. 中继服务器无法读取任何内容 —— 它看到的只是加密数据块
  4. 开源 —— 你可以自己审计代码

加密架构

以下图表详细解释 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 字节的密钥,而非全部内容

自建服务器

不信任我们的中继服务器?自己搭一个。详见 自建指南