• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kimci86 / bkcrack / 18017127585

25 Sep 2025 06:10PM UTC coverage: 77.156% (-0.05%) from 77.205%
18017127585

push

github

kimci86
Update -r option example in readme for consistency

1557 of 2018 relevant lines covered (77.16%)

1651578.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/src/main.cpp
1
#include "Arguments.hpp"
2
#include "Attack.hpp"
3
#include "ConsoleProgress.hpp"
4
#include "Data.hpp"
5
#include "SigintHandler.hpp"
6
#include "VirtualTerminalSupport.hpp"
7
#include "Zip.hpp"
8
#include "Zreduction.hpp"
9
#include "file.hpp"
10
#include "log.hpp"
11
#include "password.hpp"
12
#include "version.hpp"
13

14
#include <cassert>
15
#include <iomanip>
16
#include <limits>
17

18
namespace
19
{
20

21
const char* const usage = R"_(usage: bkcrack [options]
22
Crack legacy zip encryption with Biham and Kocher's known plaintext attack.
23

24
Options to get the internal password representation:
25
 -c, --cipher-file <file>    Zip entry or file on disk containing ciphertext
26
     --cipher-index <index>  Index of the zip entry containing ciphertext
27
 -C, --cipher-zip <archive>  Zip archive containing the ciphertext entry
28

29
 -p, --plain-file <file>     Zip entry or file on disk containing plaintext
30
     --plain-index <index>   Index of the zip entry containing plaintext
31
 -P, --plain-zip <archive>   Zip archive containing the plaintext entry
32
 -t, --truncate <size>       Maximum number of bytes of plaintext to load
33
 -o, --offset <offset>       Known plaintext offset relative to ciphertext
34
                              without encryption header (may be negative)
35
 -x, --extra <offset> <data> Additional plaintext in hexadecimal starting
36
                              at the given offset (may be negative)
37
     --ignore-check-byte     Do not automatically use ciphertext's check byte
38
                              as known plaintext
39

40
     --continue-attack <checkpoint>
41
        Starting point of the attack. Useful to continue a previous
42
        non-exhaustive or interrupted attack.
43

44
     --password <password>
45
        Password from which to derive the internal password representation.
46
        Useful for testing purposes and advanced scenarios such as reverting
47
        the effect of the --change-password command.
48

49
Options to use the internal password representation:
50
 -k, --keys <X> <Y> <Z>      Internal password representation as three 32-bits
51
                              integers in hexadecimal (requires -d, -D, -U,
52
                              --change-keys, --bruteforce or --mask)
53

54
 -d, --decipher <file>       File to write the deciphered data (requires -c)
55
     --keep-header           Write the encryption header at the beginning of
56
                              deciphered data instead of discarding it
57

58
 -D, --decrypt <archive>
59
        Create a copy of the encrypted zip archive with deciphered entries,
60
        removing the password protection (requires -C)
61

62
 -U, --change-password <archive> <password>
63
        Create a copy of the encrypted zip archive with the password set to the
64
        given new password (requires -C)
65

66
     --change-keys <archive> <X> <Y> <Z>
67
        Create a copy of the encrypted zip archive using the given new internal
68
        password representation (requires -C)
69

70
 -b, --bruteforce <charset>
71
        Try to recover the password or an equivalent one by generating and
72
        testing password candidates using characters in the given charset.
73
        The charset is a sequence of characters or shortcuts for predefined
74
        charsets listed below. Example: ?l?d-.@
75

76
          ?l lowercase letters              abcdefghijklmnopqrstuvwxyz
77
          ?u uppercase letters              ABCDEFGHIJKLMNOPQRSTUVWXYZ
78
          ?d decimal digits                 0123456789
79
          ?s special characters              !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
80
          ?a alpha-numerical characters     (same as ?l?u?d)
81
          ?p printable ASCII characters     (same as ?l?u?d?s)
82
          ?b all bytes                      (0x00 - 0xff)
83

84
 -l, --length [ <min>..<max> | <min>.. | ..<max> | <length> ]
85
        Length interval or exact length of password candidates to generate and
86
        test during password recovery (requires --bruteforce)
87

88
 -r, --recover-password [ <min>..<max> | <min>.. | ..<max> | <max> ] <charset>
89
        Shortcut for --length and --bruteforce options
90

91
 -m, --mask <mask>
92
        Try to recover the password or an equivalent one by generating and
93
        testing password candidates following the given mask.
94
        The mask is a sequence of fixed characters or character sets (either
95
        predefined or custom charsets). Example: -m ?u?l?l?l?l-?d?d?d?d
96

97
 -s, --charset <identifier> <charset>
98
        Define a custom character set. Example: -s h ?dabcdef
99

100
     --continue-recovery <checkpoint>
101
        Starting point of the password recovery. Useful to continue a previous
102
        non-exhaustive or interrupted password recovery.
103

104
Other options:
105
 -j, --jobs <count>          Number of threads to use for parallel operations
106
 -e, --exhaustive            Exhaustively look for all solutions (keys or
107
                              passwords) instead of stopping after the first
108
                              solution is found
109
 -L, --list <archive>        List entries in a zip archive and exit
110
     --version               Show version information and exit
111
 -h, --help                  Show this help and exit)_";
