urwithajit9/evnx-crypto
GitHub: urwithajit9/evnx-crypto
一个纯 Rust 实现的零知识加密原语库,为 evnx 生态系统提供端到端的密钥派生、vault 加密、非对称密钥管理和 SRP 认证能力。
Stars: 0 | Forks: 0
# evnx-crypto
[](https://crates.io/crates/evnx-crypto)
[](https://github.com/urwithajit9/evnx-crypto/actions)
[](https://github.com/urwithajit9/evnx-crypto/actions)
[](LICENSE)
一个纯 Rust 加密库,为 evnx 实现 ZKE (Zero-Knowledge Encryption) 协议。服务器**绝不**可见您的密码、主密钥或明文 `.env` 内容 —— 所有的敏感操作均在此库中本地运行。
## 本库功能
```
User Password
│
├─[Argon2id + argon2_salt]──→ Master Key (local only, never transmitted)
│ └──→ encrypt Ed25519 private key
│ └──→ wrap VaultKey (solo vaults)
│
└─[Argon2id + srp_salt]────→ SRP Password Input
└──→ SRP Verifier (sent to server)
└──→ SRP Login Proof (sent to server)
Server stores: argon2_salt, srp_salt, SRP verifier, encrypted private key
Server NEVER sees: password, master key, VaultKey, .env plaintext
```
## 模块
| 模块 | 用途 | 状态 |
|--------|---------|--------|
| [`kdf`](#kdf) | Argon2id 密钥派生 — 主密钥 + SRP 密码 | ✅ v0.1.0 |
| [`vault`](#vault) | AES-256-GCM vault 加密 + XChaCha20 密钥封装 | ✅ v0.1.0 |
| [`keypair`](#keypair) | Ed25519 + X25519 密钥对,私钥加密,ECDH vault 共享 | ✅ v0.1.0 |
| [`srp`](#srp) | SRP-6a 客户端 — 验证器、临时值、证明、验证 | ✅ v0.1.0 |
| [`zeroize`](#zeroize) | 用于堆分配秘密的安全内存类型 | ✅ v0.1.0 |
| [`errors`](#errors) | `CryptoError` — 所有失败变体 | ✅ v0.1.0 |
## 安装
此 crate 是 evnx 项目的内部库。作为 Git 依赖项添加:
```
[dependencies]
evnx-crypto = { git = "https://github.com/urwithajit9/evnx-crypto", tag = "v0.1.0" }
```
用于与 evnx-server 或 evnx CLI 并行本地开发:
```
[dependencies]
evnx-crypto = { path = "../evnx-crypto" }
```
## 快速开始
### 注册 (客户端)
```
use evnx_crypto::{
kdf::{generate_salt, derive_master_key, derive_srp_password},
keypair::{generate_keypair, encrypt_private_key},
srp::compute_verifier,
};
// 1. Generate two independent salts — NEVER share or reuse
let argon2_salt = generate_salt(); // for master key derivation
let srp_salt = generate_salt(); // for SRP authentication
// 2. Derive master key — LOCAL ONLY, never transmitted
let master_key = derive_master_key(password.as_bytes(), &argon2_salt)?;
// 3. Generate asymmetric keypair
let keypair = generate_keypair();
// 4. Encrypt private key with master key (safe to store on server)
let enc_private_key = encrypt_private_key(&keypair, &master_key)?;
// 5. Derive SRP verifier (password-equivalent stored on server)
let srp_password = derive_srp_password(password.as_bytes(), &srp_salt)?;
let verifier = compute_verifier(srp_password, srp_salt)?;
// 6. Send to server (master_key and private key NEVER leave the client):
// { email, verifier, srp_salt, argon2_salt,
// keypair.ed25519_public, keypair.x25519_public, enc_private_key }
```
### 登录 (客户端)
```
use evnx_crypto::{
kdf::{derive_master_key, derive_srp_password},
keypair::decrypt_private_key,
srp::{generate_client_ephemeral, compute_client_proof, verify_server_proof},
};
// After server returns { argon2_salt, srp_salt, enc_private_key }:
// 1. Re-derive master key and decrypt private key
let master_key = derive_master_key(password.as_bytes(), &argon2_salt)?;
let keypair = decrypt_private_key(&enc_private_key, &master_key)?;
// 2. SRP Step 1 — generate ephemeral, send A to server
let eph = generate_client_ephemeral()?;
// POST /auth/srp/init { email, client_public: eph.public_a }
// server responds with { server_public: B, srp_salt, session_id }
// 3. SRP Step 2 — compute proof, send M1 to server
let srp_password = derive_srp_password(password.as_bytes(), &srp_salt)?;
let proof = compute_client_proof(&email, srp_password, &srp_salt, &server_b, &eph)?;
// POST /auth/srp/verify { session_id, client_proof: proof.client_proof }
// server responds with { server_proof: M2 }
// 4. SRP Step 3 — verify M2 (mutual authentication)
verify_server_proof(&server_m2, &proof)?;
// If Ok(()), the server is legitimate. Proceed with session.
```
### 推送 (加密 .env)
```
use evnx_crypto::{
vault::{encrypt_vault},
keypair::unwrap_vault_key,
};
// After fetching { encrypted_vault_key, eph_pub_key } from server:
let vault_key = unwrap_vault_key(&wrapped, keypair.x25519_private_bytes())?;
let blob = encrypt_vault(env_file_bytes, &vault_key)?;
// Send { nonce: blob.nonce, ciphertext: blob.ciphertext } to server for S3 upload
```
### 拉取 (解密 .env)
```
use evnx_crypto::{
vault::decrypt_vault,
keypair::unwrap_vault_key,
};
// After fetching { nonce, ciphertext } from server:
let vault_key = unwrap_vault_key(&wrapped, keypair.x25519_private_bytes())?;
let plaintext = decrypt_vault(&blob, &vault_key)?;
// Write plaintext to .env
```
### 与协作者共享 vault
```
use evnx_crypto::keypair::{unwrap_vault_key, wrap_vault_key_for_user};
// After fetching collaborator's x25519_public from server:
let my_vault_key = unwrap_vault_key(&my_wrapped, keypair.x25519_private_bytes())?;
let wrapped_for_them = wrap_vault_key_for_user(&my_vault_key, &collab_x25519_pub)?;
// POST /vaults/{id}/members { encrypted_vault_key, eph_pub_key }
```
## 模块参考
### `kdf` {#kdf}
基于 Argon2id 的密钥派生。所有派生均在客户端进行。
```
// Generate a random 32-byte salt (call separately for argon2 and SRP salts)
pub fn generate_salt() -> [u8; 32];
// Derive 256-bit master key from password + salt
// Returns MasterKey (ZeroizeOnDrop)
pub fn derive_master_key(password: &[u8], salt: &[u8; 32]) -> Result;
// Derive SRP password input (different Argon2id call, different salt)
// Returns Zeroizing>
pub fn derive_srp_password(password: &[u8], srp_salt: &[u8; 32]) -> Result>, CryptoError>;
```
**参数 (OWASP 2024 默认值):** m=64MB, t=3 次迭代, p=4 个线程。请根据您的硬件在 `src/kdf.rs` 中进行调整。目标:300–500ms。
### `vault` {#vault}
用于 .env 加密的 AES-256-GCM。用于密钥封装的 XChaCha20-Poly1305。
```
// Generate a fresh random 256-bit vault key
pub fn VaultKey::generate() -> VaultKey;
// Encrypt plaintext → EncryptedBlob { nonce, ciphertext }
// Fresh random nonce per call. GCM auth tag embedded in ciphertext.
pub fn encrypt_vault(plaintext: &[u8], key: &VaultKey) -> Result;
// Decrypt EncryptedBlob → plaintext
// Returns Err(Decryption) on wrong key or tampered ciphertext. Never panics.
pub fn decrypt_vault(blob: &EncryptedBlob, key: &VaultKey) -> Result, CryptoError>;
// Wrap VaultKey with user's MasterKey (solo vaults / backup)
pub fn wrap_vault_key_with_master_key(vk: &VaultKey, mk: &MasterKey) -> Result, CryptoError>;
pub fn unwrap_vault_key_with_master_key(wrapped: &[u8], mk: &MasterKey) -> Result;
```
### `keypair` {#keypair}
Ed25519 (签名) + X25519 (密钥协商) 密钥对管理。
```
// Generate a fresh Ed25519 + X25519 keypair
// X25519 private key is deterministically derived from Ed25519 seed via HKDF.
// One EncryptedPrivateKey covers both.
pub fn generate_keypair() -> UserKeypair;
// Encrypt Ed25519 seed with MasterKey (XChaCha20-Poly1305)
pub fn encrypt_private_key(keypair: &UserKeypair, mk: &MasterKey) -> Result;
// Decrypt and reconstruct full keypair from EncryptedPrivateKey
pub fn decrypt_private_key(enc: &EncryptedPrivateKey, mk: &MasterKey) -> Result;
// ECDH wrap: encrypt VaultKey for a specific recipient
// Uses ephemeral X25519 + HKDF-SHA256 + XChaCha20-Poly1305
// Ephemeral private key is zeroized immediately after ECDH.
pub fn wrap_vault_key_for_user(vk: &VaultKey, recipient_pub: &X25519PublicKeyBytes) -> Result;
// ECDH unwrap: decrypt VaultKey using your X25519 private key
pub fn unwrap_vault_key(wrapped: &WrappedVaultKey, my_x25519_private: &[u8; 32]) -> Result;
```
### `srp` {#srp}
SRP-6a 客户端 — RFC 5054,使用 2048 位群和 SHA-256。
```
// Registration: compute SRP verifier from Argon2id-derived SRP password
// srp_password_bytes = output of derive_srp_password(), NOT raw password
pub fn compute_verifier(srp_password: Zeroizing>, srp_salt: [u8; 32]) -> Result;
// Login Step 1: generate ephemeral A (send public_a to server)
pub fn generate_client_ephemeral() -> Result;
// Login Step 2: compute client proof M1 (send client_proof to server)
// Keep session_key to verify M2 in Step 3.
pub fn compute_client_proof(
email: &str,
srp_password: Zeroizing>,
srp_salt: &[u8],
server_public_b: &[u8],
ephemeral: &SrpClientEphemeral,
) -> Result;
// Login Step 3: verify server proof M2 (mutual authentication)
// Returns Err if server cannot prove it knows the verifier.
pub fn verify_server_proof(server_m2: &[u8], proof: &SrpClientProof) -> Result<(), CryptoError>;
```
### `zeroize` {#zeroize}
用于堆分配秘密的安全内存封装器。
```
// Variable-length heap secret
pub struct SecretBytes(Zeroizing>);
// Password input before conversion to bytes
pub struct SecretString(Zeroizing);
// Fixed-size stack secret with ZeroizeOnDrop
pub struct SecretArray([u8; N]);
// Explicit zeroize of a mutable byte slice
pub fn zeroize_slice(buf: &mut [u8]);
```
所有类型实现的 `Debug` 和 `Display` 均显示为 `[REDACTED]` —— 可以安全地记录日志。
### `errors` {#errors}
```
pub enum CryptoError {
Kdf(String), // Argon2 derivation failure
Encryption, // AES-GCM encrypt failed
Decryption, // AES-GCM decrypt failed (wrong key or tampered)
KeyWrap, // XChaCha20 wrap failed
KeyUnwrap, // XChaCha20 unwrap failed (wrong key or tampered)
InvalidInput(String), // Malformed input (e.g., slice too short)
Srp(String), // SRP protocol error
}
```
所有库函数均返回 `Result`。库代码中无 panic。
## 安全属性
| 保证 | 实现方式 |
|-----------|-----|
| 密码从不传输 | 所有派生在任何网络调用之前均在客户端进行 |
| 主密钥从不存储 | 每次会话从密码重新派生,ZeroizeOnDrop |
| SRP 验证器泄露 ≠ vault 访问权限 | argon2_salt 和 srp_salt 是独立的;不同的 Argon2id 调用 |
| AES-GCM nonce 永不重复 | 每次 `encrypt_vault()` 调用使用全新的 OsRng nonce |
| 篡改检测 | AES-GCM 和 XChaCha20-Poly1305 认证标签;失败时返回 `Err(Decryption)` |
| ECDH 临时密钥擦除 | `EphemeralSecret` (x25519-dalek) 在 drop 时自动擦除 |
| 密钥材料从堆中擦除 | `MasterKey`, `VaultKey`, `UserKeypair`, 证明类型上的 `ZeroizeOnDrop` |
| 不跨域重用密钥 | HKDF 针对每个操作使用不同的 `info` 字符串 |
## 运行测试
```
# 所有测试
cargo test
# 特定模块
cargo test --test kdf
cargo test --test vault
cargo test --test keypair
cargo test --test srp
cargo test --test zeroize
cargo test --test integration
# 详细输出(查看计时、println! 输出)
cargo test --test integration -- --nocapture
# 更多 proptest 迭代(慢但彻底)
PROPTEST_CASES=1000 cargo test
# KDF 计时基准
cargo test --test kdf benchmark_derive_master_key -- --ignored --nocapture
# 安全审计
cargo audit
# Lint
cargo clippy -- -D warnings
```
## 密码学选择
| 决策 | 选择 | 理由 |
|----------|--------|-----------|
| KDF | Argon2id | PHC 获胜者;内存困难;`id` 变体可抵抗侧信道和 GPU 攻击 |
| Vault 加密 | AES-256-GCM | FIPS 140-2;x86/ARM 上的硬件加速;AEAD |
| 密钥封装 | XChaCha20-Poly1305 | 192 位 nonce 可安全用于大规模随机生成;无硬件要求 |
| 非对称签名 | Ed25519 | 快速、密钥/签名小、恒定时间、没有容易出错的参数选择 |
| 密钥协商 | X25519 | RFC 7748;恒定时间;与 Ed25519 天然配对 (同曲线族) |
| 用于 ECDH 的 KDF | HKDF-SHA256 | 标准;通过 `info` 参数进行域分离 |
| SRP 群 | RFC 5054 G_2048 | 2048 位 DH 群;兼顾安全性和性能 |
| SRP 哈希 | SHA-256 | 抗碰撞性;标准 |
## 本库不执行的操作
- **网络 I/O** — 无 HTTP、无 TLS、无套接字操作
- **文件 I/O** — 不读取或写入 `.env` 文件
- **服务端 SRP** — 仅客户端;`evnx-server` 实现服务端
- **密钥存储** — 不管理 OS 钥匙串或配置文件
- **Token 生成** — JWT 和 API Token 逻辑位于 `evnx-server`
## 许可证
根据您的选择,许可于 MIT 或 Apache-2.0。
## 相关仓库
| 仓库 | 角色 |
|------|------|
| [`evnx`](https://github.com/urwithajit9/evnx) | CLI —— 消费此库以进行本地加密 |
| [`evnx-server`](https://github.com/urwithajit9/evnx-server) | 后端 —— 消费此库以获取 KDF 参数和错误类型 |
## 🔐 密钥派生函数 (KDF)
KDF 模块提供安全的、基于 Argon2id 的密钥派生,用于密码到密钥的转换,实施了零知识加密的行业最佳实践。
### 概述
我们的 KDF 实现使用 **Argon2id** (Password Hashing Competition 获胜者),其参数符合 **OWASP 2024 建议**。它服务于两个关键的、隔离的目的:
1. **主密钥派生**:派生一个 32 字节的加密密钥,用于保护 Ed25519 私钥
2. **SRP 密码派生**:为安全远程密码 (SRP) 认证生成单独的输入
### 🔑 安全架构
```
graph TB
subgraph Client["🔒 Client-Side (Never Transmitted)"]
P[User Password]
AS[Argon2 Salt
32 bytes] SS[SRP Salt
32 bytes] subgraph KDF["Key Derivation Functions"] MDK[derive_master_key
Argon2id] DSP[derive_srp_password
Argon2id] end MK[Master Key
32 bytes] SP[SRP Password
32 bytes] subgraph Crypto["Cryptographic Operations"] ENC[Encrypt Ed25519
Private Key] SRP[Generate SRP
Verifier/Proof] end end subgraph Server["🖥️ Server Storage"] AS_STORE[Argon2 Salt
Stored] SS_STORE[SRP Salt
Stored] V[SRP Verifier
Stored] ENC_KEY[Encrypted
Ed25519 Key] end P --> MDK P --> DSP AS --> MDK SS --> DSP MDK --> MK DSP --> SP MK --> ENC SP --> SRP AS -.-> AS_STORE SS -.-> SS_STORE ENC --> ENC_KEY SRP --> V ENC_KEY -.-> Server V -.-> Server AS_STORE -.-> Client SS_STORE -.-> Client style MK fill:#f96,stroke:#333,stroke-width:3px style SP fill:#f96,stroke:#333,stroke-width:2px style P fill:#bbf,stroke:#333,stroke-width:2px style KDF fill:#ffebcd,stroke:#333,stroke-width:2px ``` ### 🔒 安全保证 | 特性 | 实现 | 益处 | |---------|----------------|---------| | **算法** | Argon2id v0x13 | 内存困难,抵抗 GPU/ASIC 攻击 | | **内存开销** | 64 MB (可配置) | 防止并行暴力破解攻击 | | **时间开销** | 3 次迭代 | 增加每次猜测的计算成本 | | **并行度** | 4 个线程 | 针对现代 CPU 优化 | | **输出长度** | 32 字节 (256 位) | 足以用于对称加密 | | **盐值唯一性** | 每用户 32 字节随机值 | 防止彩虹表攻击 | | **密钥分离** | 主密钥/SRP 使用独立盐值 | 秘密隔离 | | **归零化** | 自动内存清除 | 防止密钥材料泄露 | ### 🛡️ 威胁模型防护 ``` graph LR subgraph Threats["🎯 Attack Vectors"] T1[Dictionary Attack] T2[Rainbow Tables] T3[GPU/ASIC Cracking] T4[Side-Channel] T5[Memory Dump] T6[Verifier Compromise] end subgraph Defenses["🛡️ Mitigations"] D1[High iteration count
3 rounds] D2[Unique random salts
32 bytes each] D3[Memory-hard function
64 MB RAM] D4[Constant-time ops
Argon2 library] D5[ZeroizeOnDrop
Secure memory] D6[Separate derivation
paths] end T1 --> D1 T2 --> D2 T3 --> D3 T4 --> D4 T5 --> D5 T6 --> D6 style Defenses fill:#d4edda,stroke:#28a745,stroke-width:2px style Threats fill:#f8d7da,stroke:#dc3545,stroke-width:2px ``` ### 📊 性能特征 **目标硬件:** 现代服务器 (Intel Xeon / AMD EPYC) **目标派生时间:** 300–500ms | 参数 | 值 | 影响 | |-----------|-------|--------| | 内存 (m_cost) | 64 MB | 每次派生的 RAM 需求 | | 迭代 (t_cost) | 3 | CPU 时间乘数 | | 并行度 (p_cost) | 4 | 线程利用率 | | **实际时间** (Mac Mini M1) | ~1175ms | ⚠️ 可能需要调整 | ### 🔄 密钥派生流程 #### 注册流程 ``` sequenceDiagram participant U as User participant C as Client App participant S as Server U->>C: Enter password C->>C: Generate argon2_salt (random) C->>C: Generate srp_salt (random) C->>C: derive_master_key(password, argon2_salt) C->>C: Generate Ed25519 keypair C->>C: Encrypt private key with master_key C->>C: derive_srp_password(password, srp_salt) C->>C: Generate SRP verifier C->>S: Register(argon2_salt, srp_salt, verifier, encrypted_key) S->>S: Store salts + verifier S-->>C: Registration successful Note over C: Password & master_key
NEVER leave client Note over U: Only encrypted data
sent to server ``` #### 登录流程 ``` sequenceDiagram participant U as User participant C as Client App participant S as Server U->>C: Enter password C->>S: Request login (username) S->>S: Lookup user S-->>C: Return argon2_salt, srp_salt C->>C: derive_master_key(password, argon2_salt) C->>C: Fetch encrypted private key C->>C: Decrypt with master_key C->>C: derive_srp_password(password, srp_salt) C->>C: Generate SRP proof C->>S: SRP proof S->>S: Verify proof S-->>C: Authenticated! Note over C: Master key derived
locally each time Note over U: Password never
transmitted ``` ### 🔧 配置 编辑 `src/kdf.rs` 以调整参数: ``` // OWASP 2024 minimums - ADJUST FOR YOUR HARDWARE const ARGON2_M_COST: u32 = 65536; // Memory: 64 MB const ARGON2_T_COST: u32 = 3; // Iterations: 3 const ARGON2_P_COST: u32 = 4; // Parallelism: 4 lanes // Target: 300-500ms derivation time on production server ``` **调整指南:** - **增加 `M_COST`** 如果服务器有剩余 RAM (更安全) - **增加 `T_COST`** 如果 CPU 快但 RAM 有限 - **减少两者** 如果派生对于用户体验来说太慢 (安全性降低) - **运行基准测试:** `cargo test --test kdf benchmark_derive_master_key -- --ignored` ### 🧪 测试 ``` # 运行所有 KDF 测试 cargo test --test kdf # 运行并输出计时 cargo test --test kdf -- --nocapture # 运行 benchmark(默认忽略) cargo test --test kdf benchmark_derive_master_key -- --ignored --nocapture # 测试所有模块 cargo test ``` ### 📦 依赖项 ``` [dependencies] argon2 = "0.5" # Argon2 implementation rand = "0.8" # Cryptographic RNG zeroize = { version = "1.7", features = ["derive"] } # Secure memory ``` ### 🔍 安全审计清单 - ✅ Argon2id (不是 Argon2i 或 Argon2d) - ✅ 每个用户每个目的使用唯一的盐值 - ✅ 不向服务器传输密码 - ✅ 主密钥永不离开客户端 - ✅ 敏感内存的归零化 - ✅ 恒定时间比较 (通过 Argon2 库) - ✅ 密码学安全的 RNG (OsRng) - ✅ MasterKey 结构体上无 Copy/Clone # 🔐 Vault 加密模块 ## 概述 `vault` 模块为敏感配置数据提供认证加密。它专为以下目的设计: - ✅ 在存储或传输前加密 `.env` 文件 - ✅ 使用用户主密钥封装 vault 密钥以进行安全的密钥管理 - ✅ 通过 AEAD (关联数据的认证加密) 确保完整性 - ✅ 在 drop 时从内存中归零化敏感密钥 ## 密码学原语 | 组成部分 | 算法 | 用途 | |-----------|-----------|---------| | **数据加密** | AES-256-GCM | 加密 `.env` 明文并附带认证标签 | | **密钥封装** | XChaCha20-Poly1305 | 使用用户 `MasterKey` 封装 `VaultKey` | | **KDF** | Argon2id (通过 `kdf` 模块) | 从用户密码派生 `MasterKey` | | **随机性** | `rand::OsRng` | 密码学安全的 nonce/密钥生成 | | **内存安全** | `zeroize::ZeroizeOnDrop` | 在 drop 时安全擦除密钥 | ## 常量 ``` pub const VAULT_KEY_LEN: usize = 32; // 256-bit key pub const NONCE_LEN: usize = 12; // 96-bit nonce for AES-GCM pub const XCHACHA_NONCE_LEN: usize = 24; // 192-bit nonce for XChaCha20 ``` ## 核心类型 ### `VaultKey` ``` #[derive(ZeroizeOnDrop)] pub struct VaultKey(pub [u8; VAULT_KEY_LEN]); ``` 用于加密 vault 数据的 32 字节对称密钥。在 drop 时自动归零化。 #### 方法 ``` impl VaultKey { /// Generate a new cryptographically random vault key pub fn generate() -> Self; } ``` ### `EncryptedBlob` ``` pub struct EncryptedBlob { pub nonce: [u8; NONCE_LEN], // 12-byte nonce for AES-GCM pub ciphertext: Vec, // Ciphertext + 16-byte GCM auth tag
}
```
表示加密后的 `.env` 文件。nonce 以明文存储;认证标签附加在密文后。
## 公共 API
### 加密/解密 `.env` 数据
```
/// Encrypt plaintext with VaultKey using AES-256-GCM
pub fn encrypt_vault(
plaintext: &[u8],
vault_key: &VaultKey
) -> Result;
/// Decrypt EncryptedBlob with VaultKey
pub fn decrypt_vault(
blob: &EncryptedBlob,
vault_key: &VaultKey
) -> Result, CryptoError>;
```
#### 示例
```
use evnx_crypto::vault::{VaultKey, encrypt_vault, decrypt_vault};
let vault_key = VaultKey::generate();
let env_content = b"DATABASE_URL=postgres://user:pass@localhost/db\n";
// Encrypt
let blob = encrypt_vault(env_content, &vault_key)?;
// Store blob.nonce and blob.ciphertext securely...
// Later, decrypt
let decrypted = decrypt_vault(&blob, &vault_key)?;
assert_eq!(env_content, &decrypted[..]);
```
### 封装/解封 Vault 密钥
```
/// Wrap VaultKey with user's MasterKey using XChaCha20-Poly1305
/// Returns: [24-byte nonce || ciphertext]
pub fn wrap_vault_key_with_master_key(
vault_key: &VaultKey,
master_key: &MasterKey
) -> Result, CryptoError>;
/// Unwrap VaultKey from wrapped blob
pub fn unwrap_vault_key_with_master_key(
wrapped: &[u8],
master_key: &MasterKey
) -> Result;
```
#### 示例
```
use evnx_crypto::vault::{VaultKey, wrap_vault_key_with_master_key, unwrap_vault_key_with_master_key};
use evnx_crypto::kdf::MasterKey;
let master_key = MasterKey::derive_from_password("user_password", b"salt123")?;
let vault_key = VaultKey::generate();
// Wrap for storage
let wrapped = wrap_vault_key_with_master_key(&vault_key, &master_key)?;
// Store `wrapped` in database (e.g., vault_members row)
// Later, unwrap
let unwrapped = unwrap_vault_key_with_master_key(&wrapped, &master_key)?;
assert_eq!(vault_key.0, unwrapped.0);
```
## 错误处理
所有函数返回 `Result`:
```
pub enum CryptoError {
Encryption, // AES-GCM encryption failure
Decryption, // Auth tag mismatch, wrong key, or tampering
KeyWrap, // XChaCha20-Poly1305 wrap failure
KeyUnwrap, // Auth failure or invalid input during unwrap
InvalidInput(String), // Malformed input (e.g., too short)
KdfError(String), // Argon2id derivation failure
}
```
✅ **绝不 panic** —— 所有错误均被优雅处理。
## 安全属性
| 属性 | 保证 |
|----------|-----------|
| **机密性** | AES-256-GCM / XChaCha20-Poly1305 提供 IND-CCA2 安全性 |
| **完整性** | AEAD 认证标签检测任何篡改 (1 位改变 → 解密失败) |
| **Nonce 安全** | 每次加密调用生成全新的随机 nonce (OsRng) |
| **密钥隔离** | `VaultKey` 和 `MasterKey` 是独立的;泄露其中一个 ≠ 泄露另一个 |
| **内存安全** | `ZeroizeOnDrop` 确保密钥在 drop 时从堆/栈中擦除 |
| **无密钥重用** | 每次 `encrypt_vault()` 调使用唯一的 nonce;永不重用 (密钥, nonce) 对 |
## ⚠️ 关键警告
```
// ❌ NEVER reuse a nonce with the same key
// ❌ NEVER store VaultKey in plaintext
// ❌ NEVER log ciphertext, keys, or passwords
// ❌ NEVER use static/test keys in production
// ✅ ALWAYS generate fresh VaultKey per vault
// ✅ ALWAYS wrap VaultKey with user's MasterKey before storage
// ✅ ALWAYS verify decryption errors before proceeding
// ✅ ALWAYS use constant-time comparison for passwords (handled by Argon2)
```
## 测试
运行测试套件:
```
# 所有 vault 测试
cargo test --test vault
# 带详细输出
cargo test --test vault -- --nocapture
# 仅基于属性的测试
cargo test --test vault proptest
# 增加 CI 的 proptest 迭代
PROPTEST_CASES=1000 cargo test --test vault
```
### 测试覆盖范围
- ✅ 往返加密/解密 (空、小、大、二进制数据)
- ✅ nonce 唯一性 (100+ 次加密 → 所有 nonce 均不相同)
- ✅ 错误密钥 → `Err(Decryption)`,而非 panic
- ✅ 篡改 (密文、nonce、截断、追加) → 认证失败
- ✅ 密钥封装往返 + 跨密钥失败
- ✅ 边缘情况:全零、全一、空密文
- ✅ 完整工作流:加密 → 封装 → 解封 → 解密
## 依赖项
```
# Cargo.toml
[dependencies]
aes-gcm = "0.10"
chacha20poly1305 = "0.10"
rand = "0.8"
zeroize = { version = "1.7", features = ["derive"] }
[dev-dependencies]
proptest = "1.4"
```
## 另见
- [`src/kdf.rs`](../kdf.rs) — 用于 `MasterKey` 的 Argon2id 密钥派生
- [`src/errors.rs`](../errors.rs) — `CryptoError` 定义
- [`tests/vault.rs`](../../tests/vault.rs) — 综合测试套件
32 bytes] SS[SRP Salt
32 bytes] subgraph KDF["Key Derivation Functions"] MDK[derive_master_key
Argon2id] DSP[derive_srp_password
Argon2id] end MK[Master Key
32 bytes] SP[SRP Password
32 bytes] subgraph Crypto["Cryptographic Operations"] ENC[Encrypt Ed25519
Private Key] SRP[Generate SRP
Verifier/Proof] end end subgraph Server["🖥️ Server Storage"] AS_STORE[Argon2 Salt
Stored] SS_STORE[SRP Salt
Stored] V[SRP Verifier
Stored] ENC_KEY[Encrypted
Ed25519 Key] end P --> MDK P --> DSP AS --> MDK SS --> DSP MDK --> MK DSP --> SP MK --> ENC SP --> SRP AS -.-> AS_STORE SS -.-> SS_STORE ENC --> ENC_KEY SRP --> V ENC_KEY -.-> Server V -.-> Server AS_STORE -.-> Client SS_STORE -.-> Client style MK fill:#f96,stroke:#333,stroke-width:3px style SP fill:#f96,stroke:#333,stroke-width:2px style P fill:#bbf,stroke:#333,stroke-width:2px style KDF fill:#ffebcd,stroke:#333,stroke-width:2px ``` ### 🔒 安全保证 | 特性 | 实现 | 益处 | |---------|----------------|---------| | **算法** | Argon2id v0x13 | 内存困难,抵抗 GPU/ASIC 攻击 | | **内存开销** | 64 MB (可配置) | 防止并行暴力破解攻击 | | **时间开销** | 3 次迭代 | 增加每次猜测的计算成本 | | **并行度** | 4 个线程 | 针对现代 CPU 优化 | | **输出长度** | 32 字节 (256 位) | 足以用于对称加密 | | **盐值唯一性** | 每用户 32 字节随机值 | 防止彩虹表攻击 | | **密钥分离** | 主密钥/SRP 使用独立盐值 | 秘密隔离 | | **归零化** | 自动内存清除 | 防止密钥材料泄露 | ### 🛡️ 威胁模型防护 ``` graph LR subgraph Threats["🎯 Attack Vectors"] T1[Dictionary Attack] T2[Rainbow Tables] T3[GPU/ASIC Cracking] T4[Side-Channel] T5[Memory Dump] T6[Verifier Compromise] end subgraph Defenses["🛡️ Mitigations"] D1[High iteration count
3 rounds] D2[Unique random salts
32 bytes each] D3[Memory-hard function
64 MB RAM] D4[Constant-time ops
Argon2 library] D5[ZeroizeOnDrop
Secure memory] D6[Separate derivation
paths] end T1 --> D1 T2 --> D2 T3 --> D3 T4 --> D4 T5 --> D5 T6 --> D6 style Defenses fill:#d4edda,stroke:#28a745,stroke-width:2px style Threats fill:#f8d7da,stroke:#dc3545,stroke-width:2px ``` ### 📊 性能特征 **目标硬件:** 现代服务器 (Intel Xeon / AMD EPYC) **目标派生时间:** 300–500ms | 参数 | 值 | 影响 | |-----------|-------|--------| | 内存 (m_cost) | 64 MB | 每次派生的 RAM 需求 | | 迭代 (t_cost) | 3 | CPU 时间乘数 | | 并行度 (p_cost) | 4 | 线程利用率 | | **实际时间** (Mac Mini M1) | ~1175ms | ⚠️ 可能需要调整 | ### 🔄 密钥派生流程 #### 注册流程 ``` sequenceDiagram participant U as User participant C as Client App participant S as Server U->>C: Enter password C->>C: Generate argon2_salt (random) C->>C: Generate srp_salt (random) C->>C: derive_master_key(password, argon2_salt) C->>C: Generate Ed25519 keypair C->>C: Encrypt private key with master_key C->>C: derive_srp_password(password, srp_salt) C->>C: Generate SRP verifier C->>S: Register(argon2_salt, srp_salt, verifier, encrypted_key) S->>S: Store salts + verifier S-->>C: Registration successful Note over C: Password & master_key
NEVER leave client Note over U: Only encrypted data
sent to server ``` #### 登录流程 ``` sequenceDiagram participant U as User participant C as Client App participant S as Server U->>C: Enter password C->>S: Request login (username) S->>S: Lookup user S-->>C: Return argon2_salt, srp_salt C->>C: derive_master_key(password, argon2_salt) C->>C: Fetch encrypted private key C->>C: Decrypt with master_key C->>C: derive_srp_password(password, srp_salt) C->>C: Generate SRP proof C->>S: SRP proof S->>S: Verify proof S-->>C: Authenticated! Note over C: Master key derived
locally each time Note over U: Password never
transmitted ``` ### 🔧 配置 编辑 `src/kdf.rs` 以调整参数: ``` // OWASP 2024 minimums - ADJUST FOR YOUR HARDWARE const ARGON2_M_COST: u32 = 65536; // Memory: 64 MB const ARGON2_T_COST: u32 = 3; // Iterations: 3 const ARGON2_P_COST: u32 = 4; // Parallelism: 4 lanes // Target: 300-500ms derivation time on production server ``` **调整指南:** - **增加 `M_COST`** 如果服务器有剩余 RAM (更安全) - **增加 `T_COST`** 如果 CPU 快但 RAM 有限 - **减少两者** 如果派生对于用户体验来说太慢 (安全性降低) - **运行基准测试:** `cargo test --test kdf benchmark_derive_master_key -- --ignored` ### 🧪 测试 ``` # 运行所有 KDF 测试 cargo test --test kdf # 运行并输出计时 cargo test --test kdf -- --nocapture # 运行 benchmark(默认忽略) cargo test --test kdf benchmark_derive_master_key -- --ignored --nocapture # 测试所有模块 cargo test ``` ### 📦 依赖项 ``` [dependencies] argon2 = "0.5" # Argon2 implementation rand = "0.8" # Cryptographic RNG zeroize = { version = "1.7", features = ["derive"] } # Secure memory ``` ### 🔍 安全审计清单 - ✅ Argon2id (不是 Argon2i 或 Argon2d) - ✅ 每个用户每个目的使用唯一的盐值 - ✅ 不向服务器传输密码 - ✅ 主密钥永不离开客户端 - ✅ 敏感内存的归零化 - ✅ 恒定时间比较 (通过 Argon2 库) - ✅ 密码学安全的 RNG (OsRng) - ✅ MasterKey 结构体上无 Copy/Clone # 🔐 Vault 加密模块 ## 概述 `vault` 模块为敏感配置数据提供认证加密。它专为以下目的设计: - ✅ 在存储或传输前加密 `.env` 文件 - ✅ 使用用户主密钥封装 vault 密钥以进行安全的密钥管理 - ✅ 通过 AEAD (关联数据的认证加密) 确保完整性 - ✅ 在 drop 时从内存中归零化敏感密钥 ## 密码学原语 | 组成部分 | 算法 | 用途 | |-----------|-----------|---------| | **数据加密** | AES-256-GCM | 加密 `.env` 明文并附带认证标签 | | **密钥封装** | XChaCha20-Poly1305 | 使用用户 `MasterKey` 封装 `VaultKey` | | **KDF** | Argon2id (通过 `kdf` 模块) | 从用户密码派生 `MasterKey` | | **随机性** | `rand::OsRng` | 密码学安全的 nonce/密钥生成 | | **内存安全** | `zeroize::ZeroizeOnDrop` | 在 drop 时安全擦除密钥 | ## 常量 ``` pub const VAULT_KEY_LEN: usize = 32; // 256-bit key pub const NONCE_LEN: usize = 12; // 96-bit nonce for AES-GCM pub const XCHACHA_NONCE_LEN: usize = 24; // 192-bit nonce for XChaCha20 ``` ## 核心类型 ### `VaultKey` ``` #[derive(ZeroizeOnDrop)] pub struct VaultKey(pub [u8; VAULT_KEY_LEN]); ``` 用于加密 vault 数据的 32 字节对称密钥。在 drop 时自动归零化。 #### 方法 ``` impl VaultKey { /// Generate a new cryptographically random vault key pub fn generate() -> Self; } ``` ### `EncryptedBlob` ``` pub struct EncryptedBlob { pub nonce: [u8; NONCE_LEN], // 12-byte nonce for AES-GCM pub ciphertext: Vec
标签:AES-256-GCM, Argon2id, ECDH, Ed25519, Rust, SRP-6a, X25519, XChaCha20, ZKE, 内存安全, 加密库, 可视化界面, 安全管理, 安全远程密码协议, 密码学, 密钥封装, 密钥派生, 开发库, 手动系统调用, 抗暴力破解, 环境变量管理, 端到端加密, 网络安全, 网络流量审计, 通知系统, 隐私保护, 零知识加密, 零知识证明