runlocally

runlocally engineering notes

Unlock ZIP

How Unlock ZIP is built

By Geppetto · · Open Unlock ZIP →

These are the engineering notes for Unlock ZIP: the technologies it is built on, what each one is, and how it is used in the tool.

Tech used

What this removes: a password you already know

The tool takes an encrypted ZIP and the password that opens it, and writes back an identical archive with the encryption stripped. That is a decrypt-then-re-pack, not an attack: it is not a password cracker and does not brute-force anything. Without the correct password there is no output. The encryption in question is the WinZip AES scheme from the Encrypt ZIP notes — a per-file salt, a PBKDF2-derived key, AES-CTR data, and an HMAC authentication code — and it is that authentication code that lets a wrong password be detected instead of yielding garbage.

Decrypting with @zip.js/zip.js: ZipReader({ password })

The input is opened with the @zip.js/zip.js ZipReader (from the Unzip notes). getEntries() reads the central directory and counts how many entries are encrypted; if none are, there is nothing to unlock. For each encrypted file entry, entry.getData(new BlobWriter(), { password }) decrypts it in memory — zip.js checks the password against the entry’s authentication data as it decrypts, so a bad password fails here rather than producing plausible-looking bytes.

Re-packing with a passwordless ZipWriter

Each decrypted entry is streamed straight into a ZipWriter(new BlobWriter('application/zip'), { useUnicodeFileNames: true }) opened with no password, so the output carries the same contents with the encryption gone. Directory entries are re-added as directories, every entry keeps its lastModDate, and the UTF-8 filename flag (bit 11, from the Create ZIP notes) is set — so folder structure, timestamps, and non-ASCII names all survive the round trip. The password and the file never leave the page.

Shell

Same static Astro + Preact island and Service-Worker PWA shell as the other tools (see the HEIC notes).

Implementation & operational notes

Wrong password vs. not-encrypted are reported distinctly. A wrong password surfaces from zip.js as ERR_INVALID_PASSWORD (and ERR_ENCRYPTED, raised when an encrypted entry is read with an empty password) — both are re-classified into a single WrongPasswordError. An archive with no encrypted entries throws NotEncryptedError before any writing starts. The two cases map to different messages, so “the password is wrong” is never confused with “there was nothing to unlock.”

The half-built output is discarded on failure. Decryption and re-packing happen in one pass; if any entry fails mid-stream, the partially written ZipWriter is closed and thrown away, so a wrong password can never leak a truncated, partly-decrypted archive.

Both cipher families are handled. WinZip AES verifies the password via its authentication code; the legacy ZipCrypto scheme verifies via a check byte. zip.js surfaces both as the same invalid-password error, so the tool unlocks either kind of protected entry with the same code path.

Try it / source