112

113
auto getCompressionDescription(Zip::Compression compression) -> std::string;
114
void listEntries(const std::string& archiveFilename);
115

116
} // namespace
117

118
auto main(int argc, const char* argv[]) -> int
31✔
119
try
120
{
121
    // enable virtual terminal support on Windows, no-op on other platforms
122
    const auto vtSupport = VirtualTerminalSupport{};
31✔
123

124
    // version information
125
    std::cout << "bkcrack " << bkcrackVersion << " - " << bkcrackVersionDate << std::endl;
31✔
126

127
    const auto args = Arguments{argc, argv};
31✔
128
    if (args.help)
29✔
129
    {
130
        std::cout << usage << std::endl;
1✔
131
        return 0;
1✔
132
    }
133

134
    if (args.version)
28✔
135
    {
136
        // version information was already printed, nothing else to do
137
        return 0;
1✔
138
    }
139

140
    if (args.infoArchive)
27✔
141
    {
142
        listEntries(*args.infoArchive);
1✔
143
        return 0;
1✔
144
    }
145

146
    auto keysvec = std::vector<Keys>{};
26✔
147
    if (args.keys)
26✔
148
        keysvec.push_back(*args.keys);
20✔
149
    else if (args.password)
6✔
150
    {
151
        keysvec.emplace_back(*args.password);
5✔
152
        std::cout << "Internal representation for password \"" << *args.password << "\": " << keysvec.back()
5✔
153
                  << std::endl;
5✔
154
    }
155
    else
156
    // find keys from known plaintext
157
    {
158
        const auto data = args.loadData();
1✔
159

160
        // generate and reduce Zi[10,32) values
161
        auto zr = Zreduction{data.keystream};
1✔
162
        if (data.keystream.size() > Attack::contiguousSize)
1✔
163
        {
164
            std::cout << "[" << put_time << "] Z reduction using " << (data.keystream.size() - Attack::contiguousSize)
1✔
165
                      << " bytes of known plaintext" << std::endl;
1✔
166

167
            auto progress = ConsoleProgress{std::cout};
1✔
168
            zr.reduce(progress);
1✔
169
        }
1✔
170

171
        // generate Zi[2,32) values
172
        zr.generate();
1✔
173

174
        // carry out the attack on the remaining Zi[2,32) values
175
        std::cout << "[" << put_time << "] Attack on " << zr.getCandidates().size() << " Z values at index "
1✔
176
                  << (static_cast<int>(data.offset + zr.getIndex()) - static_cast<int>(Data::encryptionHeaderSize))
1✔
177
                  << std::endl;
1✔
178

179
        const auto [state, restart] = [&]() -> std::pair<Progress::State, int>
1✔
180
        {
181
            auto       start         = args.attackStart;
1✔
182
            auto       progress      = ConsoleProgress{std::cout};
1✔
183
            const auto sigintHandler = SigintHandler{progress.state};
1✔
184
            keysvec = attack(data, zr.getCandidates(), start, zr.getIndex(), args.jobs, args.exhaustive, progress);
1✔
185
            return {progress.state, start};
2✔
186
        }();
2✔
187

188
        if (state != Progress::State::Normal)
1✔
189
        {
190
            if (state == Progress::State::Canceled)
1✔
191
                std::cout << "Operation interrupted by user." << std::endl;
×
192
            else if (state == Progress::State::EarlyExit)
1✔
193
                std::cout << "Found a solution. Stopping." << std::endl;
1✔
194

195
            if (restart < static_cast<int>(zr.getCandidates().size()))
1✔
196
                std::cout << "You may resume the attack with the option: --continue-attack " << restart << std::endl;
1✔
197
        }
198

199
        // print the keys
200
        std::cout << "[" << put_time << "] ";
1✔
201
        if (keysvec.empty())
1✔
202
        {
203
            std::cout << "Could not find the keys." << std::endl;
×
204
            return 1;
×
205
        }
206
        else
207
        {
208
            std::cout << "Keys" << std::endl;
1✔
209
            for (const auto& keys : keysvec)
2✔
210
                std::cout << keys << std::endl;
1✔
211
        }
212
    }
1✔
213

214
    // From there, keysvec is not empty.
215

216
    const auto keys = keysvec.front();
26✔
217
    if ((args.decipheredFile || args.changePassword || args.changeKeys || args.bruteforce) && keysvec.size() > 1)
26✔
218
        std::cout << "Continuing with keys " << keys << "\n"
×
219
                  << "Use the command line option -k to provide other keys." << std::endl;
×
220

221
    // decipher
222
    if (args.decipheredFile)
26✔
223
    {
224
        std::cout << "[" << put_time << "] Writing deciphered data " << *args.decipheredFile;
1✔
225
        if (args.keepHeader)
1✔
226
            std::cout << " with encryption header";
×
227
        std::cout << std::endl;
1✔
228

229
        auto compression = std::optional<Zip::Compression>{};
1✔
230

231
        {
232
            auto cipherstream = openInput(args.cipherArchive ? *args.cipherArchive : *args.cipherFile);
1✔
233
            auto ciphersize   = std::numeric_limits<std::size_t>::max();
1✔
234

235
            if (args.cipherArchive)
1✔
236
            {
237
                const auto archive = Zip{cipherstream};
1✔
238
                const auto entry   = args.cipherFile ? archive[*args.cipherFile] : archive[*args.cipherIndex];
1✔
239
                Zip::checkEncryption(entry, Zip::Encryption::Traditional);
1✔
240
                compression = entry.compression;
1✔
241

242
                archive.seek(entry);
1✔
243
                ciphersize = entry.packedSize;
1✔
244
            }
1✔
245

246
            auto decipheredstream = openOutput(*args.decipheredFile);
1✔
247

248
            decipher(cipherstream, ciphersize,
1✔
249
                     args.keepHeader ? 0 : static_cast<std::size_t>(Data::encryptionHeaderSize), decipheredstream,
1✔
250
                     keys);
251
        }
1✔
252

253
        std::cout << "Wrote deciphered data";
1✔
254
        if (compression)
1✔
255
        {
256
            if (compression == Zip::Compression::Store)
1✔
257
                std::cout << " (not compressed)";
×
258
            else
259
                std::cout << " (compressed with " << getCompressionDescription(*compression) << " method)";
1✔
260
        }
261
        std::cout << "." << std::endl;
1✔
262
    }
263

264
    // decrypt
265
    if (args.decryptedArchive)
26✔
266
    {
267
        std::cout << "[" << put_time << "] Writing decrypted archive " << *args.decryptedArchive << std::endl;
1✔
268

269
        auto stream    = openInput(*args.cipherArchive);
1✔
270
        auto encrypted = Zip{stream};
1✔
271
        auto decrypted = openOutput(*args.decryptedArchive);
1✔
272

273
        auto progress = ConsoleProgress{std::cout};
1✔
274
        encrypted.decrypt(decrypted, keys, progress);
1✔
275
    }
1✔
276

277
    // unlock
278
    if (args.changePassword)
26✔
279
    {
280
        const auto& [unlockedArchive, newPassword] = *args.changePassword;
1✔
281

282
        std::cout << "[" << put_time << "] Writing unlocked archive " << unlockedArchive << " with password \""
1✔
283
                  << newPassword << "\"" << std::endl;
1✔
284

285
        {
286
            auto       stream   = openInput(*args.cipherArchive);
1✔
287
            const auto archive  = Zip{stream};
1✔
288
            auto       unlocked = openOutput(unlockedArchive);
1✔
289

290
            auto progress = ConsoleProgress{std::cout};
1✔
291
            archive.changeKeys(unlocked, keys, Keys{newPassword}, progress);
1✔
292
        }
1✔
293

294
        std::cout << "Wrote unlocked archive." << std::endl;
1✔
295
    }
296

297
    if (args.changeKeys)
26✔
298
    {
299
        const auto& [unlockedArchive, newKeys] = *args.changeKeys;
1✔
300

301
        std::cout << "[" << put_time << "] Writing unlocked archive " << unlockedArchive << " with keys " << newKeys
1✔
302
                  << std::endl;
1✔
303

304
        {
305
            auto       stream   = openInput(*args.cipherArchive);
1✔
306
            const auto archive  = Zip{stream};
1✔
307
            auto       unlocked = openOutput(unlockedArchive);
1✔
308

309
            auto progress = ConsoleProgress{std::cout};
1✔
310
            archive.changeKeys(unlocked, keys, newKeys, progress);
1✔
311
        }
1✔
312

313
        std::cout << "Wrote unlocked archive." << std::endl;
1✔
314
    }
315

316
    // recover password
317
    if (args.bruteforce || args.mask)
26✔
318
    {
319
        std::cout << "[" << put_time << "] Recovering password" << std::endl;
20✔
320

321
        auto passwords = std::vector<std::string>{};
20✔
322

323
        const auto [state, restart] = [&]() -> std::pair<Progress::State, std::string>
20✔
324
        {
325
            auto       start         = args.recoveryStart;
20✔
326
            auto       progress      = ConsoleProgress{std::cout};
20✔
327
            const auto sigintHandler = SigintHandler{progress.state};
20✔
328

329
            if (args.bruteforce)
20✔
330
            {
331
                const auto& charset                = *args.bruteforce;
10✔
332
                const auto& [minLength, maxLength] = args.length.value_or(Arguments::LengthInterval{});
10✔
333
                passwords = recoverPassword(keysvec.front(), charset, minLength, maxLength, start, args.jobs,
20✔
334
                                            args.exhaustive, progress);
20✔
335
            }
336
            else
337
                passwords = recoverPassword(keysvec.front(), *args.mask, start, args.jobs, args.exhaustive, progress);
10✔
338

339
            return {progress.state, start};
40✔
340
        }();
40✔
341

342
        if (state != Progress::State::Normal)
20✔
343
        {
344
            if (state == Progress::State::Canceled)
20✔
345
                std::cout << "Operation interrupted by user." << std::endl;
×
346
            else if (state == Progress::State::EarlyExit)
20✔
347
                std::cout << "Found a solution. Stopping." << std::endl;
20✔
348

349
            if (!restart.empty())
20✔
350
            {
351
                const auto flagsBefore = std::cout.setf(std::ios::hex, std::ios::basefield);
4✔
352
                const auto fillBefore  = std::cout.fill('0');
4✔
353

354
                std::cout << "You may resume the password recovery with the option: --continue-recovery ";
4✔
355
                for (const auto c : restart)
41✔
356
                    std::cout << std::setw(2) << int{static_cast<std::uint8_t>(c)};
37✔
357
                std::cout << std::endl;
4✔
358

359
                std::cout.fill(fillBefore);
4✔
360
                std::cout.flags(flagsBefore);
4✔
361
            }
362
        }
363

364
        std::cout << "[" << put_time << "] ";
20✔
365
        if (passwords.empty())
20✔
366
        {
367
            std::cout << "Could not recover password" << std::endl;
×
368
            return 1;
×
369
        }
370
        else
371
        {
372
            std::cout << "Password" << std::endl;
20✔
373

374
            const auto flagsBefore = std::cout.setf(std::ios::hex, std::ios::basefield);
20✔
375
            const auto fillBefore  = std::cout.fill('0');
20✔
376

377
            for (const auto& password : passwords)
40✔
378
            {
379
                std::cout << "as bytes:";
20✔
380
                for (const auto c : password)
239✔
381
                    std::cout << ' ' << std::setw(2) << int{static_cast<std::uint8_t>(c)};
219✔
382
                std::cout << std::endl;
20✔
383
                std::cout << "as text: " << password << std::endl;
20✔
384
            }
385

386
            std::cout.fill(fillBefore);
20✔
387
            std::cout.flags(flagsBefore);
20✔
388
        }
389
    }
20✔
390

391
    return 0;
26✔
392
}
31✔
393
catch (const Arguments::Error& e)
2✔
394
{
395
    std::cout << e.what() << std::endl;
2✔
396
    std::cout << "Run 'bkcrack -h' for help." << std::endl;
2✔
397
    return 1;
2✔
398
}
2✔
399
catch (const BaseError& e)
×
400
{
401
    std::cout << e.what() << std::endl;
×
402
    return 1;
×
403
}
×
404

