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
标签:逆向工具