9Elliot/Kalyx-Vault
GitHub: 9Elliot/Kalyx-Vault
Kalyx-Vault 是一个演示混合后量子密钥交换(X25519+ML-KEM-768)的移动应用和 API 项目,用于教育性地展示「现在收集,以后解密」的量子威胁场景。
Stars: 0 | Forks: 0
# Kalyx-Vault
**Kalyx-Vault** 是一个参考移动应用和 API,展示了 [Kalyx-PQ](https://pypi.org/project/kalyxpq/)(**`kalyxpq` on PyPI**):混合后量子密钥交换(X25519 + ML-KEM-768)和会话加密。目标是打造一个教育性、生产级演示,具有清晰的**"现在收集,以后解密"**叙事。
| 组件 | 技术 |
|-----------|------------|
| 移动应用 | React Native (Expo), TypeScript |
| 后端 | FastAPI (Python 3.10+) |
| 密码学(服务器)| 来自 PyPI 的 **`kalyxpq`**,带有 **`[msgpack,oqs]`** 扩展 |
| 密码学(客户端)| `@noble/curves`, `@noble/post-quantum`, `@noble/hashes`, `@noble/ciphers`, `@msgpack/msgpack` |
## 目录
1. [架构](#architecture)
2. [加密流程](#cryptographic-flow)
3. [前置条件](#prerequisites)
4. [后端设置](#backend-setup)
5. [Docker 部署(生产环境)](#docker-deployment-production)
6. [部署到 Render](#deploy-to-render)
7. [移动应用设置](#mobile-app-setup)
8. [API 参考](#api-reference)
9. [配置](#configuration)
10. [项目结构](#project-structure)
11. [故障排除](#troubleshooting)
12. [安全说明](#security-notes)
13. [参考资料](#references)
## 架构
```
sequenceDiagram
participant App as Expo app (JS)
participant API as FastAPI + kalyxpq
App->>API: POST /handshake (X25519 + ML-KEM-768 public keys, base64 JSON)
API->>API: KalyxEngine.server_respond (strict_pq)
API-->>App: server keys, pq ciphertext, session_id, telemetry
App->>App: Derive hybrid AES-256 key (HKDF-SHA3-256)
App->>API: POST /vault + X-Session-Id (KALX + AES-GCM + msgpack)
API->>API: Verify KALX, decrypt, @kalyx_safe response
API-->>App: Encrypted JSON ack (AES-GCM, session key)
```
- **握手** 匹配 `kalyxpq.HTTPHandshakeTransport` 使用的线格式:JSON 主体,带有 base64 编码的二进制字段。
- **会话绑定** 使用服务器签发的 **`session_id`** 头;服务器存储在 `server_respond` 期间派生的**相同 32 字节混合密钥**(客户端通过 JS 中的 `client_finalize` 语义独立派生)。
- **Vault 载荷** 使用 **`kalyxpq`** 的 **`MAGIC_HEADER`**(`KALX`,字节 `0x4B414C58`)前置到 AES-GCM 密文,**AAD** 设置为序列化器名称(`json` 或 `msgpack`)。
## 加密流程
### 混合握手(与 `KalyxEngine` 对齐)
1. **客户端** 生成一个 X25519 密钥对和一个 ML-KEM-768 密钥对。
2. **客户端 → 服务器**:经典和 PQ 公钥(JSON 中的 base64)。
3. **服务器** 运行 `KalyxEngine(strict_pq=True).server_respond(...)`,该函数:
- 使用客户端的经典密钥执行 X25519。
- 使用 **liboqs**(而非库内模拟 KEM)向客户端的 ML-KEM-768 公钥**封装**。
- 使用 **HKDF-SHA3-256** 派生 **32 字节**会话密钥,`salt = None`,`info = KALYX-PQ-HYBRID-HANDSHAKE-v1`。
4. **服务器 → 客户端**:服务器经典公钥、经典密文字段(与当前引擎中的服务器公钥相同)、PQ 密文、服务器上下文哈希,以及遥测数据和 `session_id`。
5. **客户端** 完成 X25519 和 ML-KEM **解封装**,然后运行**相同的 HKDF** 获取会话密钥。
严格 PQ 模式**拒绝**模拟 KEM:后端**必须**通过 **liboqs-python** 加载真实的 **ML-KEM-768**。
### Vault 通道(`KalyxSession` 布局)
- 明文被序列化(此应用中默认为 MessagePack),然后用 **AES-256-GCM** 加密。
- **线上的密文**:`MAGIC_HEADER (4 字节) || GCM 密文 || tag`。
- **AAD**:`serializer` 的 ASCII 字节(`msgpack` 或 `json`)。
### `@kalyx_safe` 响应
vault 端点返回 JSON,包含由 **`kalyx_safe`** 生成的 base64 `nonce` 和 `ciphertext`:JSON 对象的 AES-GCM 加密,**无 AAD**。移动客户端使用**相同的混合会话密钥**解密以显示确认信息。## 前置条件
### 后端
- **Python 3.10+**
- **pip** 和虚拟环境(推荐)
- **原生 liboqs** 可供 **liboqs-python** 使用(`strict_pq=True` 时必需)
### 移动端
- **Node.js**(LTS)和 **npm**
- **Expo CLI**(通过 `npx expo`)
- 对于物理设备:手机和电脑在**同一网络**(或使用隧道 URL),不使用 localhost 时
## 后端设置
```
cd backend
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
# source .venv/bin/activate
pip install -r requirements.txt
```
**运行 API**(绑定所有接口,以便手机/模拟器可以访问):
```
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
- **OpenAPI / Swagger UI:** [http://localhost:8000/docs](http://localhost:8000/docs)
- **健康检查:** `GET /health` 在引擎就绪时返回 `kem`(例如 `ML-KEM-768`)。
`KalyxEngine` 在 FastAPI 的 **lifespan** 上下文中创建,这样**导入** `app.main` 不会立即加载 liboqs;初始化(和严格 PQ 检查)在服务器启动时发生。## Docker 部署(生产环境)
**`backend/Dockerfile`** 是一个**多阶段**镜像:
1. **构建阶段** — Debian **bookworm-slim**,带编译器工具链(**`build-essential`**, **`cmake`**, **`ninja-build`**, **`git`**, **`libssl-dev`**, **`pkg-config`**),以便 **`pip install -r requirements.txt`** 可以在 Linux 上正确构建或链接 **`kalyxpq[oqs]`** / **liboqs-python**。
2. **运行阶段** — 新 slim 镜像,仅包含 **`libssl3`**, **`ca-certificates`**, 复制的虚拟环境和 **`backend/app`**。进程以非特权 **`kalyx`** 用户运行(UID 10001)。**`HEALTHCHECK`** 访问 **`GET /health`**。
从**仓库根目录**构建(不是 `backend/`):
```
docker build -f backend/Dockerfile -t kalyx-vault-api .
```
运行(设置 **`CORS_ORIGINS`** 为您的生产应用源;参见[配置](#configuration)):
```
docker run --rm -p 8000:8000 \
-e CORS_ORIGINS="https://your-expo-web-host.example,https://your-custom-domain.example" \
kalyx-vault-api
```
对于生产环境中的 HTTPS,请在负载均衡器或反向代理(例如 Caddy、nginx、Traefik)处终止 TLS,并在容器内保持 Uvicorn 运行 HTTP。## 部署到 Render
[Render](https://render.com) 运行您的 Docker 镜像并分配一个公共 **HTTPS** URL(例如 `https://kalyx-vault-api.onrender.com`)。镜像监听 Render 通过 **`PORT`** 环境变量提供的端口(Dockerfile 已使用 **`${PORT:-8000}`**)。
### 选项 A — Blueprint(推荐)
1. 将 **Kalyx-Vault** 推送到 **GitHub**(或 Render 连接的 GitLab / Bitbucket)。
2. 在 Render 中:**New** → **Blueprint** → 连接仓库 → 选择 **`render.yaml`**。
3. 出现提示时,设置 **`CORS_ORIGINS`** 为任何将调用 API 的**浏览器**源(Expo web、本地开发等),例如:
`https://your-production-site.example,http://localhost:8081,http://localhost:19006`
(如果未设置,则仅允许默认的本地开发源;原生 Expo 应用通常不发送浏览器 `Origin`,因此应用可能仍能工作。)
4. 等待**首次构建** — 安装和编译 **liboqs** 在免费套餐上可能需要 **10–20+ 分钟**。
5. 在浏览器中打开 **`https://.onrender.com/health`**。您应该看到包含 `"status":"ok"` 和 **`"kem":"ML-KEM-768"`**(或类似)的 JSON。
### 选项 B — Docker Web Service(手动)
1. **New** → **Web Service** → 连接仓库。
2. **Runtime:** **Docker**。
3. **Dockerfile 路径:** `backend/Dockerfile`。
4. **Docker 构建上下文:** **仓库根目录**(`.`)。
5. **检查路径:** `/health`。
6. 添加**环境变量** **`CORS_ORIGINS`**(同上,对 Expo web 可选但推荐)。
7. 部署并以相同方式测试 **`/health`**。
### Render 免费套餐说明
- 服务在空闲后**关闭**;睡眠后的**首次请求**可能需要 **~30–60 秒**(冷启动)。
- 构建可能**超时**,如果超过免费套餐限制;如发生这种情况,升级实例或使用付费计划进行初始镜像构建。
### Docker 中的 liboqs(Render)
**liboqs-python** 通常在 **`OQS_INSTALL_PATH`**(默认:**`$HOME/_oqs`**)下查找 `liboqs.so`。在最小化运行时镜像中没有 **`git`** 或编译器,且 **`kalyx`** 用户没有真正的家目录——因此自动安装会失败,显示 `git: not found` 或 **No oqs shared libraries found**。
**`backend/Dockerfile`** 设置 **`OQS_INSTALL_PATH=/opt/oqs`**,在 **`pip install`** 之前从真实 git 标签(**`LIBOQS_REF`**,默认 **`0.14.0`**)**克隆并 cmake-install** **liboqs C 库**,然后运行 **`import oqs`** 使绑定加载 **`/opt/oqs/lib`**,而不使用 liboqs-python 损坏的自动安装程序。
**原因:** 最近 PyPI 上的 **`liboqs-python`** 发布版本(例如 **0.14.1**)尝试从 [open-quantum-safe/liboqs](https://github.com/open-quantum-safe/liboqs) 执行 `git clone --branch 0.14.1`,但该**分支/标签不存在**(仓库有 **0.14.0**, **0.15.0** 等)。预构建完全避免了该路径。
构建镜像时覆盖 C 库版本:
```
docker build -f backend/Dockerfile --build-arg LIBOQS_REF=0.15.0 -t kalyx-vault-api .
```
使用与 **`kalyxpq[oqs]`** 引入的 **`liboqs-python`** 版本 **ABI 兼容**的标签(同一小版本最安全)。
### 将移动应用指向 Render
使用您的服务 URL,**不要尾随斜杠**:
```
# Windows PowerShell
cd mobile
$env:EXPO_PUBLIC_API_URL="https://kalyx-vault-api.onrender.com"; npx expo start
```
```
# macOS / Linux
cd mobile
EXPO_PUBLIC_API_URL=https://kalyx-vault-api.onrender.com npx expo start
```
或创建 **`mobile/.env`**(Expo 自动加载):
```
EXPO_PUBLIC_API_URL=https://kalyx-vault-api.onrender.com
```
对于**发布**构建,同时在 **`mobile/src/config.ts`** 中设置 **`PRODUCTION_API_URL`** 为相同 URL,或依赖 [EAS Build](https://docs.expo.dev/build-reference/variables/) / CI 中的 **`EXPO_PUBLIC_API_URL`**。## 移动应用设置
```
cd mobile
npm install
npx expo start
```
然后选择 **iOS 模拟器**、**Android 模拟器**、设备上的 **Expo Go**,或 **web**(注意:web 使用浏览器加密;RN polyfills 已为原生配置)。
### 在笔记本电脑上运行并冒烟测试
1. **本地后端**(可选第一步):从 **`backend`** 运行 Uvicorn 在 **`0.0.0.0:8000`**,然后在 **`mobile`** 运行 `npx expo start` 并使用默认开发 URL(`http://localhost:8000`)。对于 **Android 模拟器**,使用 **`EXPO_PUBLIC_API_URL=http://10.0.2.2:8000`** 以便模拟器访问您的主机。
2. **Render 上的后端**:将 **`EXPO_PUBLIC_API_URL`** 设置为您的 **`https://…onrender.com`** URL(参见[部署到 Render](#deploy-to-render)),然后运行 `npx expo start`。
3. 在终端 UI 中,按 **`w`** 在浏览器中运行 **Expo web**(快速检查 UI 和 fetch),或如果您安装了 **Android Studio** / **Xcode** 模拟器则按 **`a`** / **`i`**,或用手机上的 **Expo Go** 扫描二维码(与笔记本电脑在同一 Wi-Fi 下)。
4. 在应用中:点击 **Initiate Handshake** — 如果 API 可达,应该成功。然后尝试 **Securely Send** 发送一条短消息。
**笔记本电脑上的要求:** **Node.js (LTS)**、**npm**,以及原生模拟器需要 **Android Studio**(SDK + 模拟器)或 **Xcode**(仅限 macOS)。如果您只想验证逻辑而不使用模拟器,**Expo web**(`w`)就足够了。
### 随机性(React Native)
`react-native-get-random-values` 在 `mobile/index.ts` 顶部导入,以便 CSPRNG 支持的操作(KEM、AES nonce)在 React Native 运行时中工作。## API 参考
### `GET /health`
| 字段 | 描述 |
|-------|-------------|
| `status` | 服务时返回 `"ok"` |
| `kem` | 活动 KEM 名称(例如 `ML-KEM-768`)|
| `strict_pq` | `"true"` |
如果密码引擎未初始化则返回 **503**(成功启动后不应发生)。### `POST /handshake`
**Content-Type:** `application/json`
**请求体**
| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `client_classical_public_key` | 字符串 (base64) | 32 字节 X25519 公钥 |
| `client_pq_public_key` | 字符串 (base64) | ML-KEM-768 公钥(1184 字节)|
**响应体**
| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `server_classical_public_key` | base64 | 服务器 X25519 公钥 |
| `classical_ciphertext` | base64 | 当前 `kalyxpq` 引擎中与服务器经典公钥相同 |
| `pq_ciphertext` | base64 | ML-KEM 密文 |
| `server_context` | base64 | 上下文绑定哈希 |
| `session_id` | 字符串 | UUID;在 `/vault` 上作为 `X-Session-Id` 发送 |
| `telemetry` | 对象 | 见下文 |
**`telemetry` 对象**
| 字段 | 描述 |
|-------|-------------|
| `duration_ms` | 服务器端混合握手时间 |
| `kem_algorithm` | 例如 `ML-KEM-768` |
| `client_artifact_bytes` | 客户端经典 + PQ 公钥的大小 |
| `server_response_bytes` | 遥测中使用的服务器响应字段大小 |
| `total_overhead_bytes` | 客户端 + 服务器握手字节(近似线传输成本)|
### `POST /vault`
**请求头**
| 请求头 | 必需 | 描述 |
|--------|----------|-------------|
| `X-Session-Id` | 是 | 来自 `handshake` 响应的值 |
| `Content-Type` | 是 | `application/json` |
**请求体**
| 字段 | 类型 | 描述 |
|-------|------|-------------|
| `nonce` | base64 | 12 字节 GCM nonce |
| `ciphertext` | base64 | `KALX` 魔数 + GCM 密文 + tag |
| `serializer` | `"msgpack"` 或 `"json"` | 必须与密封时使用的 AAD 匹配 |
**解密后的明文**必须是包含 **`note`** 字段的对象(字符串或 JSON 兼容值)。
**响应体**
| 字段 | 描述 |
|-------|-------------|
| `nonce` | base64 GCM nonce |
| `ciphertext` | base64;使用会话密钥解密,无 AAD(`@kalyx_safe` 格式)|
**错误情况**
- **401** — 缺少或未知的 `X-Session-Id`
- **400** — 密文缺少 **`KALX** 头、解密失败或载荷格式无效
## 配置
### 移动端 API 基础 URL(`mobile/src/config.ts`)
解析顺序:
1. **`EXPO_PUBLIC_API_URL`** — 如果设置,始终使用(EAS Secrets、`.env` 或 shell)。将其用于设备测试针对 LAN IP 或强制使用暂存 API。
2. **`__DEV__`** — 当为 `true`(Metro / Expo dev)时,默认为 **`http://localhost:8000`**。
3. **`PRODUCTION_API_URL`** — 当 `EXPO_PUBLIC_API_URL` 未设置时,用于**发布**构建。**编辑此常量**为您部署的 API 源(HTTPS,无尾随斜杠)。
示例:
```
# Android emulator → 主机
EXPO_PUBLIC_API_URL=http://10.0.2.2:8000 npx expo start
# LAN 上的物理设备
EXPO_PUBLIC_API_URL=http://192.168.1.50:8000 npx expo start
```
| 环境 | 典型 URL |
|-------------|-------------|
| iOS 模拟器(Mac)| `http://localhost:8000`(开发默认)|
| Android 模拟器 | `://10.0.2.2:8000`(通过 `EXPO_PUBLIC_API_URL`)|
| 物理设备 | `http://<您的局域网IP>:8000` 或生产 HTTPS |
| 应用商店 / 生产构建 | `PRODUCTION_API_URL` 或 `EXPO_PUBLIC_API_URL` |
项目为 **Android `usesCleartextTraffic`** 和 **iOS `NSAllowsLocalNetworking`** 设置为**仅用于 HTTP 开发**。生产环境应使用 **HTTPS**。
### 后端 CORS
`CORSMiddleware` 使用明确的**允许列表**(无 `*`),`allow_credentials=True`。
- **`CORS_ORIGINS`** — 允许的 **`Origin`** 值的逗号分隔列表,用于您的 **Expo web** / 浏览器客户端,例如
`https://your-app.expo.app,https://www.yourdomain.com`
- 如果设置了 **`CORS_ORIGINS`**,这些源将与 Expo 和本地 web 使用的内置 **localhost / 127.0.0.1** 端口集(`8081`, `19006`, `19000`, `3000`)**合并**,以便本地开发与生产环境同时工作。
- 如果 **`CORS_ORIGINS`** **未设置**,仅允许这些**本地开发**源(适用于 API 纯或原生应用测试,浏览器源不适用)。
**注意:** React Native **原生** `fetch` 通常不发送浏览器 `Origin` 头;CORS 主要影响 **Expo Web** 和从浏览器打开 API 的工具。您仍应为您调用 API 的任何生产 web URL 设置 **`CORS_ORIGINS`**。## 项目结构
```
Kalyx-Vault/
├── README.md # This file
├── render.yaml # Optional Render Blueprint (Docker web service)
├── backend/
│ ├── Dockerfile # Multi-stage production image (liboqs build deps)
│ ├── .dockerignore
│ ├── requirements.txt # fastapi, uvicorn, kalyxpq[msgpack,oqs], pydantic
│ └── app/
│ ├── __init__.py
│ └── main.py # FastAPI routes, CORS, handshake, vault, kalyx_safe
└── mobile/
├── App.tsx # Security dashboard UI
├── index.ts # RNG polyfill + Expo entry
├── app.json # Expo config (cleartext / local networking for dev)
└── src/
├── config.ts # API_BASE_URL (dev / prod / EXPO_PUBLIC_API_URL)
└── crypto/
└── kalyxClient.ts # Handshake + KALX sealing (protocol-compatible JS)
```
## 故障排除
| 症状 | 可能原因 | 尝试方案 |
|--------|----------------|-------------|
| 后端启动时退出,显示 Strict-PQ / MOCK KEM | liboqs 未加载;模拟 KEM 激活 | 为 **liboqs-python** 安装 OS 包或 wheel;在 Linux/macOS/WSL 上运行 |
| 容器中出现 `No oqs shared libraries found` / `git: not found` | liboqs 不在镜像中;运行时自动安装需要 git + 构建工具 | 使用提供的 **Dockerfile**(`OQS_INSTALL_PATH=/opt/oqs`,在构建阶段预构建)。在裸金属上,安装 liboqs 或使用构建依赖运行一次 `import oqs` 以便它可以安装 |
| 移动端:网络请求失败 | 基础 URL 错误或防火墙 | 使用 LAN IP 或 `10.0.2.2`;在主机上允许端口 8000 |
| 握手 400 / 解密错误 | JS 和服务器之间的 ML-KEM 不匹配 | 确保服务器报告 `ML-KEM-768`;如果向量变化则更新 `@noble/post-quantum` |
| iOS 设备无法访问 `http://localhost:8000` | localhost 是手机本身 | 使用主机 IP + `EXPO_PUBLIC_API_URL` |
## 安全说明
此仓库是一个**演示**:
- **会话密钥**和**笔记**保存在服务器的**内存**中;重启进程会清除它们。
- **无认证**机制,除了拥有 `session_id`;将其视为实验室,而非多租户 vault。
- **CORS** 是明确的源列表(通过 **`CORS_ORIGINS`** 的生产 URL 加上默认的本地开发源)。**明文 HTTP** 仅用于移动端本地开发。
- JavaScript 中的客户端密码学**不能**替代高保障部署的硬件密钥或经过审计的原生模块。
有关威胁模型和库 API,请参阅 **[kalyxpq PyPI 页面](https://pypi.org/project/kalyxpq/)** 和上游 **[Kalyx-PQ](https://github.com/9Elliot/Kalyx-PQ)** 文档。## 参考资料
- [kalyxpq on PyPI](https://pypi.org/project/kalyxpq/)
- [FastAPI](https://fastapi.tiangolo.com/)
- [Expo](https://docs.expo.dev/)
- [@noble/post-quantum](https://www.npmjs.com/package/@noble/post-quantum)(ML-KEM-768)
- [NIST FIPS 203 (ML-KEM)](https://csrc.nist.gov/publications/detail/fips/203/final)
## 许可证
遵循您的依赖项的许可证(**kalyxpq** 依据 PyPI 为 Apache-2.0;**noble** 库为 MIT)。如果您将此仓库作为产品分发,请添加您自己的项目许可证。
标签:AES-256, AES-GCM, API安全, AV绕过, E2EE, Expo, FastAPI, Harvest Now Decrypt Later, HKDF, Hybrid Key Exchange, JSON输出, kalyxpq, Key Derivation, ML-KEM-768, Msgpack, NIST后量子密码标准, noble密码库, Post-Quantum Cryptography, Python, Quantum-Safe, React Native, Session Encryption, SHA3-256, TypeScript, X25519, 会话加密, 后量子密码学, 安全插件, 密钥派生, 无后门, 混合密钥交换, 漏洞评估, 目录枚举, 移动安全, 端到端加密, 自动化攻击, 请求拦截, 逆向工具, 量子安全