yazc (Yet Another Zip Cracker) is a simple command-line application for
recovering passwords and encryption keys from legacy ZIP archives.
The project builds and installs a single executable, yazc. Its attack modes
and archive-inspection support are available as subcommands; there is no
separate library or collection of helper executables to install.
On Ubuntu, install the required packages with:
sudo apt install -y autoconf automake bison flex zlib1g-dev pkg-config
The Vulkan brute-force backend is built automatically when the Vulkan headers
and loader development package are available. On Ubuntu, install
libvulkan-dev. Use ./configure --disable-vulkan to build without it. The
runtime also needs a Vulkan driver exposing a compute-capable device.
Building the unit tests also requires Check.
Clone, configure, compile, and install the project:
git clone https://github.com/mferland/libzc.git
cd libzc
./autogen.sh
./configure CFLAGS='-O3 -ffast-math -march=native -mtune=native'
make
sudo make install
This installs yazc, its manual page, and this README. Run yazc --help to
list the available subcommands.
There are currently four attack modes available:
This mode tries every password that can be generated from the given character set. It supports multithreading.
-c, --charset specifies the character set. For example, -c abc123
tries every combination of a, b, c, 1, 2, and 3 up to the
maximum password length, which defaults to eight characters.
-i, --initial specifies the first password to try. By default, the
initial password is the first character in the character set. For
example, with the character set abc, the search begins with a, then
b, c, aa, and so on. This option is useful for skipping part of
the password search space.
-l, --length specifies the maximum password length. The program stops
after testing every password through length.
--min-length specifies the minimum password length in character-set mode.
It defaults to one and must not exceed --length. When --initial is also
provided, the initial password may start later than this minimum but not
earlier.
-a, --alpha uses lowercase ASCII letters (a-z).
-A, --alpha-caps uses uppercase ASCII letters (A-Z).
-n, --numeric uses digits (0-9).
-s, --special uses printable special ASCII characters.
-t, --threads=N specifies the number of worker threads. Use
--threads=auto to select the number of online CPUs reported by
sysconf(_SC_NPROCESSORS_ONLN). This is the default.
-S, --stats prints runtime statistics, the estimated number of password
candidates tested, and the estimated password rate. Candidate accounting is
performed at completed worker leaf and vector-batch boundaries to avoid
synchronization or per-password timing overhead in the cracking loops.
A mask defines the allowed characters separately for each password
position. Using -m, --mask selects mask mode instead of the character-set
mode described above. Quote masks on the command line so the shell does not
interpret characters such as ?, [, or ].
-m, --mask=MASK specifies the password mask. Without either of the length
options below, the program tests passwords whose length exactly matches the
mask. Passwords cannot exceed 16 characters.
-k, --mask-minlen=N also tests shorter prefixes of the mask, starting at
length N. The minimum length cannot exceed the number of positions in the
mask.
-x, --mask-maxlen=N also tests longer passwords, up to length N, by
repeating the final mask position. The maximum length cannot be shorter than
the mask.
Each mask position can be a literal character, a character list such as
[abc], an alphanumeric range such as [a-z], or one of these placeholders:
| Placeholder | Characters |
|---|---|
?l |
Lowercase ASCII letters (a-z) |
?u |
Uppercase ASCII letters (A-Z) |
?d |
Decimal digits (0-9) |
?s |
Printable special ASCII characters, including space |
?a |
All printable ASCII characters (0x20–0x7e) |
?B |
Upper-half byte values (0x80–0xff) |
?b |
All non-NUL byte values (0x01–0xff) |
?h |
Lowercase hexadecimal characters (a-f, 0-9) |
?H |
Uppercase hexadecimal characters (A-F, 0-9) |
Lists can combine characters and ranges. For example, [a-f0-9_] matches a
lowercase hexadecimal character or an underscore. Use a backslash to include
a mask metacharacter literally, such as \? for a question mark or \\ for
a backslash. A byte can also be written as \xNN, where NN is its two-digit
hexadecimal value. Unescaped whitespace in a mask is ignored; use \x20 when
a position must contain a space.
For example, try a literal pass- prefix followed by four digits:
yazc bruteforce --mask='pass-?d?d?d?d' archive.zip
Try either Pass0 or pass0 through Pass9 or pass9:
yazc bruteforce --mask='[Pp]ass?d' archive.zip
The following mask first tests one lowercase letter, then a lowercase letter followed by one digit, and finally passwords with that prefix followed by up to two more digits:
yazc bruteforce --mask='?l?d' --mask-minlen=1 --mask-maxlen=4 archive.zip
-i, --initial can also be used in mask mode. The initial password must have
the minimum generated length and every character must match its corresponding
mask position.
Try all passwords in a-z0-9 up to eight characters using four worker
threads:
yazc bruteforce -a -n -l8 -t4 archive.zip
Try all password combinations using the characters abc123 up to a
maximum of ten characters, using the default number of worker threads:
yazc bruteforce -c abc123 -l10 archive.zip
This experimental mode generates and filters password candidates using a Vulkan compute device. It supports a custom character set and an inclusive password-length range. Each fixed-length subspace is exhausted before the next length begins.
-c, --charset specifies the character set.
The predefined character classes are the same as for the CPU brute-force
command: -a, --alpha adds lowercase ASCII letters, -A, --alpha-caps adds
uppercase ASCII letters, -n, --numeric adds digits, and -s, --special adds
printable special ASCII characters. The class options can be combined. An
explicit --charset takes precedence over them.
-l, --length specifies the maximum password length. It is optional and
defaults to eight, matching the CPU brute-force command.
--min-length specifies the minimum password length and must not exceed
--length. It is optional and defaults to one.
-d, --device selects an indexed compute device. Device zero is the default.
Use --list-devices to print the available indices.
-S, --stats prints the selected device, search configuration, runtime,
estimated number of password candidates tested, and estimated password rate.
When supported by the selected compute queue, it also reports GPU-only compute
runtime and throughput using Vulkan timestamp queries. Candidate accounting is
performed once per GPU dispatch.
Debug builds started with ZC_LOG=debug also report implementation-provided
pipeline executable statistics when the Vulkan driver supports them. These can
include compiled instruction, register, scratch-memory, and subgroup details;
the exact fields are driver-specific.
GPU searches process up to 64 million candidates per dispatch, clamped to the selected device's compute workgroup limit. This amortizes command submission, fence waits, and result readback without changing search order. Within a dispatch, each shader invocation derives the keys for one password prefix and reuses them across every final-character candidate. The backend also creates and caches a compute pipeline specialized for each password length it encounters. Password length, character-set size, and ZIP header count become compile-time constants for that pipeline; if a driver rejects specialization, the search continues with the generic pipeline.
For example, list devices and search every lowercase password from six through eight characters on device zero:
yazc vulkan --list-devices
yazc vulkan -a --min-length=6 --length=8 --device=0 archive.zip
The command reports an error instead of silently falling back to the CPU when Vulkan support or the selected device is unavailable.
This mode tries every password from the given dictionary file. If no
dictionary file is provided, the program reads passwords from standard
input (stdin).
-d, --dictionary reads passwords from the specified file.
-S, --stats prints runtime statistics.
Try all passwords from words.dict:
cat words.dict | yazc dictionary archive.zip
yazc dictionary -d words.dict archive.zip
Use John the Ripper to generate more passwords:
john --wordlist=words.dict --rules --stdout | yazc dictionary archive.zip
This mode uses a known vulnerability in the PKZIP stream cipher to find the internal representation of the encryption key. Once the internal representation has been recovered, the program tries to find the actual password or an equivalent one.
Three methods are available for mapping plaintext bytes to ciphertext
bytes: file (-f), offset (-o), and ZIP entry (the default).
If no mapping option is given, the program reads the plaintext and ciphertext from ZIP archives. Provide the corresponding entry name from each archive. For example:
yazc plaintext notencrypted.zip file.exe encrypted.zip file.exe
-o, --offset uses offsets instead of ZIP entry names. This mode can
map plaintext bytes from anywhere in one file to ciphertext bytes in
another file. The number of mapped bytes must match. This option is
useful when only part of a ZIP entry can be used. The following example
tries to find the password for archive.zip by mapping bytes 100–650
of plain.bin to bytes 112–662 of archive.zip; the first ciphertext
byte is at offset 64:
yazc plaintext -o plain.bin 100 650 archive.zip 112 662 64
-f, --file uses plaintext bytes from plaintextfile and maps them to
bytes in cipherfile. The program assumes that the first 12 bytes of
cipherfile are the encryption header. Bytes that cannot be mapped are
ignored, which can happen when either file is shorter. For example:
yazc plaintext -f plaintextfile cipherfile
-i, --password-from-internal-rep finds a password from the provided
internal representation. See section 3.6 of the Biham and Kocher paper
for more information. For example:
yazc plaintext -i 0x777095c0 0xc1764180 0xf5d5b494
-p, --internal-rep-from-password calculates the internal representation
of a password. For example:
yazc plaintext -p pAssW0Rd
-t, --threads=N specifies the number of worker threads. Use
--threads=auto to select the number of online CPUs reported by
sysconf(_SC_NPROCESSORS_ONLN). This is the default.
-S, --stats prints runtime statistics.
The info subcommand lists the contents of a ZIP archive. It provides
information useful for plaintext and other attack modes. For example:
yazc info data/noradi.zip
Result:
INDEX NAME OFFSETS SIZE CSIZE ENCRYPTED HEADER
0 TEXT1.TXT 39 51 155 110 116 875dee36d843e98819faae48
1 TEXT2.TXT 194 206 302 99 108 4fa3648cd55cdbdc071bfae1
2 TEXT3.TXT 341 353 439 88 98 0d9507f1cd95d217c8cadb11
- The first column (INDEX) is the index of the file in the archive.
- The second column (NAME) is the name of the file taken from the ZIP header.
- The third column (OFFSETS) contains offsets useful for the plaintext
attack when using the
-ooption. The first number is the offset of the first byte of the encrypted header; the second is the offset of the first byte of the compressed data; and the third is the offset of the last byte of the compressed data. - The fourth column (SIZE) is the original file size in bytes.
- The fifth column (CSIZE) is the compressed file size including the encrypted header (always 12 bytes).
- The sixth column (ENCRYPTED HEADER) is the encrypted header.
This subcommand makes it easier to inspect the contents of ZIP archives.
Another tool you can use is zipinfo.
Run the CPU attack benchmarks with:
scripts/benchmark-attacks.sh
Set VULKAN_DEVICE to append the fixed-length Vulkan workload to the report:
VULKAN_DEVICE=0 scripts/benchmark-attacks.sh
For dedicated Vulkan compute measurements at password lengths 6, 7, and 8, run:
scripts/benchmark-vulkan.sh
The three workloads exhaust the lowercase search spaces of 308,915,776,
8,031,810,176, and 208,827,064,576 candidates, respectively. Their passwords
are all z characters, placing the match at the end of each search space.
The length-eight workload is intentionally long-running. Select a GPU and take
multiple samples with:
VULKAN_DEVICE=0 RUNS=3 scripts/benchmark-vulkan.sh
data/bruteforce-7char.zip is the stable brute-force workload. It contains
five traditionally encrypted entries and uses the password zzzzzzz, so an
alphabetic search through length seven tests all 8,353,082,582 candidates.
Use BRUTEFORCE_THREADS to select a fixed worker count when comparing builds:
BRUTEFORCE_THREADS=12 RUNS=3 scripts/benchmark-attacks.sh
The brute-force header-filter batch size defaults to 64 candidates. To compare
other sizes, define ZC_BRUTEFORCE_BATCH_SIZE while configuring the build. The
supported range is 1 through 1024:
CPPFLAGS=-DZC_BRUTEFORCE_BATCH_SIZE=32 ./configure
Distributed under the GPLv3+ license. See COPYING for more information.
Marc Ferland - marc.ferland@gmail.com