Rohan-Prabhakar/QPQT
GitHub: Rohan-Prabhakar/QPQT
QPQT是一个量子安全的列式存储格式,提供高效的查询性能和行粒度懒加载解密。
Stars: 0 | Forks: 0
# QPQT - 量子安全列式存储格式
一个专为二进制列式文件格式(`.qpqt`)设计的格式,具有本地
后量子密码学和行粒度懒加载解密功能,这是现有列式格式所不具备的。
**加密堆栈:** ML-KEM-768 (FIPS 203) + HKDF-SHA-256 + AES-256-GCM (FIPS 197)
## 快速入门
```
pip install qpqt
```
```
import qpqt
# 生成量子安全密钥对
pub, sec = qpqt.keygen()
kid = qpqt.generate_key_id()
# 加密 - ssn 列受 ML-KEM-768 + AES-256-GCM 保护
w = qpqt.Writer("customers.qpqt",
column_names=["id", "state", "ssn"],
column_types=["int32", "string", "string"],
pqc_columns=["ssn"],
public_key=pub, key_id=kid)
w.write_batch({"id":[1,2,3], "state":["CA","NY","TX"], "ssn":["111","222","333"]}, 3)
w.close()
# 读取 - 懒惰解密,仅解密匹配的行
r = qpqt.Reader("customers.qpqt")
r.set_secret_key(sec)
data = r.query(where={"id": 2})
```
轮子捆绑了liboqs和OpenSSL。无需系统依赖。
## 问题
企业面临双重使命:监管压力要求采用后量子
密码学(CNSA 2.0,NIST FIPS 203,截止日期2035)以及在大规模列式数据仓库上
保持查询性能的需求。
天真方法 - 在行级别应用ML-KEM-768 - 即使有4核并行化,1M行的成本也
为**9,600ms**。这确立了问题的上限:错误实现的PQC在分析查询规模上
是不可用的。
## 解决方案
QPQT围绕PQC成本重新设计了存储格式:
1. **混合KEM构建** - 每个包含4,096行的页面使用ML-KEM-768来封装AES-256-GCM页面密钥。
这将KEM操作从每百万行1M减少到250。
2. **完全分离的列部分** - 结构性(未加密)和PQC列在磁盘上以4KB OS页面边界物理隔离。
在CPU缓存中不加载PQC部分的情况下,谓词在结构列上运行。
3. **行粒度懒加载解密** - 谓词首先在廉价的结构列上执行。只有那些在谓词触发
KEM解封装和AES-GCM解密后幸存的单个行。
4. **O(1)清单查找** - 页脚中的平面加密清单通过指针运算将任何行映射到其页面密钥。
## 性能 - 坦诚的三基准比较
在Kaggle Xeon CPU(4核)、1M行、真实ML-KEM-768 + AES-256-GCM上进行基准测试。
测量了两个基准,而不是估计:
- **天真每行PQC** - 行级ML-KEM封装。确立了问题的上限。这是快速`liboqs`集成产生的结果。
- **有能力的每页PQC** - 正确的混合KEM构建(每页ML-KEM + AES-GCM,就像QPQT一样),但以
纯布局存储,没有列分离,没有懒加载解密。由于解密是块粒度的,因此解密查询列中的每行。
这隔离了QPQT的实际贡献。
| 选择性 | 天真每行 | 有能力每页 | QPQT | QPQT vs 有能力 |
|---|---|---|---|---|
| 1% | 9,600ms | 2,150ms | **78ms** | **27.6x** |
| 5% | 9,600ms | 2,111ms | **163ms** | **12.9x** |
| 10% | 9,600ms | 2,113ms | **264ms** | **8.0x** |
| 25% | 9,600ms | 2,103ms | **557ms** | **3.8x** |
| 50% | 9,600ms | 2,148ms | **1,055ms** | **2.0x** |
| 100% | 9,600ms | 2,147ms | **2,098ms** | **1.02x (no advantage)** |
QPQT的贡献是行粒度懒加载解密。在低选择性 - 分析查询的常见情况 - 它解密
的行比有能力的列式未知实现少得多,提供了8-27x。当选择性接近100%
时,优势缩小到相等。**在100%选择性下,QPQT没有比有能力的每页PQC
提供任何优势** - 当每行都通过谓词时,就没有什么可以跳过的。
| 指标 | 值 |
|---|---|
| 写入吞吐量(1M行) | 534K行/秒(1,871ms) |
| 结构性扫描(无加密) | 5ms,188M行/秒 |
| 文件大小(1M行) | 80MB |
| 存储与天真每行ML-KEM | 80MB vs ~1,084MB(92%减少) |
## 密码设计
```
ML-KEM-768 keypair -> secret key stored in KMS (file holds only key_id)
|
Per page (4,096 rows):
ML-KEM-768 encapsulate(public_key)
|-- kem_ciphertext -> CRYPTO MANIFEST
+-- shared_secret (32 bytes)
|
HKDF-SHA-256(shared_secret, page_context)
+-- aes_page_key (32 bytes, unique per page)
|
AES-256-GCM per row
|-- IV (12B, deterministic)
|-- ciphertext (= plaintext length)
+-- auth_tag (16B, tamper detection)
```
### IV构建和GCM非安全性
QPQT使用确定性的AES-GCM IV。**这是安全的,因为nonce唯一性在
每个密钥范围内是保证的。**每个包含4,096行的页面通过ML-KEM封装
+ HKDF-SHA-256推导出自己的唯一AES-256密钥。IV只需要在给定的
密钥下是唯一的,并且在单个页面密钥中,`(row_index, column_index)`
元组通过构造是唯一的。`file_uuid`组件防止页面密钥在文件之间
重复使用时的跨文件冲突。在任何单个密钥下都没有nonce重复 - 打破GCM
的失败模式不会发生。
所有组件都是NIST批准的,并且是量子安全的:
- ML-KEM-768:FIPS 203(替换RSA/ECDH用于密钥建立)
- AES-256-GCM:FIPS 197(量子安全对称;Grover的算法只减半
有效密钥强度,留下128位安全性)
- HKDF-SHA-256:RFC 5869 / SP 800-56C
## 为什么需要单独的格式(而不是Parquet)?
Parquet已经有了模块化加密 - 为什么不从中提取AES密钥
并得到今天的量子安全Parquet?
仅就加密而言,你可以。加密不是贡献。
贡献是行粒度懒加载解密。Parquet支持谓词
下推,并且可以通过页脚统计信息跳过整个加密列块。
它无法做到的是仅解密谓词没有全部消除的块中的幸存行。
Parquet以块粒度解密,而不是幸存行粒度。填补这个差距需要
物理分离的结构列和每行可寻址的密钥清单 - 不同的文件布局。
现有格式同时满足的三个条件:
1. 结构性列在操作系统页面边界上与加密列物理分离,因此过滤器
永远不会将加密部分页入缓存。
2. 每行的解密密钥在O(1)内可访问,无需先解密任何内容 - 页脚中的平面
清单。
3. 在页面内以单行粒度表达解密。Parquet将块视为一个原子加密单元。
想法很简单。使它可执行的是格式贡献。
## 文件格式
```
+-----------------------------------------------------+
| FILE HEADER (48 bytes) |
| magic + version + file_uuid + total_rows + offsets |
+-----------------------------------------------------+
| SCHEMA BLOCK (variable) |
+-----------------------------------------------------+
| KEY REFERENCE BLOCK (32 bytes) - key_id, not the key|
+-----------------------------------------------------+
| ROW GROUP 0 (100,000 rows) |
| |-- SECTION 1: Structural columns (unencrypted) |
| | [tightly packed, padded to 4KB boundary] |
| +-- SECTION 2: PQC columns (AES-256-GCM per row) |
| [starts on 4KB OS page boundary] |
+-----------------------------------------------------+
| ROW GROUP 1 ... N |
+-----------------------------------------------------+
| FILE FOOTER |
| |-- Row group offset table |
| |-- CRYPTO MANIFEST (flat array, O(1) lookup) |
| +-- FOOTER HEADER (40 bytes) + CRC32 |
+-----------------------------------------------------+
```
## 密钥管理
```
# Python
pub, sec = qpqt.keygen()
kid = qpqt.generate_key_id()
# 命令行界面(从源代码构建)
./qpqt keygen --out-pub pub.bin --out-sec sec.bin
```
- 公钥(1184字节) - 可以安全地与写入者共享。
- 秘密密钥(2400字节) - **永不共享,永不提交。**
- 密钥ID(16字节) - 存储在文件头中,而不是密钥本身。
**如果您丢失了秘密密钥,使用其公钥加密的数据将永久
不可恢复。**
| 环境 | 建议的密钥存储 |
|---|---|
| 本地开发 | 在存储库之外,例如 `~/.qpqt/keys/` |
| AWS | AWS KMS + Secrets Manager |
| Azure | Azure Key Vault |
| GCP | Cloud KMS |
| Databricks | `dbutils.secrets` |
| 本地 | HashiCorp Vault或HSM |
密钥轮换无需重写现有数据文件 - QPQT在头中存储`key_id`引用,
而不是密钥本身。
## 从源代码构建
对于CLI使用或贡献:
```
# 先决条件:Ubuntu 22.04+,CMake 3.16+,C++17,OpenSSL 开发头文件
bash scripts/install_deps.sh # builds liboqs from source
mkdir build && cd build
cmake .. && make -j$(nproc)
./qpqt_tests # run all 39 tests
./qpqt_bench # reproduce the benchmark table
```
## 生态系统集成
| 工具 | 如何 |
|---|---|
| Python / pandas | `pip install qpqt` |
| CLI | `qpqt encrypt/decrypt/inspect`在CSV或Parquet上(从源代码构建) |
| DuckDB / Polars / Spark | `qpqt_arrow export`将结构列作为Arrow IPC产生 |
## 路线图
- **v0.1(当前):** PyPI轮,完整的加密堆栈,CLI,Python绑定,Arrow导出,39个测试
- **v0.2:** pandas `read_qpqt` / `to_qpqt`单行命令,CLI中的Parquet读写,DuckDB配方
- **v1.0:** Spark DataSource连接器,ML-DSA-65元数据签名,威胁模型文档
- **v2.0:** 分布式操作,S3/Azure直接集成
## 许可证
MIT
## 作者
Rohan Prabhakar
标签:逆向工具