Skip to main content

Automated timing side-channel scanner for NIST PQC standards ML-KEM and ML-DSA

Project description

PQC Side-Channel Scanner

An open-source automated timing side-channel scanner for NIST post-quantum cryptography standards. Detects constant-time violations in ML-KEM (FIPS 203) and ML-DSA (FIPS 204) implementations using TVLA statistical methodology.

Built by Collective Qubits.


Why This Exists

NIST finalized the post-quantum cryptography standards in August 2024. Banks, cloud providers, and government agencies are actively deploying ML-KEM and ML-DSA right now to protect against future quantum attacks. But timing side-channel vulnerabilities in cryptographic implementations can leak secret key material to an attacker — even when the algorithm itself is mathematically secure.

Existing tools like dudect require manual C integration, have no PQC-specific targets, and predate the NIST standards by years. No open-source automated scanner existed for FIPS 203 and FIPS 204 specifically.

This tool fills that gap.


What It Found

What It Found

Python layer — ML-KEM-768 (kyber-py):

Operation t-statistic Verdict
Decapsulation -3868 CRITICAL LEAK
Encapsulation -3377 CRITICAL LEAK
KeyGen 3.1 PASS

Python layer — ML-DSA-65 (dilithium-py) — bare metal Linux:

Operation t-statistic Verdict
Sign 0.4 PASS
Verify 15.4 LEAK
KeyGen 7.1 LEAK

C layer — liboqs ML-KEM-768 (CLOCK_MONOTONIC_RAW):

Operation Delta t-statistic p-value Verdict
Encapsulation 264ns 75.5 p=0.0 CRITICAL LEAK
Decapsulation 37ns -12.4 p=2.21e-35 LEAK

C-level noise floor (AES-128): 11.7ns. Encapsulation finding is 22x above noise floor.

How It Works

The scanner implements Test Vector Leakage Assessment (TVLA) — the same methodology used by Riscure and documented in ISO 17825. For each cryptographic operation it:

  1. Collects timing measurements for a fixed input (same value every time)
  2. Collects timing measurements for random inputs (different every time)
  3. Runs Welch's t-test on the two distributions
  4. If |t| > 4.5 the implementation is leaking — its timing depends on the input, which means an attacker can learn information about secret keys

The scanner has two measurement layers:

  • Python layer — targets kyber-py and dilithium-py, detects millisecond-level leaks
  • C layer — targets liboqs directly with CLOCK_MONOTONIC_RAW, detects nanosecond-level leaks

Installation

Requirements

  • Ubuntu 20.04+ or WSL2 (Ubuntu 24.04 tested)
  • Python 3.10+
  • gcc and clang
  • liboqs (instructions below)

1. Clone the repository

git clone https://github.com/Disha231102004/pqc-scanner
cd pqc-scanner

2. Set up Python environment

python3 -m venv venv
source venv/bin/activate
pip install kyber-py dilithium-py scipy numpy click rich

3. Install liboqs

sudo apt install -y cmake ninja-build libssl-dev clang

git clone --depth=1 https://github.com/open-quantum-safe/liboqs ~/liboqs
cmake -S ~/liboqs -B ~/liboqs/build -DBUILD_SHARED_LIBS=ON -GNinja
cmake --build ~/liboqs/build --parallel 4
sudo cmake --build ~/liboqs/build --target install
sudo ldconfig

4. Build the C harnesses

cd harness

# Production scanner (liboqs ML-KEM-768)
gcc -O2 -o ml_kem_harness ml_kem_harness.c -loqs -lssl -lcrypto

# Noise floor baseline (AES-128)
gcc -O2 -o baseline_harness baseline_harness.c -lssl -lcrypto

cd ..

Usage

Always activate the virtual environment first:

source venv/bin/activate

Scan ML-KEM and ML-DSA (Python targets)

python cli.py scan --algorithm both --traces 10000 --pin-cpu

Options:

  • --algorithmml-kem, ml-dsa, or both
  • --traces — number of measurements per operation (default 10000)
  • --pin-cpu — pin process to CPU core 0 for lower noise
  • --no-exit — do not exit with code 1 on findings (for scripting)
  • --open-html — open HTML report in browser automatically

Production scan (liboqs C library)

python cli.py liboqs --traces 100000

This is the primary scan. Uses CLOCK_MONOTONIC_RAW via C harness for nanosecond precision. 100,000 traces recommended for statistical confidence.

Algorithm comparison

python cli.py compare --traces 10000 --pin-cpu

Scans ML-KEM-768 and ML-DSA-65 side by side. Produces a comparison HTML report with bar chart.

Python noise floor characterization

python cli.py baseline --traces 10000 --pin-cpu

Runs SHA-256 as a control experiment. SHA-256 is provably constant-time so any signal here is measurement noise. Run this before reporting findings.

C-level noise floor characterization

python cli.py c-baseline --traces 100000

Runs AES-128 (AES-NI hardware instruction) through the same C harness. Reports the noise floor delta so you can assess the signal-to-noise ratio of liboqs findings.

Compiler flag sweep

python cli.py compiler-sweep --traces 50000 --runs 3

Builds the C harness with six compiler flag combinations and scans each. Looking for optimization-induced timing leaks — the same search space where KyberSlash was found. Each config is run --runs times and the median t-statistic reported for stability.


Output

Every scan produces two files:

  • *.json — structured findings for CI/CD integration
  • *.html — interactive report with timing distribution charts

JSON format

{
  "scanner": "pqc-scanner v0.3",
  "target": "liboqs ML-KEM-768",
  "summary": {
    "total": 1,
    "critical": 1,
    "high": 0,
    "medium": 0
  },
  "findings": [
    {
      "operation": "ML-KEM Encapsulation",
      "t_statistic": 75.5185,
      "p_value": 0.0,
      "delta_ns": 264.0,
      "severity": "CRITICAL",
      "verdict": "LEAK DETECTED"
    }
  ]
}

