Cracking LUKS/dm-crypt passphrases
Linux uses dm-crypt in order to provide transparent disk or partition encryption. What are the options in case you need to recover passphrase from such encryption? There are already ready-made tools, but we have also produced and published our own in order to support newer LUKS format/ciphers/hashing.
dm-crypt is a transparent disk encryption subsystem in the Linux kernel. It is implemented as a device mapper target and may be stacked on top of other device mapper transformations. It can thus encrypt whole disks (including removable media), partitions, software RAID volumes, logical volumes, as well as files. It appears as a block device, which can be used to back file systems, swap or as an LVM physical volume.
There are many formats or types which dm-crypt/cryptsetup support (current version supports luks, luks1, luks2, plain, loopaes, tcrypt), but the most commons ones are LUKS1 and LUKS2, where LUKS2 is an obviously newer format, which uses argon2i by default. It is a less known fact that cryptsetup supports TrueCrypt/VeraCrypt as well. Here are usual compiled-in defaults of cryptsetup:
Default compiled-in key and passphrase parameters:
Maximum keyfile size: 8192kB, Maximum interactive passphrase length 512 (characters)
Default PBKDF2 iteration time for LUKS: 2000 (ms)
Default PBKDF for LUKS2: argon2i
Iteration time: 2000, Memory required: 1048576kB, Parallel threads: 4
Default compiled-in device cipher parameters:
loop-AES: aes, Key 256 bits
plain: aes-cbc-essiv:sha256, Key: 256 bits, Password hashing: ripemd160
LUKS1: aes-xts-plain64, Key: 256 bits, LUKS header hashing: sha256, RNG: /dev/urandom
Introduction
If you are using any popular Linux distribution and you’re using encrypted partitions, there is a high chance that it is using LUKS1. Android encryption is also using LUKS for device encryption option. The way the LUKS works is that you have a master key which is generated for encryption and there are 8 key slots which are guarding the master key. Any key slot is able to unlock the partition if it is enabled and it is also able to dump the master key. When you setup the passphrase for the encryption, you are actually changing the passphrase for the slot and you’re not changing the master key itself as that would require reencrypting the whole partition. If somebody has access to the master key, that somebody can decrypt the data without knowing any passphrase.
In this text, we will focus on cracking the passphrases behind key slots and not attacking the master key itself as that would require much more resources if the master key is generated properly. Once you have a valid passphrase for any of the key slot, it is possible to dump the master key. So, basically having a passphrase is the same as having the master key and attacking the passphrases, in most cases, is the most viable option.
Recovering the passphrase of such an encryption depends on format, cipher and key size, mode used and the strength of the passphrase that you are recovering.
Identifying LUKS
There are different ways to identify LUKS. One of the most easiest one is to use blkid
:
# blkid -t TYPE=crypto_LUKS -o device
/dev/sdb2
/dev/sdb3
Command will output each device/partition identified to stdout separated by new line. Once identified, you can gain more data about the target with luksDump
command:
# cryptsetup luksDump /dev/sdb3
Command will output information about encryption used and key slots on specified partition (in this case: /dev/sdb3
).
Backup
Before we go any further with cracking, you should be careful with encryption actions you perform. If you are an IT veteran, you will know that backup is essential before doing anything experimental (or just create experimental LUKS encryption to test). Let me give you a single argument in this case: you don’t want to lose your data. Another argument is that you don’t want to transfer the whole disk, of which you only want to recover the LUKS passphrase, but only the smaller part. This is especially important if you plan to distribute it to many computers for cracking purposes. Command to backup the needed data from the disk is following:
$ dd if=/dev/sdb3 bs=1 count=2066432 of=./sdb3-to-crack
You’re probably wondering how I figured the exact number of 2066432 bytes? Well, you can try with the smaller number and open it with luksDump command and you will get the following output:
$ cryptsetup luksDump sdb3-to-crack
Device sdb3-to-crack is too small. (LUKS1 requires at least 2066432 bytes.)
So, this is the evidence that you copied enough data in order to crack it.
Basic cracking
cryptsetup itself allows to test the single passphrase by using –test-passphrase option:
$ echo "test" | cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
Therefore, it is possible to run the basic cracking job using wordlist with the following options:
$ cat wordlist.txt | xargs -t -P `nproc` -i echo {} | cryptsetup --verbose --test-passphrase open sdb3-to-crack sdb3_crypt
It is also possible to run password cracking legend John The Ripper with any of his powerful options (you just need –stdout option):
$ john --wordlist=wordlist.txt --stdout | xargs -t -P `nproc` -i echo {} | cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
Hashcat is also an option to generate candidate passwords:
$ hashcat -m 0 --stdout -a 3 ?a?a?a?a | xargs -t -P `nproc` -i echo {} | cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
The main problem here is that such cracking is pretty slow, as you have to spawn cryptsetup for each test of the candidate password. You also have to inspect the output of the commands manually in order to check that password was cracked.
Faster cracking
Basic cracking works if you have few candidate passwords to try. But, if you have a tougher job and you need to guess the password faster, as you have many more candidate passwords to try, it is time to look for faster options. Both Hashcat and John the Ripper support password cracking of LUKS passphrases, but they are both limited to what cipher/hashing/LUKS[12] they support. If you’re lucky enough that you need to recover passphrase from some older LUKS encryption, you can use both tools.
Cracking using John The Ripper
For example, when using John The Ripper (Jumbo version!), you need to prepare the data for cracking by using luks2john helper python script available from the run directory of John The Ripper:
# luks2john.py /dev/sdb3 > sdb3.john
Best keyslot [0]: 460431 keyslot iterations, 4000 stripes, 120250 mkiterations
Cipherbuf size: 128000
sdb3.john file should end up with all the data needed for cracking. You will recognize it by $luks$1$
magic in front of the hash. Once the data is prepared, you can begin with standard John the Ripper session:
# john sdb3.john
John the Ripper has a hard limitations on cipher/hash/mode combinations, so there is a high chance that you will not be able to crack it with John The Ripper. One of the examples when luks2john fails is the following:
$ luks2john.py sdb3-to-crack
sdb3-to-crack : Only cbc-essiv:sha256 mode is supported. Used mode: xts-plain64
Cracking using Hashcat
In case you get that message from John, and if using LUKS version 1, you will have more luck if you try to crack it by using Hashcat. Hashcat is a bit different to use, but it does have far better and complete support for LUKS cracking than John The Ripper. In order to prepare the target for cracking, you have to dump the LUKS header and add a first sector of payload since hashcat has optimized the cracking, where it does not perform second PBKDF2 which LUKS performs, so cracking is significantly faster using hashcat. Usually, the preparation consist of copying the LUKS header and payload with dd command:
# dd if=/dev/sdb3 of=hashcat.luks bs=512 count=4097
Once you have the header, you can start the cracking session by using 14600 as hash type:
$ hashcat -a 0 -m 14600 hashcat.luks wordlist.txt
The output is pretty standard hashcat status output (luks1 type with aes, cbc-essiv:sha256, sha1):
Session..........: hashcat
Status...........: Exhausted
Hash.Type........: LUKS
Hash.Target......: hashcat.luks
Time.Started.....: Sun Nov 10 07:43:27 2019 (1 min, 16 secs)
Time.Estimated...: Sun Nov 10 07:44:43 2019 (0 secs)
Guess.Base.......: File (example.dict)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 882 H/s (2.29ms) @ Accel:2 Loops:64 Thr:64 Vec:1
Speed.#2.........: 885 H/s (2.29ms) @ Accel:2 Loops:64 Thr:64 Vec:1
Speed.#*.........: 1767 H/s
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 128416/128416 (100.00%)
Rejected.........: 0/128416 (0.00%)
Restore.Point....: 123344/128416 (96.05%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:141568-141591
Restore.Sub.#2...: Salt:0 Amplifier:0-1 Iteration:141568-141591
Candidates.#1....: tom -> webintec
Candidates.#2....: webis -> zzzzzzzzzzz
Hardware.Mon.#1..: Util: 27% Core: 406MHz Mem:1250MHz Bus:16
Hardware.Mon.#2..: Util: 0% Core:1000MHz Mem:1250MHz Bus:4
And for another type of LUKS1 hash (aes, xts-plain64, sha256) output is also standard one:
Session..........: hashcat
Status...........: Exhausted
Hash.Type........: LUKS
Hash.Target......: hashcat.luks
Time.Started.....: Sun Nov 10 07:59:45 2019 (3 mins, 7 secs)
Time.Estimated...: Sun Nov 10 08:02:52 2019 (0 secs)
Guess.Base.......: File (example.dict)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 351 H/s (2.71ms) @ Accel:2 Loops:64 Thr:64 Vec:1
Speed.#2.........: 358 H/s (2.52ms) @ Accel:2 Loops:64 Thr:64 Vec:1
Speed.#*.........: 709 H/s
Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) Salts
Progress.........: 128416/128416 (100.00%)
Rejected.........: 0/128416 (0.00%)
Restore.Point....: 123344/128416 (96.05%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:331584-331604
Restore.Sub.#2...: Salt:0 Amplifier:0-1 Iteration:331584-331604
Candidates.#1....: tom -> webintec
Candidates.#2....: webis -> zzzzzzzzzzz
Hardware.Mon.#1..: Util: 0% Core: 420MHz Mem:1250MHz Bus:16
Hardware.Mon.#2..: Util: 7% Core:1000MHz Mem:1250MHz Bus:4
As you can see, speed of cracking LUKS1 on two R9 290x GPUs is around 790 H/s (candidate passwords per seconds). Therefore, cracking is not that fast as some other password/hashing formats. But benchmarking is topic for another article.
There are cases when specified size is not the standard one, so you will get error message like this:
$ hashcat64.bin -a 0 -m 14600 sdb3-to-crack wordlist.txt
hashcat (v5.1.0) starting...
OpenCL Platform #1: The pocl project
====================================
* Device #1: pthread-Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz, 8192/21999 MB allocatable, 8MCU
Hashfile 'sdb3-to-crack': Invalid LUKS filesize
No hashes loaded.
If you get such or similar error, sometimes LuksHeader4Hashcat utility by paule965 might help as it rebuilds luksheader in order to prepare it for hashcat:
# ./LuksHeader4Hashcat.py /dev/sdb3
##############################################################################################################
Basic-Data
----------
Date/ Time (YYYY-MM-DD HH:MM:SS): 2019-11-16 07:12:17
FileName(arg1): /dev/sdb3
ScriptName(arg0): ./LuksHeader4Hashcat.py
Filepath: /dev/sdb3
############################################################
Luks-Basic-Data
[..]
Status SlotNumber Iterations MeyMaterialSector AF-Stripes
----------------------------------------------------------------------------------
ACTIVE-Slot: 0 141592 0X0008 4000
EMPTY-Slot: 1 - - -
EMPTY-Slot: 2 - - -
[..]
##################################################################################
Which KeySlot should be used? Possible is [0]: 0
Your Choice is KeySlot0.
Write to File: /dev/sdb3_KeySlot0.bin
Even if LuksHeader4Hashcat cannot help you, check the format and LUKS version of the target to crack with luksDump
command.
The real problem is that, both hashcat and JtR, support older LUKS1 format, so you would get an error if you try to crack the newer format like LUKS2 (or other uncommon format).
In such cases you have to read further in order to recover such passphrase.
Cracking newer formats
Currently, to crack newer or other uncommon formats, it is only possible to use cryptsetup based tools. That means that you have to go back to basic cracking section of this article, and use the shell scripts or binaries that use direct functions from the cryptsetup library.
Cracking using grond.sh
One of such scripts is grond.sh and you can use it to crack luks format. Its pretty limited and thread support is pretty hard coded, but you can use it for basic cracking.
Basic invocation of grond script is following:
# ./grond.sh -t4 -w wordlist.txt -d /dev/sdb3
Grond can use multiple threads, but if you need something faster, there are still different options.
Cracking using bruteforce-luks
More advanced tool is bruteforce-luks. bruteforce-luks is a C program which binds to cryptsetup library and has the basic bruteforcing options included.
Once you manage to compile it, you can invoke it by number of threads you want to use and choose different modes of cracking. For example, you can use dictionary mode and read the candidate password from the wordlist or dictionary:
# bruteforce-luks -t 4 -f wordlist.txt /dev/sdb3
There is also an interesting mode where you can specify possible beginning of the password and ending of the password and bruteforce-luks will bruteforce the missing characters in the middle:
# bruteforce-luks -t 4 -l 5 -m 12 -b "Begin" -e "End" /dev/sdb3
Actually, while writting this article, there were commits which enabled bruteforce-luks to support LUKS2, so it was good timing to actually introduce this feature.
Cracking using modified cryptsetup
The only issue with bruteforce-luks is that you cannot use John the Ripper and hashcat powerful candidate rule generation as it does not support stdin. Also, I wanted to have an approach where cracking will work under any custom parameter and format that cryptsetup supports. Therefore, an approach was to change the cryptsetup itself minimally to accept multiple tries from standard input (stdin). Such patch was made and you can download and compile original cryptsetup with patch.
git clone -b pwguess https://github.com/diverto/cryptsetup-pwguess.git
cd cryptsetup-pwguess
./autogen.sh
./configure
make
You can still use the newly compiled cryptsetup as usual with single passphrase:
$ echo "test" | ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
Where modified cryptsetup really shines is when you pass multiple passwords, separated with the newline, on its standard input:
$ cat wordlist.txt | ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
It will try each password candidate from the wordlist.txt and report if password is correct. Another helpful way of cracking is by using rexgen, where you can specify password candidates using regular expression (as an example it will generate Test01 to Test99 password candidates):
$ rexgen 'Test[0-9]{2}' | ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
There are also helpful environment variables for password guessing. For example, you can set DIVERTO_LUKS_VERBOSE
to any value and it will report each password result:
$ export DIVERTO_LUKS_VERBOSE=1
I would suggest to use DIVERTO_LUKS_VERBOSE
for setting up cracking session and checking if everything is working like expected, and later you can just unset it:
$ unset DIVERTO_LUKS_VERBOSE
Another helpful environment variable is DIVERTO_LUKS_OUT
which you can set to write successfully guessed passwords. Example is trivial:
$ export DIVERTO_LUKS_OUT=/tmp/cracked.txt
When using it this way, you can monitor for /tmp/cracked.txt file if cracking was successful. Make sure that cryptsetup have permissions to create file in the directory you plan to write the output to.
Real power comes from using Hashcat and/or John The Ripper candidate password generator feature and piping it to the modified cryptsetup:
$ john --wordlist=wordlist.txt --rule=jumbo --stdout | ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
And example for hashcat
$ hashcat -m 0 --stdout -a 3 ?a?a?a?a | ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
If you want to crack using multiple threads, you can take advantage of GNU parallel. The following is an example with 2 threads:
$ cat wordlist.lst | parallel --pipe -j 2 -N 1000 ./cryptsetup open --test-passphrase sdb3-to-crack sdb3_crypt
The following example is using jumbo rules to feed it to the modified cryptsetup (using 2 threads):
$ john --wordlist=wordlist.txt --rule=jumbo --stdout | parallel --pipe -j 2 -N 1000 ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
Similar can be used to crack using Hashcat (using 4 threads):
$ hashcat -m 0 --stdout -a 3 ?a?a?a?a | parallel --pipe -j 4 -N 1000 ./cryptsetup --test-passphrase open sdb3-to-crack sdb3_crypt
Remediations
If you are worried that someone might steal your data by using these techniques, you can start by changing the LUKS passphrase you’re using. Of course, you can even change it on a regular basis. LUKS can hold up to 8 slots numbered from 0 to 7 and any key slot is able to unlock the partition if it is enabled. So, changing the passphrase consists of calling luksChangeKey
with slot number specified (if having a single passphrase, slot should be 0):
# cryptsetup luksChangeKey /dev/sdb3 -S 0
Alternative would be adding a new passphrase to an empty slot and deleting the slot that holds the old passphrase:
# cryptsetup -y luksAddKey /dev/sdb3
# cryptsetup luksRemoveKey /dev/sdb3
Advantage of this method is that you can first test is everything is working before deleting the old passphrase. Still, if you forget to remove the slot, both old and new passphrases will work and therefore will reduce the overall security level of the encryption.
Converting to LUKS2
If you’re still worried and want to increase your security posture, you can carefully choose encryption/hash/mode and convert your partition to LUKS2 format. Note that GRUB2 still does not support LUKS2, so having LUKS2 format requires having at least /boot
partition unencrypted. Converting to LUKS2 format is done by using the following command:
# cryptsetup convert /dev/sdb3 --type=luks2
WARNING!
========
This operation will convert /dev/sdb3 to LUKS2 format.
Are you sure? (Type uppercase yes):
Hiding LUKS headers
Another good trick is to remove the luks header completely from the partition, in cases when you are forced to provide your key to encrypted data or when your passphrase leaked. Attacker would have a hard time recovering as he does not have encryption methods used and salt. When creating such scenario, you can use following command:
# cryptsetup luksFormat --type luks2 /dev/sdb3 --align-payload 8192 --header /somewhere/safe/header.luks
Opening the encrypted container is also bit different as you have to specify location of LUKS header:
cryptsetup open --header=/somewhere/safe/header.luks /dev/sdb3 sdb3_crypt
In this case, it is no longer easy to identify LUKS partition. Of course, entropy analysis would provide clue about potential encryption.
By looking at the hashcat discoveries, it seems that it would be harder for an attacker to backup and remove first sector of the payload itself. Idea is to backup LUKS header and first sectors of the encrypted data to different safe medium:
# dd if=/dev/sdb3 of=luks.sensitive bs=512 count=4097
After successful backup, you can overwrite it with the random data:
# dd if=/dev/random of=/dev/sdb3 bs=512 count=4097
In this case, you have to copy back the first sector of the payload data before specifying the location of the LUKS header:
# dd if=luks.sensitive of=/dev/sdb3 bs=512 skip=4096 seek=4096 count=1 conv=notrunc
# cryptsetup open --header=/somewhere/safe/luks.sensitive /dev/sdb3 sdb3_crypt
And of course, after finishing - filling up with the random bytes again:
# cryptsetup close sdb3_crypt
# dd if=/dev/random of=/dev/sdb3 bs=512 skip=4096 seek=4096 count=1 conv=notrunc
Recovery concerns and backup
If you are worried that you will forget your passphrase or your data, it is a good practice to actually backup LUKS header and store it somewhere safe. So, in case of LUKS data corruption - you would still have the most valuable data in recovery - the keys to the encrypted data. Command is:
# cryptsetup luksHeaderBackup /dev/sdb3 --header-backup-file /somewhere/safe/sdb3-luks-header.backup
Restore of the header is done via luksHeaderRestore command:
# cryptsetup luksHeaderRestore /dev/sdb3 --header-backup-file /somewhere/safe/sdb3-luks-header.backup
Note that, in the case of recovery of backup, valid passphrase would be in time when the backup is performed.
Backup of master key
Another thing that can help during the recovery procedure is backing up the master key. Having the master key allows access to the encrypted data without the knowledge of any passphrase of the slots. You can dump the master key with the --dump-master-key
option:
# cryptsetup luksDump -q --dump-master-key /dev/sdb3 > /somewhere/safe/sdb3-luks-master.key
With master key you can manage key slots even when you don’t know any passphrase. Therefore, you should store your master key in a safe place. Even better, it is recommended to immediately encrypt it in the process. One of the good options is using your GPG key, so you can just pipe it to the gpg for encryption. Of course, all of that depends on your threat model and encryptions preferred.
Note that dump-master-key will dump the master key in hex format under MK dump
field. Therefore, you need to convert it to the binary format if you plan to use it later with cryptsetup. You can use the following oneliner to create a binary file:
# cryptsetup luksDump -q --dump-master-key /dev/sdb3 | grep -A 3 'MK dump' | sed -e 's/MK dump://g' -e 's/\s//g' | xxd -r -p > sdb3-luks-master.bin
After having the binary form of the master key, you can easily manipulate with the key slots. Here’s the example:
# cryptsetup luksAddKey /dev/sdb3 --master-key-file sdb3-luks-master.bin
Enter new passphrase for key slot:
Verify passphrase:
Disclaimer
Current limitations of the tools are described as of the date of this blog post. Hopefully, both Hashcat and John The Ripper will get a support for all of the format/hash/cipher combinations that LUKS supports.
Tools
Tools mentioned in this article:
- cryptsetup-pwguess - cryptsetup with minimal modification to accept multiple tries of password guessing
- bruteforce-luks - separate LUKS cracking project taking advantage of cryptsetup library with needed function calls
- grond.sh - shell script to automatize cracking
- rexgen - A tool to create words based on regular expressions
- John The Ripper Jumbo - Jumbo version of the John The Ripper
- hashcat - Hashcat, advanced password recovery
- LuksHeader4Hashcat - rebuild a luksheader for hashcat