runlocally

runlocally engineering notes

Encrypt ZIP

How Encrypt ZIP is built

By Geppetto · · Open Encrypt ZIP →

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

Tech used

WinZip AES vs. legacy ZipCrypto

A ZIP can carry entry data under two very different encryption schemes. The original one, ZipCrypto, is a stream cipher keyed from the password with a 12-byte header per entry; it is decades old and vulnerable to known-plaintext attacks, so it is not used here. The modern one, WinZip AES, is the standardized AES scheme most current tools implement. Per entry it derives a key from the password with PBKDF2 (HMAC-SHA1) over a random per-file salt, encrypts the data with AES in CTR mode, and appends an HMAC authentication code so tampering or a wrong password is detectable rather than silently producing junk. encryptionStrength: 3 selects a 256-bit key — AES-256 — with a 16-byte salt. One detail that matters downstream: WinZip AES encrypts the entry data, not the file names, so the central directory stays readable (which is why Unzip can list a locked archive without the password).

@zip.js/zip.js: ZipWriter with a password

The archive is built with the @zip.js/zip.js ZipWriter (introduced in the Create ZIP notes) wrapped around a BlobWriter('application/zip'). Passing { password, encryptionStrength: 3 } turns on WinZip AES-256 for every entry; each input File is streamed in with writer.add(name, new BlobReader(file)), and writer.close() resolves to the finished archive as one Blob. The password is held only in that writer instance — it is never sent anywhere; the whole archive is assembled locally and downloaded from an object URL.

UTF-8 filenames (general-purpose bit 11)

useUnicodeFileNames: true is kept explicit so bit 11 — the UTF-8 language-encoding flag from the Create ZIP notes — is set on every entry, and non-ASCII names (Japanese, emoji) extract correctly on Windows instead of turning into mojibake. Because AES leaves names in cleartext, this flag still applies to an encrypted archive.

Shell

Same static Astro + Preact island and Service-Worker PWA shell as the other tools (see the HEIC notes); zip.js runs the AES encryption and deflate in the Web Workers described in the Create ZIP notes.

Implementation & operational notes

AES zips need a compatible extractor. WinZip AES is widely but not universally supported. The Windows built-in “Extract All” (Explorer’s compressed-folder handler) has historically not opened AES-encrypted zips; 7-Zip, Keka, WinZip, and most modern archivers do. The tool produces a standard AES zip; the constraint is on the reader side, not the file.

The password is unrecoverable. There is no escrow and no recovery path — the key is derived from the password alone. A forgotten password means the data cannot be retrieved from the archive. An empty password is rejected up front, since it would produce an archive that is not actually protected.

Folder paths and duplicate names. A dropped folder keeps each file’s webkitRelativePath as its entry name, so the directory tree is preserved inside the archive; duplicate names are disambiguated as a.txt, a (1).txt, a (2).txt, with the suffix placed before the extension.

Try it / source