CI/CD integration

The scanner exits with code 1 when findings are detected, making it compatible with any CI pipeline:

# GitHub Actions example
- name: PQC Timing Scan
  run: |
    source venv/bin/activate
    python cli.py liboqs --traces 50000
  # Pipeline fails automatically if leaks are found

Use --no-exit to suppress the exit code when running in reporting-only mode.

SARIF output (GitHub Advanced Security)

Generate SARIF output for direct integration with GitHub Advanced Security, Microsoft Defender, and enterprise security tools:

python cli.py liboqs --traces 100000 --format sarif --output results

Upload to GitHub Security tab in Actions:

- name: PQC Timing Scan
  run: python cli.py liboqs --traces 50000 --no-exit --format sarif --output pqc

- name: Upload to GitHub Security
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: pqc.sarif

Findings will appear under the Security → Code scanning tab of your repository.


Methodology

TVLA (Test Vector Leakage Assessment)

Developed by Cryptography Research Inc. and documented in ISO 17825. The core principle: if an implementation is constant-time, its execution time should be statistically independent of the input. We test this by comparing timing distributions for fixed vs random inputs.

Welch's t-test

Used instead of Student's t-test because it does not assume equal variance between the two distributions. Threshold: |t| > 4.5 indicates a statistically significant timing difference, regardless of noise floor.

Measurement precision

  • Python layertime.perf_counter_ns(), noise floor ~100-130ns in WSL
  • C layerCLOCK_MONOTONIC_RAW via clock_gettime(), noise floor ~10-12ns in WSL

Noise reduction

  • 500-trace warmup — discards initial measurements to stabilize CPU cache and branch predictor
  • 5% outlier trimming — removes top 5% of measurements to eliminate OS interrupt spikes
  • CPU affinitysched_setaffinity pins the process to core 0 to reduce scheduler noise
  • Pre-generated test vectors — random inputs generated before the timed window so os.urandom() does not contaminate measurements

WSL limitations

WSL2 adds ~10-130ns of hypervisor noise depending on system load. This affects:

  • Decapsulation finding — 37ns delta is borderline, confirmed in 3 of 4 runs
  • Compiler sweep — results below 50ns delta are marked inconclusive

Production assessment of sub-50ns signals requires bare metal Linux.


Project Structure

pqc-scanner/
├── cli.py                      ← all commands
├── requirements.txt
├── scanner/
│   ├── timing.py               ← TVLA engine, warmup, outlier trim, CPU affinity
│   ├── static.py               ← static analysis for crypto context
│   └── report.py               ← JSON and HTML report generation
├── targets/
│   ├── ml_kem_target.py        ← ML-KEM-768 Python harness (kyber-py)
│   ├── ml_dsa_target.py        ← ML-DSA-65 Python harness (dilithium-py)
│   ├── baseline_target.py      ← SHA-256 control experiment
│   └── liboqs_target.py        ← liboqs subprocess interface
└── harness/
    ├── ml_kem_harness.c        ← C timing harness, CLOCK_MONOTONIC_RAW
    └── baseline_harness.c      ← AES-128 noise floor harness

Comparison With Existing Tools

Capability dudect Riscure This tool
ML-KEM / ML-DSA targets
liboqs integration
Python + C dual layer
Automated HTML reporting ✓ (hardware)
CI/CD exit codes
Noise floor validation ✓ (hardware)
No hardware required
Single command scan
Open source

Related Work

  • dudect — Reparaz et al. (2016). General-purpose constant-time testing library in C. No PQC targets.
  • KyberSlash — Cryspen et al. (2023). Manual timing analysis finding division-based leak in Kyber reference implementation.
  • TVLA — Cryptography Research Inc. (2011). Original leakage assessment methodology.
  • ISO 17825 — Testing methods for the mitigation of non-invasive attack classes against cryptographic modules.

Limitations

  • WSL2 hypervisor adds noise. Sub-50ns findings require bare metal Linux for confirmation.
  • kyber-py and dilithium-py are documented as non-constant-time reference implementations. Python layer findings are expected and not novel.
  • liboqs findings are statistically confirmed. Exploitability in a real network attack requires further research.
  • Compiler sweep is inconclusive below 50ns on WSL. clang -O3 finding above 50ns is reproducible.

License

MIT License. See LICENSE for details.


Collective Qubits

This project was developed as part of Collective Qubits — a quantum computing projects and education organization.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pqc_scanner-1.0.0.tar.gz (33.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pqc_scanner-1.0.0-py3-none-any.whl (33.7 kB view details)

Uploaded Python 3

File details

Details for the file pqc_scanner-1.0.0.tar.gz.

File metadata

  • Download URL: pqc_scanner-1.0.0.tar.gz
  • Upload date:
  • Size: 33.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pqc_scanner-1.0.0.tar.gz
Algorithm Hash digest
SHA256 7182aaad0c2780205c4e524fc661921af1bd92c41472f598c915805d943d5dec
MD5 e8610947bb90b38d9312eb9e24fa90de
BLAKE2b-256 8cd8ab31315b31169a68a4da67885666cebc42e7ab29b2b6601613497a1bfbaa

See more details on using hashes here.

File details

Details for the file pqc_scanner-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pqc_scanner-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 33.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for pqc_scanner-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0a2717f2e9fa4b21f171f8f0b4409a029671e5052676bd3726177b066fa6c1c6
MD5 94125e02c64aa14026707ddf40a52488
BLAKE2b-256 1b15d57d5ee7b35b93356505a5eb284a3ea61d7026fb190af18668d9fbe856f7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page