LUKS, hashcat, and hidden volumes
TL;DR: You can encrypt/protect a volume/partition using LUKS, and then nest another encrypted volume inside it (using VeraCrypt or similar), which results in the hash for the LUKS volume not being validated correctly using hashcat.
Why is this an issue?
Brute forcing LUKS2 volumes is computationally intensive, and current validation processes may result in incomplete or inaccurate results. The following are notes related to testing and validation of encrypted volumes which do not have a root filesystem.
Create a LUKS device (default version is 2). Password is set as 'password'
$ cryptsetup luksFormat /dev/sdc
WARNING!
========
This will overwrite data on /dev/sdc irrevocably.
Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sdc:
Verify passphrase:Verify /dev/sdc is now a LUKS device
$ cryptsetup luksDump /dev/sdc
LUKS header information
Version: 2
Epoch: 3
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
UUID: f4bd2ee4-969d-4022-b61e-23524e83f592
Label: (no label)
Subsystem: (no subsystem)
Flags: (no flags)
Data segments:
0: crypt
offset: 16777216 [bytes]
length: (whole device)
cipher: aes-xts-plain64
sector: 512 [bytes]
Keyslots:
0: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: argon2id
Time cost: 5
Memory: 1048576
Threads: 4
Salt: 60 a0 ab 32 26 09 c0 7b 1c bf 08 a0 2a 2f a6 0f
3b ab aa 65 d3 33 73 05 ac e1 72 a8 b9 a8 6d 44
AF stripes: 4000
AF hash: sha256
Area offset:32768 [bytes]
Area length:258048 [bytes]
Digest ID: 0
Tokens:
Digests:
0: pbkdf2
Hash: sha256
Iterations: 118724
Salt: 50 d4 e4 45 2e 30 1a b9 a7 72 4e b6 eb 94 de cb
d7 f6 f1 93 65 c7 76 fb 22 6f d9 d5 9f 3f 1a 3d
Digest: 93 25 02 6f f6 82 09 72 d5 19 b3 74 44 aa 10 65
a0 6a 61 5c bf 4c fc 13 2a bf fc f8 73 9a ab 91Run luks2hashcat against /dev/sdc
python3 luks2hashcat.py /dev/sdc > sdc.hashVerify the contents of sdc.hash, noting the first line of values (luks,2,argon2id,sha256, etc etc) aligns with the values we observed above. Note the end of the file (after the final $ placeholder) only contains zeroes - we'll come back to that shortly.

