Skip to content

+ VeraCrypt/TrueCrypt containers: open, mount and create - #808

Merged
Hawkynt merged 9 commits into
mainfrom
feat/veracrypt-volumes
Nov 10, 2026
Merged

Hawkynt merged 9 commits into
mainfrom
feat/veracrypt-volumes

Conversation

@Hawkynt

@Hawkynt Hawkynt commented Nov 10, 2026

Copy link
Copy Markdown
Owner

VeraCrypt and TrueCrypt containers can now be opened, mounted and created — back to TrueCrypt 1.0.

Changes

  • Block ciphers in Core: Serpent/Twofish decrypt, Camellia (RFC 3713), Kuznyechik (RFC 7801), CAST-128 (RFC 2144); Serpent S-boxes as Osvik's circuits (Botan, BSD-2) — 2.7 → 18 MiB/s.
  • FileFormat.VeraCrypt: header decryption by trial over every PRF (SHA-512/256, Whirlpool, BLAKE2s, Streebog, RIPEMD-160, SHA-1, Argon2id) with PIM and keyfiles; XTS for AES/Serpent/Twofish/Camellia/Kuznyechik and every cascade; TrueCrypt 4.x LRW (GF(2^128) and GF(2^64)) and CBC / outer-CBC / inner-CBC with Blowfish, CAST5, 3DES; backup and hidden headers.
  • VeraCryptFormatDescriptor (.hc/.tc): list/extract, mount read-only/read-write (Pim/Prf/Cipher/Hidden/Keyfiles), create VeraCrypt or TrueCrypt 7.1 containers, optionally sparse.
  • Sourcing: cryptsetup lib/tcrypt (LGPL) converted; legacy sector modes specified from TrueCrypt 4.3a, compiled standalone only as an oracle (Compression.Tests/VeraCrypt/ReferenceVectors). Spec: docs/VERACRYPT-ON-DISK.md. Notices packed.
  • CI step "VeraCrypt oracle": VeraCrypt 1.26.24 console (checksummed) and cryptsetup's TCRYPT test images (pinned).

Verification

  • All 76 cryptsetup TCRYPT images open; every one whose data survived decrypts to its FAT serial.
  • 24 TrueCrypt 4.3a legacy vectors match both ways.
  • VeraCrypt creates containers for every cipher/cascade/PRF and we decrypt what it wrote; VeraCrypt mounts ours (sparse too); cryptsetup tcryptOpen maps our VeraCrypt and TrueCrypt containers. Locally: AES/Kuznyechik cases (kernel modules missing on the dev host); all of them run in CI.

Closes #801

@Hawkynt
Hawkynt force-pushed the feat/veracrypt-volumes branch from c2dd5ac to 3ff4d58 Compare November 10, 2026 01:51
Serpent and Twofish only encrypted, which was enough for the counter
and feedback modes that used them so far. XTS disk volumes need the
block decryption too.

