Well, in the most naive case you can roughly do something like
for a in 'fo' 'foo' 'fooo'
for b in 'ba' 'bar' 'baar'
for c in 'bz' 'baz' 'bazz'
echo -n "$a$b$c" | cryptsetup open /dev/luks luks \
&& echo "'$a$b$c' is the winner!" \
&& break 3
and it goes through all the puzzle pieces ... foobarbz foobarbaz foobarbazz ... etc. in order. (If you have optional pieces, add '' empty string. If your pieces are in random order, well, think about it yourself).
To optimize performance, you can:
cryptsetup to keep reading passphrases from stdin (lukscrackplus on github for one such example but it's dated)
- generate the complete list of words,
split it into separate files, and run multiple such loops (one per core, perhaps even across multiple machines)
- compile cryptsetup with a different/faster crypto backend (e.g. nettle instead of gcrypt), difference was huge last time I benchmarked it
- find a different implementation meant to bruteforce LUKS
But it's probably pointless to optimize if you have either too little (can go through in a day w/o optimizing) or way too many possibilities (no amount of optimizing will be successful).
At the same time, check:
- are you using the wrong keyboard layout?
- is the LUKS header intact?
(with LUKS1 there is no way to know for sure, but if you
hexdump -C it and there is no random data where it should be, no need to waste time then)
There's also a similar question here: https://security.stackexchange.com/q/128539
But if you're really able to narrow it down by a lot, the naive approach works too.