Let's run hashcat (v7.1.2, which supports argon2) against the hash with a simple dictionary attack (-a 0) and using 100 words in passwords.list (which contains 'password' as a candidate)
$ hashcat.exe -a 0 -m 34100 sdc.hash passwords.list
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=10485...000000
Time.Started.....: Sun Nov 09 12:41:24 2025 (24 secs)
Time.Estimated...: Sun Nov 09 12:41:48 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)
Guess.Queue......: 1/1 (100.00%)Strange - should've identified the correct password, right? That's because the LUKS container doesn't contain any data, and appears to be required to prove decryption.
Let's go back to /dev/sdc and create an ext4 filesystem, then compare our hashes and try again.
$ lsblk -lf
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sdc crypto_LUKS 2 f4bd2ee4-969d-4022-b61e-23524e83f592
$ cryptsetup luksOpen /dev/sdc sdc
Enter passphrase for /dev/sdc:
$ mkfs.ext4 /dev/mapper/sdc
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 520192 4k blocks and 130048 inodes
Filesystem UUID: 20bb32f6-e8cc-4ea8-a483-136ea7d5fed8
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912
Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done
$ cryptsetup luksClose sdc
$ lsblk -lf
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sdc crypto_LUKS 2 f4bd2ee4-969d-4022-b61e-23524e83f592
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdc 8:32 0 2G 0 disk
$ python3 luks2hashcat.py /dev/sdc > sdc.ext4.hashA quick comparison between sdc.hash and sdc.ext4.hash shows that the data portion we identified above, is now populated with data (since there's a filesystem, the container is no longer empty and actually contains data).

If we run hashcat again using the same command, we'll see the password is now cracked.
$3b900f5a933786926fedf8e520089c600444d103fba763f5490822e0b3b1f97bcf4ed4c7eb3580bad2a63a73eef5ce539369995d6898c59f6b9c246f028f082051e958d5b110e8f90df0cf81216b8776fd1c93bf7146b8e493253f25c2d21a7a3be2c8a57a04874c75f75acd1d5406e06a7901756df56e07a536c9bfaec652aecaefd4e4bae3dc38a639d919c3fbc54b98d6a66ef90475248a76868a47fe1c31b064943c2835542ce1cae1a5b5e38455ae8cd615357132c915abcc19159afa2e0a058960b0e9e63447bf53c7168bc0fddf8ee7edadb7e8a59a616004d5b9e008428ed88cd837dc85b76c255306375c60bd362b6bc6966328a1bda6c97b1e4c6194c194d4f347eb420cd93821dcf120060e65b679a8641bf2731b9982adc81da24f0074894566c0c3ed15f49d9340941575a45801bca5c089954459cf90b7b45b64dabe51ded570ca9d1d7cd52b696822aa10bcd798942ad893ce8b5e0b5c0aef03044855b463c7a70c81216967956b44670fa14d73512ba394fbfae743e8b277ffb6303de7e30e2ccc82ad8fe7e304d34b91319ae4e8c494a71273c93dd488ed2c7dadd4dd406b6680e9d2471afe48968d01b2b95d02d0be8bd3254d17b17594afe6f89126cb7d4def020c6e31b8ee71e055afe43b3c35a0238b862c9d3951fa75b90f60d21f1845d0b54b7f5de8b9fade8bbaa5d00ea9a427d5bc9b27c782a4:password
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=10485...c782a4
Time.Started.....: Sun Nov 09 12:48:26 2025 (9 secs)
Time.Estimated...: Sun Nov 09 12:48:35 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)This raised the question - does the data portion of the hash have to be verified by opening/mounting the container/filesystem etc before the password candidate will be identified as valid? What if the encrypted block device/partition was intended to be mounted by another program (VeraCrypt, for example) - how will this complicate recovery efforts?
Using a fresh /dev/sdc, we'll do the following;
Create a LUKS2 volume, password is password.
Leave the block device unformatted (no filesystem).
Mount the LUKS2 volume as 'sdc'
Encrypt /dev/mapper/sdc using VeraCrypt, 'veracrypt' as the password
Completing steps 1 to 3, and then running luks2hashcat against /dev/sdc, we can see the data portion is still zeroes. So we'll create a VeraCrypt container (using /dev/mapper/sdc)
bc$0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
$ cryptsetup luksOpen /dev/sdc sdc
Enter passphrase for /dev/sdc:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdc 8:32 0 2G 0 disk
└─sdc 253:0 0 2G 0 crypt
$ veracrypt -t -c
Volume type:
1) Normal
2) Hidden
Select [1]:
Enter volume path: /dev/mapper/sdc
Encryption Algorithm:
1) AES
2) Serpent
3) Twofish
4) Camellia
5) Kuznyechik
6) AES(Twofish)
7) AES(Twofish(Serpent))
8) Camellia(Kuznyechik)
9) Camellia(Serpent)
10) Kuznyechik(AES)
11) Kuznyechik(Serpent(Camellia))
12) Kuznyechik(Twofish)
13) Serpent(AES)
14) Serpent(Twofish(AES))
15) Twofish(Serpent)
Select [1]:
Hash algorithm:
1) SHA-512
2) SHA-256
3) BLAKE2s-256
4) Whirlpool
5) Streebog
Select [1]:
Filesystem:
1) None
2) FAT
3) Linux Ext2
4) Linux Ext3
5) Linux Ext4
6) NTFS
7) exFAT
8) Btrfs
Select [2]: 5
Enter password:
WARNING: Short passwords are easy to crack using brute force techniques!
We recommend choosing a password consisting of 20 or more characters. Are you sure you want to use a short password? (y=Yes/n=No) [No]: y
Re-enter password:
Enter PIM:
Enter keyfile path [none]:
Please type at least 320 randomly chosen characters and then press Enter:
Done: 100.000% Speed: 25 MiB/s Left: 0 s
The VeraCrypt volume has been successfully created.
$ cryptsetup luksDump /dev/sdc
LUKS header information
Version: 2
Epoch: 3
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
UUID: 089aa779-bc36-41e6-a207-73981b4d3e36
Label: (no label)
Subsystem: (no subsystem)
Flags: (no flags)
Data segments:
0: crypt
offset: 16777216 [bytes]
length: (whole device)
cipher: aes-xts-plain64
sector: 512 [bytes]
Keyslots:
0: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: argon2id
Time cost: 4
Memory: 997316
Threads: 4
Salt: d2 50 7c 75 e0 59 6e c7 1a 6b 8c df b2 15 62 81
8c 7b 17 7e f2 fd 50 e5 73 04 c8 a5 75 19 fc 11
AF stripes: 4000
AF hash: sha256
Area offset:32768 [bytes]
Area length:258048 [bytes]
Digest ID: 0
Tokens:
Digests:
0: pbkdf2
Hash: sha256
Iterations: 89043
Salt: 65 9c e9 12 ba 39 f9 6a 36 1a 41 c7 e0 31 6e 1a
59 e0 89 26 69 ee 6d 40 b6 59 62 c6 91 03 1d 07
Digest: 5e 29 39 16 6f 23 e9 e4 57 09 b6 4b 14 7b fa 9b
03 ed 18 15 70 6a cb 9f 6d da 59 8e e7 3c c2 06Verify there is no filesystem on /dev/sdc, or sdc (/dev/mapper/sdc)
$ lsblk -lf
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sdc crypto_LUKS 2 089aa779-bc36-41e6-a207-73981b4d3e36
sdcMount VeraCrypt container and verify
$ mkdir /mnt/vc
$ veracrypt /dev/mapper/sdc /mnt/vc
Enter password for /dev/mapper/sdc:
Enter PIM for /dev/mapper/sdc:
Enter keyfile [none]:
Protect hidden volume (if any)? (y=Yes/n=No) [No]:
$ ls /mnt/vc
lost+found
$ lsblk -lf
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
sdc crypto_LUKS 2 089aa779-bc36-41e6-a207-73981b4d3e36
sdc
veracrypt1 ext4 1.0 00adf9dc-6299-4a42-86ac-dfeca64c21d6 1.8G 0% /mnt/vcUnmount both the VeraCrypt and LUKS volumes.
$ veracrypt -u /dev/mapper/sdc; cryptsetup luksClose sdcCalculate the hash for /dev/sdc (expecting a non-zero footer)
$ python3 luks2hashcat.py /dev/sdc > sdc.vc.hash
$ tail -c 1024 -f sdc.vc.hash
c26e500a620208c74f17b177524a2d2b2c91dfcc3ab683ce7a075848290321c9b4b810b147c3e4f640e751de74e35b7c48b36ff08f5f13d98524eec50730fbd28722d80867849230535dfb932d9347188fe413f6e4106a0d731dbe8380f4853b1b482354879cb06f91a0c76cea8a5d6a4bf13178edb196769429ab7854635e92cf1ba3dfe48e548038e779b93d0942e7860dab90e39ecffa243c8c7146600ef7de2a5f86d214b7e99f64218ad052e9010d3d105b5546ef20e1be4cfe062691b51efdde4c874ee059d2ef860b685404b805b73b518593df96439517df9bfd9ef6a1248a7f42dabd6fd06329357bea2f7c315d2846131f03fad7705e9545f23e5b643575c29fa9d7f24c4d7a7cea93a9e7772a8be6e7e58a21a009a7497b8f4a0dc7bfb4ce37792a79d4a8c2d55ae4720c0bcd6666065c0f1d1a9fece9db41cabd94fdded4adc8d0cd69d63acb9b5493b334f2030df05f327115fcf3654b53baa3b7769aa63f1c1985a09cc004911ac694871e46efee372613ac8ce1f6020ea81abbb31c55be8704f266309fc2d654d1763e061fe759943e5c61084beeccb7555ca56d6b8e3c91e91cb2e102ff26bec883cc54edfc491930c09981d45bf463828d954f03f194399c5f8c6875d1a2705453dd89ac0c097948ff2b8cc8d207e058377789a5b2d5ae2ae4e2c78e7c436e4baf7a62e1b81b2abfe5f4d64164381a2bRun hashcat again.. no results.
$ hashcat.exe -a 0 -m 34100 sdc.vc.hash passwords.list
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 34100 (LUKS v2 argon2 + SHA-256 + AES)
Hash.Target......: $luks$2$argon2id$sha256$aes$xts-plain64$512$m=99731...81a2bf
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (passwords.list)
Guess.Queue......: 1/1 (100.00%)Last updated