405
namespace
406
{
407

408
auto getEncryptionDescription(Zip::Encryption encryption) -> std::string
2✔
409
{
410
    switch (encryption)
2✔
411
    {
412
    case Zip::Encryption::None:
×
413
        return "None";
×
414
    case Zip::Encryption::Traditional:
2✔
415
        return "ZipCrypto";
4✔
416
    case Zip::Encryption::Unsupported:
×
417
        return "Other";
×
418
    }
419
    assert(false);
×
420

421
    return "";
422
}
423

424
auto getCompressionDescription(Zip::Compression compression) -> std::string
3✔
425
{
426
    switch (compression)
3✔
427
    {
428
#define CASE(c)                                                                                                        \
429
    case Zip::Compression::c:                                                                                          \
430
        return #c
431
        CASE(Store);
3✔
432
        CASE(Shrink);
×
433
        CASE(Implode);
×
434
        CASE(Deflate);
6✔
435
        CASE(Deflate64);
×
436
        CASE(BZip2);
×
437
        CASE(LZMA);
×
438
        CASE(Zstandard);
×
439
        CASE(MP3);
×
440
        CASE(XZ);
×
441
        CASE(JPEG);
×
442
        CASE(WavPack);
×
443
        CASE(PPMd);
×
444
#undef CASE
445
    }
446

447
    return "Other (" + std::to_string(static_cast<int>(compression)) + ")";
×
448
}
449

450
void listEntries(const std::string& archiveFilename)
1✔
451
{
452
    auto       stream  = openInput(archiveFilename);
1✔
453
    const auto archive = Zip{stream};
1✔
454

455
    std::cout << "Archive: " << archiveFilename << "\n"
456
              << "Index Encryption Compression CRC32    Uncompressed  Packed size Name\n"
457
                 "----- ---------- ----------- -------- ------------ ------------ ----------------\n";
1✔
458

459
    const auto flagsBefore =
460
        std::cout.setf(std::ios::right | std::ios::dec, std::ios::adjustfield | std::ios::basefield);
1✔
461
    const auto fillBefore = std::cout.fill(' ');
1✔
462

463
    auto index = std::size_t{};
1✔
464
    for (const auto& entry : archive)
3✔
465
    {
466
        // clang-format off
467
        std::cout << std::setw(5) << index++ << ' '
2✔
468

469
                  << std::left
2✔
470
                  << std::setw(10) << getEncryptionDescription(entry.encryption) << ' '
4✔
471
                  << std::setw(11) << getCompressionDescription(entry.compression) << ' '
6✔
472
                  << std::right
2✔
473

474
                  << std::setfill('0') << std::hex
2✔
475
                  << std::setw(8) << entry.crc32 << ' '
2✔
476
                  << std::setfill(' ') << std::dec
2✔
477

478
                  << std::setw(12) << entry.uncompressedSize << ' '
2✔
479
                  << std::setw(12) << entry.packedSize << ' '
2✔
480
                  << entry.name << '\n';
2✔
481
        // clang-format on
482
    }
1✔
483

484
    std::cout.fill(fillBefore);
1✔
485
    std::cout.flags(flagsBefore);
1✔
486
}
1✔
487

488
} // namespace
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc