Skip to content

des: fuse S-boxes and P permutation at compile time - #603

Open
Ne9roni wants to merge 1 commit into
RustCrypto:masterfrom
Ne9roni:perf/des-sp-tables
Open

Ne9roni wants to merge 1 commit into
RustCrypto:masterfrom
Ne9roni:perf/des-sp-tables

Conversation

@Ne9roni

@Ne9roni Ne9roni commented Oct 23, 2026

Copy link
Copy Markdown

Precompute each S-box's P-permuted contribution at compile time, replacing runtime S-box assembly and the separate P permutation with eight u32 lookups. This removes work from every DES round, including all four 3DES variants. E expansion, key scheduling, key handling and public APIs are unchanged.

The tradeoff is 512 B → 2 KiB of table data (+1,536 B), without per-key storage or heap allocation growth. With the measured toolchain, the des archive's .text shrinks 2,617 → 2,302 B and .rodata grows 1,134 → 2,670 B: +1,221 B combined. These object-section sizes depend on compiler/target.

Security tradeoff for maintainer review: each secret-indexed subtable grows from 64 to 256 bytes. With aligned 64-byte cache lines this spans four lines instead of one, potentially exposing more index information. Both implementations are non-constant-time; the larger tables can worsen cache-based leakage. This PR does not measure attack exploitability.

Measured b99ebe6a2d394833a14a84b79aac60c0c0611577 → a466d47d8546fc28c18f7cb24a05f86a776a2f19 on an i7-13700KF / WSL2, pinned to logical CPU 2, nightly-2026-07-19, the same lockfile/harness, and unchanged Cargo bench settings (no CPU, optimization-level or LTO overrides). DES ran in preplanned A–B–B–A order; all results were retained.

Before → after, µs per 1,024 blocks; key initialization is ns per constructor. “Single” uses 1,024 scalar calls; “batch” uses one batch call. Values are means of two runs per side; libtest adapts sample counts.

Cipher Encrypt single Encrypt batch Decrypt single Decrypt batch Key init (ns)
des 150.17 → 93.28 149.68 → 93.00 148.35 → 93.41 149.82 → 92.83 84.92 → 85.22
tdes_ede2 471.80 → 294.19 470.01 → 294.89 463.66 → 294.66 470.13 → 295.39 172.06 → 173.37
tdes_ede3 465.98 → 297.16 464.58 → 296.97 467.78 → 298.21 465.38 → 298.83 257.34 → 260.47
tdes_eee2 463.57 → 291.38 466.59 → 292.73 462.78 → 295.35 464.45 → 292.43 170.49 → 170.53
tdes_eee3 464.01 → 292.63 470.07 → 293.51 462.95 → 293.77 461.84 → 294.15 259.82 → 256.55

All 20 encryption/decryption cases take 35.8–38.0% less time (1.56–1.61× throughput). Initialization changes range from −1.26% to +1.21%; the key-schedule machine code is identical. These small changes remain visible in the raw results.

The full workspace benchmark suite completed 137/137 cases (324 measurements). The other 112 cases used byte-identical executables and varied −7.66% to +7.56%, with Twofish batch encryption the largest increase. This illustrates the environmental/harness variation; WSL2 guest affinity does not control host scheduling. No concurrent build/benchmark jobs were detected.

Validation:

  • Existing NESSIE DES/EDE2/EDE3 vectors and weak-key tests; added OpenSSL-cross-checked EEE2/EEE3 known answers using the existing scalar/batch encrypt/decrypt test helper.
  • Rust 1.85.0 (MSRV) and 1.95.0: default/no-default/all-features tests, all-features checks, and no-default release builds for thumbv7em-none-eabi and wasm32-unknown-unknown.
  • Workspace formatting and Clippy checks passed. A local differential check matched the baseline for all five ciphers over 1,024 keys × 17 blocks each, both directions, including parity-bit handling.

Raw results, all per-case comparisons, fixture provenance and reproduction instructions. The benchmark additions cover initialization and all DES/3DES variants; one-off verification scripts are not part of this PR.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant