This document describes all security-relevant changes made to the TrueCrypt 7.1a codebase as part of the macOS 12+ universal (arm64 + x86_64) port. The goal is to fix known weaknesses identified by the original OCAP audits (2013-2015) and bring the implementation to modern standards — without breaking compatibility with existing TrueCrypt volumes.
No backdoors were found in the original TrueCrypt 7.1a source code during our comprehensive audit (RNG, PBKDF2, encryption, volume headers, memory handling, and suspicious pattern analysis).
File: Common/Tcdefs.h
Problem: The original burn() macro used a volatile pointer trick that compilers
could optimize away, leaving sensitive data (keys, passwords) in memory.
Fix: Replaced with memset_s() (C11 Annex K), which is guaranteed not to be
optimized away. Available on macOS 10.9+.
File: Volume/VolumePassword.cpp
Problem: The stack buffer holding the user's password was not zeroed after use.
Fix: Added burn(passwordBuf, sizeof(passwordBuf)) after password processing.
File: Core/RandomNumberGenerator.cpp (3 locations)
Problem: The entropy pool used += (addition) for mixing, which can accumulate
bias over time and is not entropy-neutral.
Fix: Changed to ^= (XOR), which is the standard cryptographic mixing operation
and preserves entropy.
File: Core/RandomNumberGenerator.cpp
Problem: After mixing, the pool was inverted (~Pool[i]), a non-standard operation
with no cryptographic justification that reduces effective entropy.
Fix: Removed the inversion step entirely.
File: Core/RandomNumberGenerator.cpp
Problem: PoolHash->ProcessData() was called without prior Init(), potentially
processing data with stale internal state.
Fix: Added PoolHash->Init() before each ProcessData() call.
File: Core/RandomNumberGenerator.cpp
Problem: The RNG self-test ran before the pool was seeded with system entropy,
testing uninitialized state.
Fix: Reordered Start(): set hash algorithm, seed pool, then run self-test.
File: Platform/StringConverter.cpp
Problem: StringConverter::Erase() overwrote strings with spaces instead of using
burn(), and the space-writing could be optimized away.
Fix: Now uses burn() (→ memset_s) for both string and wstring variants.
Files: Platform/Memory.h, Platform/Memory.cpp, Platform/Buffer.h
Problem: BufferPtr::IsDataEqual() used memcmp(), which can leak information
about key material through timing side channels.
Fix: Added Memory::ConstantTimeCompare() using volatile pointers and OR-accumulation.
IsDataEqual() now uses this function.
File: Common/Pkcs5.c (4 locations: SHA-512, SHA-1, RIPEMD-160, Whirlpool)
Problem: The PBKDF2 block counter was written as a single byte (counter[3] = b),
which is incorrect per RFC 2898 (must be 4-byte big-endian). For derived keys longer
than one hash output block, this would produce incorrect key material.
Fix: Proper 4-byte big-endian encoding of the block counter.
File: Platform/Buffer.cpp
Problem: SecureBuffer contents (encryption keys, passwords) could be swapped to
disk by the OS, where they persist unencrypted.
Fix: SecureBuffer::Allocate() now calls mlock() to pin pages in RAM.
SecureBuffer::Free() calls munlock() after Erase().
Files: Volume/EncryptionModeXTS.cpp, Volume/EncryptionModeXTS.h
Problem: XTS mode (IEEE 1619 / NIST SP 800-38E) requires that the primary
encryption key and the secondary (tweak) key are not identical. TrueCrypt did not
verify this.
Fix: SetKey() now calls ValidateXtsKeys() which compares each cipher's primary
key against its corresponding secondary key using constant-time comparison. Throws
ParameterIncorrect if keys are equal.
Files removed: Volume/VolumePasswordCache.h, Volume/VolumePasswordCache.cpp
Files changed: Core/CoreBase.h, Core/Unix/CoreUnix.h, Core/Unix/CoreUnix.cpp,
Core/Unix/CoreServiceProxy.h, Core/MountOptions.h, Core/MountOptions.cpp,
Volume/Volume.make
Problem: The original TrueCrypt password cache stored plaintext passwords in the
heap after successful mounts, keeping sensitive key material in memory longer than
necessary. Even with the timeout mechanism added in an earlier hardening pass, cached
passwords represent unnecessary attack surface — a memory dump at any point during
the timeout window reveals all recently-used passwords.
Fix: The password cache has been completely removed from all layers:
VolumePasswordCacheclass deleted (header + implementation)CachePasswordfield removed fromMountOptions(serialization, clone, init)- Cache lookup branch removed from
CoreServiceProxy::MountVolume() IsPasswordCacheEmpty()andWipePasswordCache()removed fromCoreBaseinterface- All UI layers (SwiftUI bridge, CLI) cleaned of cache references Users must enter their password for each mount operation. This is the most secure approach and eliminates an entire class of password exposure risk.
Files: Volume/Pkcs5Kdf.h, Volume/Pkcs5Kdf.cpp
Problem: Original iteration counts (1000–2000) were chosen in 2004 and offer
negligible brute-force resistance on modern hardware.
Fix: New volumes use hardened iteration counts matching VeraCrypt levels:
| Hash Algorithm | TrueCrypt 7.1a | This Port | Factor |
|---|---|---|---|
| SHA-512 | 1,000 | 500,000 | 500x |
| Whirlpool | 1,000 | 500,000 | 500x |
| RIPEMD-160 | 2,000 | 655,331 | 328x |
| SHA-1 | 2,000 | 500,000 | 250x |
Backward compatibility: Legacy KDF classes with original iteration counts are
preserved and tried first when opening volumes. This ensures existing TrueCrypt 7.1a
volumes open without delay. New volumes are always created with high iterations.
GetAlgorithm() (used for volume creation) returns only modern KDFs.
- Existing TrueCrypt 7.1a volumes: Fully supported. Legacy KDFs are tried first during volume open, so there is no performance penalty.
- New volumes created by this port: Use high iteration counts. They cannot be opened by the original TrueCrypt 7.1a (which lacks the modern KDFs) but could be opened by any implementation that supports the same iteration counts.
- VeraCrypt volumes: Not directly compatible (different header format and magic bytes), but the iteration counts are equivalent.
File: Crypto/Aes_hw_cpu_arm.c
Problem: The software AES implementation uses T-tables (lookup tables) that are
vulnerable to cache-timing side-channel attacks. Additionally, software AES is ~10x
slower than hardware AES on Apple Silicon.
Fix: New drop-in replacement using ARMv8 Cryptographic Extensions (NEON intrinsics:
vaeseq_u8, vaesdq_u8, vaesmcq_u8, vaesimcq_u8). Provides constant-time AES
operations immune to cache-timing attacks. Automatically detected via __ARM_FEATURE_CRYPTO
at compile time. Works with NOASM=1 as these are C intrinsics, not assembly.
Provides aes_hw_cpu_encrypt/decrypt and _32_blocks variants matching the x86 AES-NI
interface for seamless integration.
File: Core/RandomNumberGenerator.cpp
Problem: Entropy was gathered exclusively from /dev/urandom via file descriptor
I/O — functional but suboptimal. The additional /dev/random non-blocking read on
macOS is pointless (both are identical on modern macOS, backed by Fortuna CSPRNG).
Fix: On macOS, the primary entropy source is now getentropy() (available since
macOS 10.12). This is a direct system call to the kernel CSPRNG, requires no file
descriptors, and cannot be intercepted by filesystem-level attacks.
Falls back to /dev/urandom if getentropy() fails (should never happen on macOS 10.12+).
The redundant /dev/random non-blocking read is removed on macOS.
Files: Originally Main/GraphicUserInterface.cpp, Main/TextUserInterface.cpp (now
removed with wxWidgets). The upgrade logic lives in Core/CoreBase.cpp (ChangePassword
with wipePassCount parameter). Each UI layer (CLI, SwiftUI) can implement its own
upgrade prompt using the VolumeOperationCallback interface.
Problem: Existing TrueCrypt volumes use low PBKDF2 iterations (1000-2000) that offer
minimal brute-force resistance. Users must manually re-encrypt headers to benefit from
the improved iteration counts.
Fix: After mounting a volume with legacy iterations (< 10,000), the UI layer offers to
upgrade the volume header to modern iterations. The dialog shows the exact current and
target iteration counts.
The upgrade preserves the user's password, keyfiles, and hash algorithm choice. Only the iteration count changes. The file hash will change (header bytes are different), but the on-disk encryption format and data content are unchanged.
Files: Core/CoreBase.h, Core/CoreBase.cpp
Problem: ChangePassword() performs PRAND_DISK_WIPE_PASSES (256 in release builds)
wipe passes per header, re-deriving the key each time. This is a security feature designed
to make the old key material forensically unrecoverable when changing passwords. However,
a KDF upgrade keeps the same master key — 256 wipe passes with expensive PBKDF2 iterations
(500,000+) would take 4+ minutes and freeze the GUI.
Fix: Added an optional wipePassCount parameter to ChangePassword(). The KDF upgrade
calls with wipePassCount=1 since the master key is unchanged and there is no old key
material to securely erase. The normal "Change Password" dialog continues to use the full
256 wipe passes.
Status: This mitigation was part of the wxWidgets-based GUI and has been superseded
by the complete removal of wxWidgets in Wave 6. The SwiftUI-based UI uses
NSSecureTextField (via SwiftUI SecureField) which is managed by the system's Cocoa
layer, and the ObjC++ bridge handles password data through mlock()-pinned buffers in
the TCCocoaCallback class.
Original problem (now eliminated): wxWidgets wxTextCtrl stored passwords as
wxString in the regular heap with copy-on-write semantics, undo buffers, and
platform string conversions creating uncontrollable copies.
Files removed: Entire Main/ directory (86 files), all wx build infrastructure
Problem: wxWidgets was a significant attack surface and source of security concerns:
- Password handling through
wxStringwith uncontrollable heap copies (Wave 5 mitigation) - Destruction-order bugs causing memory corruption (
DestroyChildren()before C++ dtors) - Signal handler deadlocks (
wxMessageBoxfrom signal context →NSAlert runModal()hang) - Large, complex dependency (~2 MB of third-party code) with its own vulnerability history
- No access to modern macOS security APIs (Secure Enclave, Keychain integration, etc.)
Fix: wxWidgets completely removed. Replaced with:
libTrueCryptCore.a— UI-independent static library containing all crypto, volume, and platform logic. Zero wxWidgets symbols.VolumeOperationCallback— Abstract C++ interface for user interaction, implemented separately by each UI layer.- Standalone CLI (
basalt-cli) — getopt_long + POSIX terminal I/O (termios). Password input usestcsetattr()to disable echo. No toolkit dependency. - SwiftUI macOS app (
Basalt.app) — Native macOS UI via ObjC++ bridge (TCCoreBridge.mm). Passwords handled throughNSSecureTextField(system-managed).
Files: Basalt/Bridge/TCCoreBridge.mm, Basalt/Bridge/TCCocoaCallback.mm
Design principles:
- C++ exceptions are caught at the bridge boundary and converted to
NSError. No C++ exceptions propagate into Swift/ObjC runtime. - Password strings from
NSSecureTextFieldare immediately converted toVolumePassword(which usesmlock()-pinnedSecureBuffer) and theNSStringintermediate is short-lived and managed by ARC. - All UI callbacks dispatch to the main thread via
dispatch_sync(dispatch_get_main_queue())— no cross-thread UI manipulation. arc4random_buf()used for entropy in the CocoaCallback (system CSPRNG, no file I/O).
Files: CLI/CLICallback.h, CLI/CLICallback.cpp
Design: Password input uses POSIX termios to disable terminal echo (ECHO flag).
Echo is restored in all code paths (including exceptions) via RAII-style cleanup.
Passwords are read directly into VolumePassword objects backed by mlock()-pinned
SecureBuffer. No intermediate wxString or heap-allocated string copies.
Files: Basalt/App/VolumeManager.swift, Basalt/App/PreferencesManager.swift
Problem: Mounted volumes remain accessible indefinitely, even when the user has
stopped working. An unattended machine with mounted encrypted volumes defeats the
purpose of encryption.
Fix: Per-volume I/O activity tracking using the existing TotalDataRead and
TotalDataWritten counters from the FUSE driver. Every 2 seconds, the refresh timer
compares current I/O totals with previously recorded values. If a volume has had no
read or write activity for the configured timeout (5/10/30/60/120 minutes), it is
automatically dismounted. Each volume is tracked independently — only idle volumes
are affected.
Files: Basalt/App/BasaltApp.swift (AppDelegate)
Problem: Volumes remain mounted during security-sensitive system transitions
(screen lock, sleep, application exit, logout) where the user is not actively present.
Fix: Four automatic dismount triggers:
- Screen saver:
DistributedNotificationCenterobservescom.apple.screensaver.didstart - System sleep:
NSWorkspace.willSleepNotification - Logout/shutdown/restart:
NSWorkspace.willPowerOffNotification(default: on) - Application quit:
applicationShouldTerminatewith.terminateLaterfor async dismount before exit Each trigger is independently configurable. Force dismount is enabled by default and applies to all dismount operations (manual and automatic), ensuring volumes can always be closed even when processes hold open file handles.
Files: Basalt/App/PreferencesManager.swift, Basalt/App/MainWindow.swift
Problem: Non-forced dismount fails when any process holds an open file handle to
the mounted volume. This creates a security issue: the user intends to close the volume
but cannot because of a background process (Spotlight indexing, antivirus scan, shell
cd into the mount point, etc.). The volume remains accessible against the user's intent.
Fix: forceDismount defaults to true. All dismount operations — toolbar buttons,
context menu, keyboard shortcut (Cmd+Shift+D), and all auto-dismount triggers — respect
this preference. The setting can be disabled in Preferences for users who prefer to be
notified about open files. The previous separate "Dismount (Force)" context menu item
has been removed in favor of the unified preference.
Files: CLI/main.cpp
Problem: The KDF upgrade dialog (Wave 4, #16) was only available in the SwiftUI GUI.
CLI users mounting legacy volumes had no way to upgrade their volume headers to modern
iteration counts.
Fix: After a successful mount in interactive mode, VolumeOperations::UpgradeKdf()
is called, which checks if the mounted volume uses legacy iterations (< 10,000) and
prompts the user to upgrade. Skipped in --non-interactive mode to allow scripted usage.
Files: Common/Tcdefs.h
Problem: The FAST_ERASE64 macro used volatile uint64* pointer writes to wipe
XTS whitening values and LRW key material. The C/C++ standards do not guarantee that
volatile stores to stack-allocated memory survive optimization when the buffer is about
to go out of scope. This affected all XTS encrypt/decrypt paths (EncryptionModeXTS.cpp,
Xts.c, Crypto.c) — a total of 16 wipe sites handling key-derived whitening material.
Fix: FAST_ERASE64 macro now delegates to burn(), which uses memset_s() (C11
Annex K). This is the only standards-compliant guarantee against compiler optimization
of wipe operations.
Files: Core/Unix/CoreUnix.cpp
Problem: VeraCrypt 1.26.18 fixed CVE-2025-23021: volumes could be mounted on system
directories (/usr/bin, /etc, /System, etc.), enabling arbitrary code execution by
replacing system binaries with volume contents.
Fix: MountVolume() now validates the mount point against a list of protected system
paths before proceeding. Mounting on /, /usr, /bin, /sbin, /etc, /var,
/System, /Library, /Applications, /dev, /private, and /opt is rejected.
Files: Core/Unix/CoreUnix.cpp, Core/Unix/MacOSX/CoreMacOSX.cpp
Problem: VeraCrypt 1.26.15 fixed CVE-2024-54187: Process::Execute() was called with
bare binary names (mount, umount, hdiutil), allowing PATH hijacking. An attacker
placing a malicious binary earlier in PATH could execute arbitrary code with elevated
privileges.
Fix: All Process::Execute() calls on macOS now use absolute paths:
/sbin/mount, /sbin/umount, /usr/bin/hdiutil, /usr/bin/open. The FUSE exec functor
is unaffected as it calls fuse_main() directly, not via PATH lookup.
Files: Basalt/App/MainWindow.swift, Basalt/App/MountSheet.swift,
Basalt/App/ChangePasswordSheet.swift, Basalt/Bridge/TCCocoaCallback.mm,
Basalt/Bridge/TCCoreBridge.mm
Problem: Screen recording malware can capture password entry and volume metadata
(container paths, mount points). macOS provides NSWindow.sharingType = .none to prevent
screenshots, screen recording, and AirPlay mirroring of specific windows.
Fix: The entire application window (main window + all sheets) sets sharingType = .none
on its hosting NSWindow. This prevents screenshots from capturing any TrueCrypt content —
volume paths, mount points, encryption details, and password entry are all invisible to screen
capture, screen sharing, and AirPlay mirroring. NSAlert password dialogs also set this
property directly. Implemented via NSViewRepresentable helper with screenCaptureProtection()
SwiftUI modifier.
Files: src/Fuse/FuseService.cpp
Problem: Without explicit restrictions, a mounted TrueCrypt volume could contain
setuid/setgid binaries or device nodes. An attacker who can place files on a volume
(e.g., a shared volume, a volume from an untrusted source) could use setuid binaries
for privilege escalation or device nodes for unauthorized hardware access.
Fix: All FUSE mounts now include nosuid,nodev options. nosuid prevents execution
of setuid/setgid programs from the volume, nodev prevents creation or use of device
special files. These are standard mount hardening flags used by default in most Linux
distributions for removable media.
Files: Basalt/Bridge/TCCoreBridge.mm
Problem: The VolumeCreationOptions C++ struct has no constructor. The bridge allocated
it with make_shared<VolumeCreationOptions>() without initializing FilesystemClusterSize,
leaving it as garbage memory. The FAT formatter used this garbage value as cluster size
instead of auto-detecting the optimal size, producing a corrupt FAT filesystem that
hdiutil attach could not read — making newly created FAT volumes unmountable.
Fix: Explicitly set cppOpts->FilesystemClusterSize = 0 (0 = auto-detect).
Files: Basalt/Bridge/TCCoreBridge.mm, Basalt/App/VolumeManager.swift
Problem: The VolumeCreator C++ class only formats FAT filesystems internally. HFS+
(Mac OS Extended) requires an external formatter (newfs_hfs), which the deleted wxWidgets
UI layer previously handled. Without it, HFS+ volumes were created with encrypted
random data but no filesystem, causing mount failures.
Fix: New formatVolumeFilesystem: bridge method. After volume creation completes,
the volume is temporarily mounted with NoFilesystem=true (FUSE + hdiutil attach -nomount),
/sbin/newfs_hfs -v TrueCrypt is run on the virtual block device (/dev/diskN), and the
volume is dismounted. The VolumeManager orchestrates this automatically for HFS+ volumes.
Files: Basalt/Bridge/TCCoreBridge.h, Basalt/Bridge/TCCoreBridge.mm,
Basalt/App/CreateVolumeSheet.swift
Problem: Volumes created with modern iteration counts (500,000+) cannot be opened by
TrueCrypt 7.1a. Users who need cross-version compatibility had no option to create
legacy-compatible volumes.
Fix: Added legacyIterations property to TCVolumeCreationOptions and a toggle in the
Create Volume wizard: "TrueCrypt 7.1a compatible (legacy iterations)". When enabled, the
bridge passes allowLegacy=true to Pkcs5Kdf::GetAlgorithm(), selecting the original
iteration counts (1000/2000). A warning is displayed explaining the weaker key derivation.
Files: Basalt/App/PasswordView.swift
Problem: The show/hide password toggle button (eye icon) was focusable, intercepting
Tab key navigation between password fields. In the Create Volume wizard, pressing Tab after
the first password field focused the eye icon instead of the confirmation field.
Fix: Added .focusable(false) to the toggle button, removing it from the Tab order.
This applies to all password fields across the application (Mount, Change Password, Create).
The original TrueCrypt 7.1a codebase included several subsystems designed for Windows full-disk encryption and enterprise environments. These have been completely removed from Basalt because they are irrelevant to container-based encryption and represent unnecessary attack surface.
Deleted: Driver/Ntdriver.c/h, Ntvol.c/h, DriveFilter.c/h,
DumpFilter.c/h, EncryptedIoQueue.c/h, build infrastructure.
Kernel drivers run at Ring 0 with full system access. A single vulnerability (buffer overflow, IOCTL validation error, race condition) grants an attacker complete control. Basalt uses DarwinFUSE — a userspace NFSv4 implementation that requires no kernel extension and runs unprivileged.
Deleted: Entire Boot/Windows/ directory — BootMain.cpp, BootSector.asm,
BootEncryptedIo.cpp, BootDiskIo.cpp, BootConsoleIo.cpp, BootMemory.cpp,
and 21 additional files (16-bit assembly, BIOS I/O, decompressor, CRT startup).
Pre-boot code runs before any operating system, with no ASLR, no DEP, no stack canaries. It is the hardest code to audit and the most dangerous to get wrong. VeraCrypt's pre-boot DCS bootloader is signed by Microsoft UEFI CA 2011, placing Microsoft in the trust chain. Basalt has no bootloader and no third-party trust chain.
Deleted: Common/SecurityToken.cpp/h (~800 lines), pkcs11.h, pkcs11f.h,
pkcs11t.h.
PKCS#11 is a complex C API for hardware security modules that loads third-party
shared libraries (.dylib / .dll) at runtime. Each loaded library runs in the
application's address space with full access to all memory, including encryption
keys. A compromised or backdoored PKCS#11 module would bypass all other security
measures. Basalt uses keyfiles (regular files) as the second factor instead.
Deleted: Entire Main/, Mount/, Format/ directories.
See Wave 6, §19 for details. The wxWidgets removal eliminated password handling
through wxString with uncontrollable heap copies, signal handler deadlocks,
destruction-order memory corruption, and a large third-party dependency.
The full git diff --stat from TrueCrypt 7.1a to Basalt shows 110,488 lines
deleted and 16,195 lines added across 496 files changed. The codebase shrank
from 194,634 lines (345 source files) to 47,439 lines (223 source files) — a
75.6% reduction. Every removed line is a line that cannot contain a
vulnerability.
This application remembers nothing. No favorites, no cached passwords, no default keyfiles, no mount history, no recently-used lists. Every operation starts clean.
- No password cache: The original TrueCrypt cached volume passwords in memory
(
VolumePasswordCache). This port removed the feature entirely (Wave 7). - No favorites or history: Volume paths are never persisted to disk. There is no record of which containers exist, when they were last mounted, or how often they are used. Forensic analysis of the application reveals nothing about your volumes.
- No default keyfile paths: Keyfile selection is always explicit, per-operation.
- No window state persistence: Window positions, last-used directories, and dialog states are not saved.
- Screen capture protection: The entire application window is invisible to screenshots,
screen recording, and screen sharing (
NSWindow.sharingType = .none).
The existence of your containers is your business, not your application's.
Basalt uses its own header magic (BSLT) for newly created volumes but can open volumes
from all three ecosystems:
| Magic Bytes | Format | Read (Mount) | Write (Create) |
|---|---|---|---|
BSLT |
Basalt | ✓ | ✓ |
TRUE |
TrueCrypt | ✓ | — |
VERA |
VeraCrypt | ✓ | — |
The magic bytes are encrypted — they reside inside the volume header, which is itself encrypted with the user's password via PBKDF2. This means:
- No forensic fingerprint: A Basalt volume is indistinguishable from random data on disk. No tool can determine whether a file is a Basalt, TrueCrypt, or VeraCrypt volume without the correct password.
- No shortcut for iteration detection: The magic bytes are only visible after successful decryption. The application must still try all KDF/cipher combinations during mount — the magic bytes merely confirm that decryption succeeded.
- Hidden volumes unaffected: Outer and inner headers are encrypted independently. The magic bytes of one cannot reveal the existence of the other.
VeraCrypt limitation: Only VeraCrypt volumes using AES, Serpent, or Twofish (and their cascades) can be mounted. Volumes using Camellia or Kuznyechik are not supported (see "Cipher Selection" above for the rationale).
Backward compatibility: Basalt volumes (BSLT) cannot be opened by TrueCrypt 7.1a
or VeraCrypt, as they do not recognize the BSLT magic. Use the "TrueCrypt 7.1a
compatible" option during volume creation if cross-application compatibility is required
(this uses legacy iteration counts but still writes the BSLT header).
- Encryption algorithms: AES, Serpent, Twofish and their cascades remain unchanged.
- XTS mode implementation: Only the key validation was added; the XTS math is unchanged.
- Volume header layout: Binary structure (offsets, sizes, backup header position) is identical to TrueCrypt 7.1a / VeraCrypt. Only the 4-byte magic identifier differs.
- On-disk format: Volumes are byte-compatible at the encryption layer. Data area layout, sector sizes, and XTS tweak computation are unchanged.
- FUSE driver: Volume mounting uses DarwinFUSE (built-in NFSv4 userspace FUSE). The FUSE driver logic is unchanged (only mount options were hardened with nosuid/nodev).
These are known issues that may be addressed in future work:
- No authenticated encryption: XTS does not detect tampering. AES-GCM or similar would require a format change.
- NSSecureTextField internal buffers: The SwiftUI
SecureField(backed byNSSecureTextField) is managed by Cocoa. While the system provides better password handling than wxWidgets, Apple's internal buffer management is opaque and may briefly retain password data. The bridge minimizes exposure by immediately converting tomlock()-pinnedSecureBuffer. - Legacy hash algorithms: SHA-1 and RIPEMD-160 are maintained for compatibility but are not recommended for new volumes. SHA-512 is the recommended default.
- Serpent/Twofish remain software-only: No hardware acceleration for these ciphers on ARM (no hardware support exists).
- Code signing: The app bundle is not currently signed or notarized. macOS Gatekeeper will require the user to manually allow execution.
VeraCrypt added Camellia and Kuznyechik (GOST R 34.12-2015) as additional cipher options. This port deliberately does not include them:
Kuznyechik (Russian GOST standard):
- Designed by the FSB (Russian Federal Security Service, successor to the KGB).
- In 2019, researchers Léo Perrin and Aleksei Udovenko demonstrated that the S-box was not randomly generated as claimed in the specification. It contains a hidden algebraic structure (a decomposition into two simpler permutations composed with a linear map). This is the same type of "nothing up my sleeve" violation that made Dual_EC_DRBG (NSA-backed RNG) a scandal.
- The design rationale for the S-box has never been publicly explained.
- Adding a cipher with unexplained structural anomalies designed by a signals intelligence agency would undermine the trust model of the entire application.
Camellia (NTT/Mitsubishi):
- A well-regarded cipher with no known weaknesses and solid academic analysis.
- However, it provides no security advantage over AES: same block size (128-bit), same key lengths, comparable security margin.
- No hardware acceleration on Apple Silicon — performance would be 5-10x slower than AES with ARMv8 Cryptographic Extensions.
- More cipher options means more code, more testing surface, and more potential for implementation errors, without any corresponding security benefit.
Our cipher suite (AES + Serpent + Twofish):
- All three are AES competition finalists with 25+ years of public cryptanalysis.
- AES: Hardware-accelerated on both ARM and x86, constant-time via dedicated instructions.
- Serpent: Most conservative design in the AES competition (32 rounds; best known attack reaches 12 rounds). Highest security margin of any practical block cipher.
- Twofish: Unbroken, with a deliberately different internal structure from AES and Serpent, making cascade combinations (AES-Twofish-Serpent) robust against algorithm-specific breaks.
- Cascaded modes provide defense-in-depth without introducing questionable ciphers.
VeraCrypt allows users to specify a custom PIM value that controls the PBKDF2 iteration count. This port deliberately does not implement PIM:
PIM is security by obscurity:
- PIM adds a second secret parameter to the mount process, but one with far less entropy than the password itself. Typical PIM values range from 1 to 999, providing at most ~10 bits of entropy. An attacker simply iterates all PIM values, multiplying the brute-force cost by only ~1,000x — the same improvement gained by adding 2–3 characters to the password.
- Users may perceive PIM as a second factor, but it is merely a weak multiplier on existing key derivation. True second factors (keyfiles, hardware tokens) provide independent entropy sources.
PIM can actively reduce security:
- VeraCrypt allows low PIM values. With SHA-512, PIM=1 results in only ~2,048 PBKDF2 iterations — 245x weaker than this port's fixed 500,000 iterations. Users who set low PIM values for "faster mount times" dramatically undermine their volume's brute-force resistance.
- This creates a dangerous asymmetry: the feature is marketed as a security enhancement but its most common use case (performance optimization) reduces security.
Our approach — fixed high iterations — is strictly better:
- 655,331 iterations (RIPEMD-160) / 500,000 (SHA-512, Whirlpool, SHA-1) cannot be weakened by user misconfiguration.
- Users who want stronger protection should use longer passwords and/or keyfiles, which add real entropy rather than obscurity.
- The iteration counts match VeraCrypt's defaults (PIM=0), so there is no performance or security disadvantage compared to a correctly-configured VeraCrypt volume.
This port addresses the same vulnerability classes as VeraCrypt but takes a more security-conservative approach in several areas:
Areas where this port is stricter than VeraCrypt:
- Password cache completely removed. VeraCrypt still maintains an in-memory password cache that stores plaintext passwords after mount. This port eliminates the feature entirely.
- No questionable ciphers. VeraCrypt includes Kuznyechik despite the 2019 S-box structure concerns. This port uses only AES competition finalists.
- Force dismount by default. Ensures volumes can always be closed, even when background processes hold file handles. VeraCrypt defaults to non-forced dismount.
- Smaller codebase. wxWidgets removal eliminates ~86 files and a large third-party dependency with its own vulnerability history.
Areas where VeraCrypt has capabilities this port does not:
- PIM (Personal Iterations Multiplier): User-tunable PBKDF2 iteration count. Deliberately not implemented — PIM is security by obscurity that can reduce security when misconfigured (see "Why Not PIM?" above). Fixed high iteration counts are safer.
- Full disk encryption (Windows): VeraCrypt's primary use case. Not applicable to this macOS-only port.
- Cross-platform support: Windows, Linux, macOS, FreeBSD. This port targets macOS only.
VeraCrypt bootloader trust chain concern: VeraCrypt's UEFI bootloader (DcsBoot.efi, DcsInt.efi) is sourced from the separate VeraCrypt-DCS project, which has received minimal updates since 2022. These bootloader binaries are signed by Microsoft Corporation UEFI CA 2011 — a third-party certificate that places Microsoft in the trust chain for full disk encryption. The DCS source code uses an outdated EDK2 fork, and reproducible builds of the shipped binaries have not been demonstrated. For full disk encryption, the bootloader IS the security boundary, making this a significant trust concern.
This port does not provide boot encryption and therefore has no bootloader trust chain dependency. All components are built from source with no pre-compiled binary blobs.
Any static, publicly available file can serve as a keyfile — in addition to a strong password, not as a replacement.
Requirements for a good public keyfile:
- Bit-identical across all sources (verify with SHA-256 after download)
- Available from multiple independent mirrors (no single point of failure)
- Memorable enough to locate again without written notes
- Static — the file must never change (avoid "latest version" links)
Examples: Old OS installation ISOs (Windows 98, Ubuntu 14.04), RFC documents from IETF, Linux kernel release tarballs, Wikimedia Commons images with stable file hashes, Project Gutenberg plain-text editions.
The border crossing scenario: When crossing borders with aggressive device inspection, carrying an encrypted volume is already suspicious. Carrying a USB stick with keyfiles makes it worse. With a public-file keyfile strategy:
- Cross the border with an empty laptop (or with the volume on cloud storage)
- After arrival, download the keyfile from its public source
- Mount the volume with password + keyfile
- No suspicious USB stick, no keyfile on the device, no evidence of what the keyfile is
Important: The keyfile adds entropy from its content (first 1,048,576 bytes are hashed into the key derivation pool). The security comes from the attacker not knowing which of millions of publicly available files you chose — not from the file being secret. This is defense-in-depth on top of a strong password, not a standalone protection.
The security audit was performed using LLM-assisted analysis of the complete TrueCrypt 7.1a source code, covering:
- Random number generation (pool mixing, entropy sources, self-tests)
- Key derivation (PBKDF2 implementation, iteration counts, RFC compliance)
- Encryption (AES/Serpent/Twofish implementations, mode of operation)
- Volume header format and key management
- Memory security (erasure, locking, lifetime)
- Suspicious patterns and potential backdoors
Additionally, all 2,687 commits of the VeraCrypt fork were analyzed to identify security-relevant changes, fixes, and potential concerns. This informed the selection and prioritization of hardening measures.