关于 wycheproof
Wycheproof 是一组用于验证密码实现正确性并测试已知攻击的测试向量,面向密码库开发者和审计人员。测试向量按算法与参数分组,并标注预期结果及潜在风险。
主要功能
- 01覆盖多种密码算法与曲线
- 02提供有效、可接受和无效用例
- 03按密钥尺寸等参数分组
- 04JSON 格式测试向量
- 05支持测试框架与 CI 集成
适用场景
- 01验证 AES-GCM 加解密实现
- 02检查 ECDSA 签名编码处理
- 03测试 ECDH 对无效曲线点的处理
- 04在 CI 中检测密码实现回归
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trailofbits/ skills --skill wycheproof安装后在 Claude Code 中输入/wycheproof确认已加载。
附带文件agents/openai.yamlassets/trail-of-bits-mark.svg
详细介绍
Wycheproof
Wycheproof 是一个庞大的测试向量集合,旨在验证加密实现的正确性,并针对已知攻击进行测试。该项目最初由 Google 开发,如今由社区维护,贡献者可以为特定的加密结构添加测试向量。
背景
核心概念
| 概念 | 描述 |
|---|---|
| 测试向量 | 用于验证加密实现是否正确的输入/输出对 |
| 测试组 | 一组共享属性(密钥大小、IV 大小、曲线)的测试向量 |
| 结果标志 | 表示测试应通过(有效)、失败(无效)还是可接受 |
| 边界情况测试 | 针对已知漏洞和攻击模式进行测试 |
为什么这很重要
加密实现向来很难做到正确无误。即使是微小的错误也可能:
- 泄露私钥
- 允许伪造签名
- 使消息可被解密
- 在不同实现对相同输入作出不同接受/拒绝判定时引发共识问题
Wycheproof 曾在多个主要库中发现漏洞,包括 OpenJDK 的 SHA1withDSA、Bouncy Castle 的 ECDHC 和 elliptic npm 包。
使用场景
以下情况适用 Wycheproof:
- 测试加密实现(AES-GCM、ECDSA、ECDH、RSA 等)
- 验证加密代码能否正确处理边界情况
- 根据已知攻击向量验证实现
- 为加密库设置 CI/CD
- 审查第三方加密代码的正确性
以下情况可考虑替代方案:
- 测试时序侧信道(改用恒定时间测试工具)
- 发现未知的新漏洞(改用模糊测试)
- 测试自定义/实验性加密算法(Wycheproof 仅涵盖成熟算法)
快速参考
| 场景 | 推荐方法 | 备注 |
|---|---|---|
| AES-GCM 实现 | 使用 aes_gcm_test.json | 44 个测试组中共有 316 个测试向量 |
| ECDSA 验证 | 针对特定曲线使用 ecdsa_*_test.json | 测试签名可塑性和 DER 编码 |
| ECDH 密钥交换 | 使用 ecdh_*_test.json | 测试无效曲线攻击 |
| RSA 签名 | 使用 rsa_*_test.json | 测试填充预言机攻击 |
| ChaCha20-Poly1305 | 使用 chacha20_poly1305_test.json | 测试 AEAD 实现 |
测试工作流
Phase 1: Setup Phase 2: Parse Test Vectors
┌─────────────────┐ ┌─────────────────┐
│ Add Wycheproof │ → │ Load JSON file │
│ as submodule │ │ Filter by params│
└─────────────────┘ └─────────────────┘
↓ ↓
Phase 4: CI Integration Phase 3: Write Harness
┌─────────────────┐ ┌─────────────────┐
│ Auto-update │ ← │ Test valid & │
│ test vectors │ │ invalid cases │
└─────────────────┘ └─────────────────┘
仓库结构
Wycheproof 仓库的组织结构如下:
┣ 📜 README.md : Project overview
┣ 📂 doc : Documentation
┣ 📂 java : Java JCE interface testing harness
┣ 📂 javascript : JavaScript testing harness
┣ 📂 schemas : Test vector schemas
┣ 📂 testvectors : Test vectors
┗ 📂 testvectors_v1 : Updated test vectors (more detailed)
核心文件夹是 testvectors 和 testvectors_v1。两者包含相似的文件,但 testvectors_v1 提供了更详细的信息,建议新集成使用该目录。
支持的算法
Wycheproof 为广泛的加密算法提供测试向量:
| 类别 | 算法 |
|---|---|
| 对称加密 | AES-GCM、AES-EAX、ChaCha20-Poly1305 |
| 签名 | ECDSA、EdDSA、RSA-PSS、RSA-PKCS1 |
| 密钥交换 | ECDH、X25519、X448 |
| 哈希 | HMAC、HKDF |
| 曲线 | secp256k1、secp256r1、secp384r1、secp521r1、ed25519、ed448 |
测试文件结构
每个 JSON 测试文件都针对一种特定的加密结构进行测试。所有测试文件都包含一些共同属性:
"algorithm" : The name of the algorithm tested
"schema" : The JSON schema (found in schemas folder)
"generatorVersion" : The version number
"numberOfTests" : The total number of test vectors in this file
"header" : Detailed description of test vectors
"notes" : In-depth explanation of flags in test vectors
"testGroups" : Array of one or multiple test groups
测试组
测试组根据共享属性(例如以下内容)对测试集合进行分组:
- 密钥大小
- IV 大小
- 公钥
- 曲线
这种分类方式可以提取符合特定条件、且与被测结构相关的测试。
测试向量属性
通用属性
所有测试向量都包含四个通用字段:
- tcId:文件中测试向量的唯一标识符
- comment:关于测试用例的补充信息
- flags:特定测试用例类型和潜在危险的说明(在
notes字段中引用) - result:测试的预期结果
result 字段可以取三个值:
| 结果 | 含义 |
|---|---|
| valid | 测试用例应成功 |
| acceptable | 测试用例可以成功,但包含不理想的属性 |
| invalid | 测试用例应失败 |
独有属性
独有属性是所测算法特有的:
| 算法 | 独有属性 |
|---|---|
| AES-GCM | key、iv、aad、msg、ct、tag |
| ECDH secp256k1 | public、private、shared |
| ECDSA | msg、sig、result |
| EdDSA | msg、sig、pk |
实现指南
阶段 1:将 Wycheproof 添加到项目中
选项 1:Git 子模块(推荐)
将 Wycheproof 添加为 Git 子模块,可确保自动更新:
git submodule add https://github.com/C2SP/wycheproof.git
选项 2:获取特定测试向量
如果无法使用子模块,可获取特定的 JSON 文件:
#!/bin/bash
TMP_WYCHEPROOF_FOLDER=".wycheproof/"
TEST_VECTORS=('aes_gcm_test.json' 'aes_eax_test.json')
BASE_URL="https://raw.githubusercontent.com/C2SP/wycheproof/master/testvectors_v1/"
# Create wycheproof folder
mkdir -p $TMP_WYCHEPROOF_FOLDER
# Request all test vector files if they don't exist
for i in "${TEST_VECTORS[@]}"; do
if [ ! -f "${TMP_WYCHEPROOF_FOLDER}${i}" ]; then
curl -o "${TMP_WYCHEPROOF_FOLDER}${i}" "${BASE_URL}${i}"
if [ $? -ne 0 ]; then
echo "Failed to download ${i}"
exit 1
fi
fi
done
阶段 2:解析测试向量
找到对应算法的测试文件并解析 JSON:
Python 示例:
import json
def load_wycheproof_test_vectors(path: str):
testVectors = []
try:
with open(path, "r") as f:
wycheproof_json = json.loads(f.read())
except FileNotFoundError:
print(f"No Wycheproof file found at: {path}")
return testVectors
# Attributes that need hex-to-bytes conversion
convert_attr = {"key", "aad", "iv", "msg", "ct", "tag"}
for testGroup in wycheproof_json["testGroups"]:
# Filter test groups based on implementation constraints
if testGroup["ivSize"] < 64 or testGroup["ivSize"] > 1024:
continue
for tv in testGroup["tests"]:
# Convert hex strings to bytes
for attr in convert_attr:
if attr in tv:
tv[attr] = bytes.fromhex(tv[attr])
testVectors.append(tv)
return testVectors
JavaScript 示例:
const fs = require('fs').promises;
async function loadWycheproofTestVectors(path) {
const tests = [];
try {
const fileContent = await fs.readFile(path);
const data = JSON.parse(fileContent.toString());
data.testGroups.forEach(testGroup => {
testGroup.tests.forEach(test => {
// Add shared test group properties to each test
test['pk'] = testGroup.publicKey.pk;
tests.push(test);
});
});
} catch (err) {
console.error('Error reading or parsing file:', err);
throw err;
}
return tests;
}
阶段 3:编写测试框架
创建能够处理有效和无效测试用例的测试函数。
Python/pytest 示例:
import pytest
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
tvs = load_wycheproof_test_vectors("wycheproof/testvectors_v1/aes_gcm_test.json")
@pytest.mark.parametrize("tv", tvs, ids=[str(tv['tcId']) for tv in tvs])
def test_encryption(tv):
try:
aesgcm = AESGCM(tv['key'])
ct = aesgcm.encrypt(tv['iv'], tv['msg'], tv['aad'])
except ValueError as e:
# Implementation raised error - verify test was expected to fail
assert tv['result'] != 'valid', tv['comment']
return
if tv['result'] == 'valid':
assert ct[:-16] == tv['ct'], f"Ciphertext mismatch: {tv['comment']}"
assert ct[-16:] == tv['tag'], f"Tag mismatch: {tv['comment']}"
elif tv['result'] == 'invalid' or tv['result'] == 'acceptable':
assert ct[:-16] != tv['ct'] or ct[-16:] != tv['tag']
@pytest.mark.parametrize("tv", tvs, ids=[str(tv['tcId']) for tv in tvs])
def test_decryption(tv):
try:
aesgcm = AESGCM(tv['key'])
decrypted_msg = aesgcm.decrypt(tv['iv'], tv['ct'] + tv['tag'], tv['aad'])
except ValueError:
assert tv['result'] != 'valid', tv['comment']
return
except InvalidTag:
assert tv['result'] != 'valid', tv['comment']
assert 'ModifiedTag' in tv['flags'], f"Expected 'ModifiedTag' flag: {tv['comment']}"
return
assert tv['result'] == 'valid', f"No invalid test case should pass: {tv['comment']}"
assert decrypted_msg == tv['msg'], f"Decryption mismatch: {tv['comment']}"
JavaScript/Mocha 示例:
const assert = require('assert');
function testFactory(tcId, tests) {
it(`[${tcId + 1}] ${tests[tcId].comment}`, function () {
const test = tests[tcId];
const ed25519 = new eddsa('ed25519');
const key = ed25519.keyFromPublic(toArray(test.pk, 'hex'));
let sig;
if (test.result === 'valid') {
sig = key.verify(test.msg, test.sig);
assert.equal(sig, true, `[${test.tcId}] ${test.comment}`);
} else if (test.result === 'invalid') {
try {
sig = key.verify(test.msg, test.sig);
} catch (err) {
// Point could not be decoded
sig = false;
}
assert.equal(sig, false, `[${test.tcId}] ${test.comment}`);
}
});
}
// Generate tests for all test vectors
for (var tcId = 0; tcId < tests.length; tcId++) {
testFactory(tcId, tests);
}
阶段 4:集成 CI
通过以下方式确保测试向量保持最新:
- 使用 Git 子模块:在运行测试前于 CI 中更新子模块
- 获取最新测试向量:在执行测试前运行获取脚本
- 定期更新:设置每周或每月更新,以发现新的测试向量
常见漏洞检测
Wycheproof 测试向量旨在捕捉特定的漏洞模式:
| 漏洞 | 说明 | 受影响的算法 | 示例 CVE |
|---|---|---|---|
| 签名可塑性 | 同一消息存在多个有效签名 | ECDSA、EdDSA | CVE-2024-42459 |
| 无效的 DER 编码 | 接受非规范的 DER 签名 | ECDSA | CVE-2024-42460、CVE-2024-42461 |
| 无效曲线攻击 | 使用无效曲线点进行 ECDH | ECDH | 多个库中常见 |
| 填充预言机 | 填充验证中的时序泄漏 | RSA-PKCS1 | 历史上的 OpenSSL 问题 |
| 标签伪造 | 接受被篡改的认证标签 | AES-GCM、ChaCha20-Poly1305 | 多种实现 |
签名可塑性:深入分析
问题: 未验证签名编码的实现可能会接受同一消息的多个有效签名。
示例(EdDSA): 在签名末尾添加或移除零:
Valid signature: ...6a5c51eb6f946b30d
Invalid signature: ...6a5c51eb6f946b30d0000 (should be rejected)
检测方法:
# Add signature length check
if len(sig) != 128: # EdDSA signatures must be exactly 64 bytes (128 hex chars)
return False
影响: 不同实现对相同签名的接受或拒绝结果不一致时,可能导致共识问题。
相关 Wycheproof 测试:
- EdDSA:tcId 37——“从签名中移除 1 个字节 0”
- ECDSA:tcId 06——“旧版:ASN 编码中的 r 缺少前导 0”
案例研究:Elliptic npm 包
本案例研究展示了 Wycheproof 如何在热门 elliptic npm 包中发现三个 CVE(超过 3000 个依赖方,每周下载数百万次)。
概述
elliptic 库是一个使用 JavaScript 编写的椭圆曲线密码学库,支持 ECDH、ECDSA 和 EdDSA。使用 Wycheproof 测试向量测试 6.5.6 版本时,发现了多个漏洞:
- CVE-2024-42459:EdDSA 签名可塑性(添加或移除零)
- CVE-2024-42460:ECDSA DER 编码——位位置无效
- CVE-2024-42461:ECDSA DER 编码——长度字段中存在前导零
方法论
- 识别受支持的曲线:用于 EdDSA 的 ed25519
- 查找测试向量:
testvectors_v1/ed25519_test.json - 解析测试向量:加载 JSON 并提取测试
- 编写测试框架:创建参数化测试
- 运行测试:找出失败项
- 分析根本原因:检查实现代码
- 提出修复方案:添加验证检查
主要发现
EdDSA 问题(CVE-2024-42459):
- 缺少签名长度验证
- 允许签名末尾带有零
- 修复:添加
if(sig.length !== 128) return false;
ECDSA 问题 1(CVE-2024-42460):
- 未检查 DER 编码的 r 和 s 值的首位是否为零
- 修复:添加
if ((data[p.place] & 128) !== 0) return false;
ECDSA 问题 2(CVE-2024-42461):
- DER 长度字段接受前导零
- 修复:添加
if(buf[p.place] === 0x00) return false;
影响
这三个漏洞都会导致同一条消息存在多个有效签名,从而造成不同实现之间的共识问题。
经验教训:
- Wycheproof 能捕获细微的编码错误
- 可复用的测试框架能带来长远收益
- 测试向量中的注释和标志有助于诊断问题
- 即使是热门库,也能从系统化的测试向量验证中获益
高级用法
提示与技巧
| 提示 | 帮助 |
|---|---|
| 按参数筛选测试组 | 聚焦于与实现约束相关的测试向量 |
| 使用测试向量标志 | 了解正在测试的具体漏洞模式 |
检查 notes 字段 | 获取标志含义的详细说明 |
| 同时测试加密/解密和签名/验证 | 确保双向正确性 |
| 在 CI 中运行测试 | 捕获回归问题,并从新增测试向量中获益 |
| 使用参数化测试 | 获取包含 tcId 和注释的清晰失败信息 |
常见错误
| 错误 | 错误原因 | 正确做法 |
|---|---|---|
| 只测试有效情况 | 会遗漏接受无效输入的漏洞 | 测试所有结果类型:有效、无效、可接受 |
| 忽略“可接受”结果 | 实现可能存在细微错误 | 将可接受结果视为值得调查的警告 |
| 不筛选测试组 | 在不受支持的参数上浪费时间 | 根据实现情况,按 keySize、ivSize 等筛选 |
| 不更新测试向量 | 错过新的漏洞模式 | 使用子模块或定期获取 |
| 只测试一个方向 | 加密/签名可能正常,但解密/验证会失败 | 测试两种操作 |
相关技能
工具技能
| 技能 | 在 Wycheproof 测试中的主要用途 |
|---|---|
| pytest | 用于参数化测试的 Python 测试框架 |
| mocha | 用于生成测试的 JavaScript 测试框架 |
| constant-time-testing | 结合 Wycheproof 进行时序侧信道测试 |
| cryptofuzz | 基于模糊测试的加密测试,可发现其他错误 |
技术技能
| 技能 | 适用时机 |
|---|---|
| coverage-analysis | 确保测试向量覆盖加密实现中的所有代码路径 |
| property-based-testing | 测试数学性质(例如,加密/解密往返) |
| fuzz-harness-writing | 为加密解析器创建测试框架(与 Wycheproof 互补) |
相关领域技能
| 技能 | 关系 |
|---|---|
| crypto-testing | Wycheproof 是全面加密测试方法中的关键工具 |
| fuzzing | 使用模糊测试发现 Wycheproof 未覆盖的错误(新的边界情况) |
技能依赖关系图
┌─────────────────────┐
│ wycheproof │
│ (this skill) │
└──────────┬──────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ pytest/mocha │ │ constant-time │ │ cryptofuzz │
│ (test framework)│ │ testing │ │ (fuzzing) │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
│ │ │
└───────────────────┼───────────────────┘
│
▼
┌──────────────────────────┐
│ Technique Skills │
│ coverage, harness, PBT │
└──────────────────────────┘
资源
官方仓库
官方仓库包含:
testvectors/和testvectors_v1/中的所有测试向量schemas/中的 JSON 架构- Java 和 JavaScript 参考实现
doc/中的文档
真实案例
pycryptodome 库将 Wycheproof 测试向量集成到其测试套件中,展示了 Python 加密实现的最佳实践。
社区资源
- C2SP 社区 - 维护 Wycheproof 的加密规范和标准社区
- Wycheproof 问题跟踪器 - 报告测试向量中的错误或建议新的构造
总结
Wycheproof 是一款重要工具,可用于根据已知攻击向量和边缘情况验证加密实现。将 Wycheproof 测试向量集成到测试工作流中,可以:
- 发现隐蔽的编码和验证错误
- 防止签名可塑性问题
- 确保不同实现之间的行为一致
- 受益于社区贡献的测试向量
- 防范已知的加密漏洞
编写可复用测试框架的投入会带来持续回报,因为 Wycheproof 仓库会不断添加新的测试向量,你也能持续进行验证。