mcpField
W
wycheproof

wycheproof

github.com/trailofbits测试与质量7.9k5.2k 次安装今天更新
代码仓库

关于 wycheproof

Wycheproof 是一组用于验证密码实现正确性并测试已知攻击的测试向量,面向密码库开发者和审计人员。测试向量按算法与参数分组,并标注预期结果及潜在风险。

主要功能

  1. 01覆盖多种密码算法与曲线
  2. 02提供有效、可接受和无效用例
  3. 03按密钥尺寸等参数分组
  4. 04JSON 格式测试向量
  5. 05支持测试框架与 CI 集成

适用场景

  1. 01验证 AES-GCM 加解密实现
  2. 02检查 ECDSA 签名编码处理
  3. 03测试 ECDH 对无效曲线点的处理
  4. 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.json44 个测试组中共有 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-GCMkey、iv、aad、msg、ct、tag
ECDH secp256k1public、private、shared
ECDSAmsg、sig、result
EdDSAmsg、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

通过以下方式确保测试向量保持最新:

  1. 使用 Git 子模块:在运行测试前于 CI 中更新子模块
  2. 获取最新测试向量:在执行测试前运行获取脚本
  3. 定期更新:设置每周或每月更新,以发现新的测试向量

常见漏洞检测

Wycheproof 测试向量旨在捕捉特定的漏洞模式:

漏洞说明受影响的算法示例 CVE
签名可塑性同一消息存在多个有效签名ECDSA、EdDSACVE-2024-42459
无效的 DER 编码接受非规范的 DER 签名ECDSACVE-2024-42460、CVE-2024-42461
无效曲线攻击使用无效曲线点进行 ECDHECDH多个库中常见
填充预言机填充验证中的时序泄漏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 编码——长度字段中存在前导零
方法论
  1. 识别受支持的曲线:用于 EdDSA 的 ed25519
  2. 查找测试向量:testvectors_v1/ed25519_test.json
  3. 解析测试向量:加载 JSON 并提取测试
  4. 编写测试框架:创建参数化测试
  5. 运行测试:找出失败项
  6. 分析根本原因:检查实现代码
  7. 提出修复方案:添加验证检查
主要发现

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-testingWycheproof 是全面加密测试方法中的关键工具
fuzzing使用模糊测试发现 Wycheproof 未覆盖的错误(新的边界情况)

技能依赖关系图

                    ┌─────────────────────┐
                    │    wycheproof       │
                    │   (this skill)      │
                    └──────────┬──────────┘
                               │
           ┌───────────────────┼───────────────────┐
           │                   │                   │
           ▼                   ▼                   ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│  pytest/mocha   │ │ constant-time   │ │   cryptofuzz    │
│ (test framework)│ │   testing       │ │   (fuzzing)     │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘
         │                   │                   │
         └───────────────────┼───────────────────┘
                             │
                             ▼
              ┌──────────────────────────┐
              │   Technique Skills       │
              │ coverage, harness, PBT   │
              └──────────────────────────┘

资源

官方仓库

Wycheproof GitHub 仓库

官方仓库包含:

  • testvectors/ 和 testvectors_v1/ 中的所有测试向量
  • schemas/ 中的 JSON 架构
  • Java 和 JavaScript 参考实现
  • doc/ 中的文档
真实案例

pycryptodome

pycryptodome 库将 Wycheproof 测试向量集成到其测试套件中,展示了 Python 加密实现的最佳实践。

社区资源
  • C2SP 社区 - 维护 Wycheproof 的加密规范和标准社区
  • Wycheproof 问题跟踪器 - 报告测试向量中的错误或建议新的构造

总结

Wycheproof 是一款重要工具,可用于根据已知攻击向量和边缘情况验证加密实现。将 Wycheproof 测试向量集成到测试工作流中,可以:

  1. 发现隐蔽的编码和验证错误
  2. 防止签名可塑性问题
  3. 确保不同实现之间的行为一致
  4. 受益于社区贡献的测试向量
  5. 防范已知的加密漏洞

编写可复用测试框架的投入会带来持续回报,因为 Wycheproof 仓库会不断添加新的测试向量,你也能持续进行验证。

评论

支持 Markdown