Serpent.Decrypt runs the 32 rounds backwards: the final key, then per
round the inverse linear transformation and the inverse S-box (the
inverse permutation of the paper's S-box table) before the round key.
Twofish.Decrypt reverses the Feistel network with the round subkeys in
reverse order, swapping the one-bit rotations, and the whitening words
exchanged. Both keep the existing byte order (little-endian words,
NESSIE/LibTomCrypt order). Both now expose a public BlockSize const.

Sourcing: rung 3, the original papers ("Serpent: A Proposal for the
Advanced Encryption Standard", section 2, and "Twofish: A 128-Bit Block
Cipher", section 4.1), the same documents the encryption was written
from. No licence-compatible source needed taking for an inverse this
small.

Verified by decrypting every existing known-answer ciphertext (7
Serpent vectors from the LibTomCrypt self-test, 3 Twofish vectors from
the paper) back to its plaintext, and by random encrypt/decrypt round
trips for all Serpent key lengths 1-32 and Twofish 16/24/32.
Block ciphers VeraCrypt and TrueCrypt volumes may use besides AES,
Serpent and Twofish. Each is a sealed class taking the key in its
constructor, with in-place Encrypt/Decrypt and a public BlockSize.

Camellia (RFC 3713): 128/192/256-bit keys, 16-byte blocks, big-endian
as in the RFC. S-box lookups and the P-function are merged into eight
64-bit tables built once; decryption uses the subkey swaps of section
2.3.3.

Kuznyechik (GOST R 34.12-2015, RFC 7801): 256-bit key, 16-byte blocks,
byte 0 being the leftmost byte of the RFC's hex strings (a_15). L(S(x))
is merged into sixteen 128-bit lookup tables; decryption uses an L^-1
table and Pi^-1. The RFC's printed formula for l lists a_15 twice where
a_14 is meant; the coefficients are confirmed by its R and L examples.

CAST-128/CAST5 (RFC 2144): 5- to 16-byte keys zero-padded to 128 bits,
12 rounds for keys up to 80 bits, 8-byte big-endian blocks.

Sourcing: rung 3, the RFCs. Permissively licensed implementations
exist, but the RFCs are short, normative and complete, and give the
test vectors, so writing from them was the cleaner route. All tables
(Camellia SBOX1 and Sigma1-6, Kuznyechik Pi, Pi^-1 and the l
coefficients, CAST-128 S1-S8) were extracted mechanically from the RFC
text files rather than retyped.

Verified against RFC 3713 Appendix A (all three key sizes), RFC 7801
sections 5.5/5.6 and RFC 2144 Appendix B.1 (128/80/40-bit keys) in both
directions, the RFC 2144 B.2 million-iteration maintenance test, and
random round trips for every key size.
Symptom: Serpent-XTS disk decryption ran at 2.7 MiB/s, so mounting a
Serpent VeraCrypt volume crawled.

Root cause: every S-box application looped over the 32 bit columns, looking
each 4-bit nibble up in the paper's table - 32 table lookups and bit shuffles
per S-box, 32 S-boxes per block.

Fix: apply each S-box (and its inverse) to all 32 columns at once with Dag
Arne Osvik's boolean circuits, converted statement for statement from
Botan's serpent_sbox.h (Simplified BSD, rung 1; notice beside the code).
The table form stays as the specification for the key schedule, and a test
checks every circuit and its inverse against it. 2.7 -> 18 MiB/s for
Serpent-XTS; the NESSIE/LibTomCrypt known-answer vectors still pass.
…eCrypt 1.0

VeraCrypt and TrueCrypt containers were not supported.

- Header: salt + 448 encrypted bytes, opened when magic and both CRCs agree.
  Derivation and cipher chain are found by trial like the tools do: every
  VeraCrypt PRF (SHA-512/256, Whirlpool, BLAKE2s-256, Streebog-512,
  RIPEMD-160, Argon2id) with PIM, the TrueCrypt PRFs back to SHA-1, and
  keyfiles. Primary, backup, hidden (64 KiB / TrueCrypt<6 end-1536) and
  backup hidden headers.
- Sectors: XTS for AES, Serpent, Twofish, Camellia, Kuznyechik and every
  cascade; TrueCrypt 4.x LRW over GF(2^128) and GF(2^64) (Blowfish, CAST5,
  3DES); CBC with seeded IVs and CRC whitening, outer-CBC and inner-CBC
  cascades. Data offsets/IV bases follow cryptsetup; pre-5.0 headers with
  volume size 0 run to the end of the container.
- VeraCryptFormatDescriptor ("VeraCrypt", .hc/.tc): list/extract the
  decrypted volume and metadata.ini, mount the filesystem inside read-only
  or read-write (Pim/Prf/Cipher/Hidden/Keyfiles parameters), create
  VeraCrypt or TrueCrypt 7.1 containers with any XTS chain and PRF, PIM,
  keyfiles, random-filled or sparse unused space.
- XTS/LRW XOR all tweaks of a sector around one bulk ECB pass, and the
  framework ciphers reuse cached ECB transforms (AES-XTS 13 -> 290 MiB/s).

Sourcing: rung 1 for the header side - cryptsetup lib/tcrypt (LGPL-2.1+)
converted, notice beside the code. Rung 2 for the legacy sector modes, which
cryptsetup leaves to the kernel: TrueCrypt 4.3a's Crypto.c (TrueCrypt
License) was read for docs/VERACRYPT-ON-DISK.md and compiled standalone as
an oracle; its output is committed under ReferenceVectors with provenance.

Verified:
- all 76 cryptsetup TCRYPT test images (TrueCrypt 1-7.1, VeraCrypt, hidden,
  every PRF incl. Argon2id/BLAKE2s/Streebog) open, and every one whose data
  survived decrypts to its FAT serial DEAD-BABE/CAFE-BABE;
- the 24 TrueCrypt 4.3a legacy vectors (CBC, outer/inner CBC, LRW-128/64)
  match both ways - the Blowfish and CBC-cascade images carry no data;
- veracrypt 1.26 creates containers for every cipher/cascade/PRF and we
  decrypt what it wrote; it mounts ours (sparse too); cryptsetup tcryptOpen
  maps our VeraCrypt and TrueCrypt containers to our plaintext. Kernel-cipher
  cases run on CI (new "VeraCrypt oracle" step, pinned + checksummed).

Closes #801
Attribution is a licence term: the converted cryptsetup TCRYPT code and
Botan's Serpent circuits carry their notices beside the code, but the
NuGet packages did not include them. Pack them under notices/ like the
existing Sqx, Chfs, Lfs and PartImage notices.
New public types: Camellia, Kuznyechik, Cast5 (Core) and the TrueCrypt/
VeraCrypt reader, writer and descriptor (FileSystems). Regenerated with
RepositoryTemplate's package-readme at v1; the Svr4Bfs namespace block is
taken from main because the local run cannot load Svr4BfsWriter.
ProvenanceClaimsAreRecordedTests failed: Serpent.cs says its S-box circuits
were converted from Botan, but docs/LICENCE-PROVENANCE.md had no row for it.
Add class-A rows for the Botan circuits (BSD-2) and the cryptsetup TCRYPT
conversion behind FileFormat.VeraCrypt (LGPL-2.1+), with their revisions and
notices.
The VeraCrypt oracle failed its two Argon2 cases on CI: 1.26.24 has no
Argon2 key derivation (VeraCrypt added it in 1.26.27), so it could neither
create an Argon2 container nor mount ours. 1.26.29 is the release the
oracle was run against locally; its .deb checksum is pinned.
Main gained the BitLocker types; recount and relist with the VeraCrypt ones
(Svr4Bfs block kept from main, which the local run cannot load).
@Hawkynt
Hawkynt force-pushed the feat/veracrypt-volumes branch from 3ff4d58 to 120248c Compare November 10, 2026 03:03
@Hawkynt
Hawkynt merged commit fada50f into main Nov 10, 2026
5 checks passed
@Hawkynt
Hawkynt deleted the feat/veracrypt-volumes branch November 10, 2026 04:33
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.

VeraCrypt/TrueCrypt volumes: open, mount and create

1 participant