zipcrypt

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 21, 2026 License: MIT Imports: 19 Imported by: 0

README

zipcrypt

Go Reference CI

Password-protected ZIP archives for Go, on top of the standard archive/zip.

go get github.com/creatorKoo/zipcrypt

archive/zip cannot encrypt or decrypt, and adding that was proposed and declined (golang/go#12081). The packages that can, such as alexmullins/zip and yeka/zip, are copies of an old archive/zip that no longer receive its fixes.

zipcrypt does not copy archive/zip. It wraps a *zip.Writer and adds the encryption layer through Writer.RegisterCompressor, so headers, ZIP64 and the central directory stay with the standard library. It has no dependencies and needs Go 1.24 or later.

Writing

w := zipcrypt.NewWriter(out)

fh := &zip.FileHeader{Name: "report.pdf", Method: zip.Deflate}
fh.SetMode(0o644)
fw, err := w.CreateWith(fh, zipcrypt.Options{
	Method:   zipcrypt.AES256,
	Password: "secret",
})
if err != nil {
	return err
}
if _, err := io.Copy(fw, report); err != nil {
	return err
}

return w.Close()

Writer embeds the *zip.Writer. Entries added through it (w.Create, w.CreateHeader) are written unencrypted, exactly as archive/zip writes them.

Reading

r, err := zip.OpenReader("archive.zip")
if err != nil {
	return err
}
defer r.Close()
for _, f := range r.File {
	rc, err := zipcrypt.Open(f, "secret") // unencrypted entries open as with f.Open
	if err != nil {
		return err
	}
	// read rc, then
	rc.Close()
}

Encryption methods

Method Supported by Notes
ZipCrypto Windows Explorer, macOS Archive Utility and practically every ZIP tool Weak: it falls to known-plaintext attacks. Use it when the recipient cannot install anything.
AES128, AES192, AES256 7-Zip, WinZip, WinRAR, libarchive and others WinZip AES, AE-1 format

Options

  • Level is the flate level for Deflate entries. Zero means 5, the level archive/zip uses.
  • NoCompression writes Deflate entries as uncompressed blocks, for files that are already compressed, such as JPEG, MP4 or ZIP. It is as fast as Store, but unlike Store the entry ends itself, so readers that scan an archive from the front, such as macOS ditto, cannot cut it short at a stray signature inside the data.

Compatibility

CI checks archives written by zipcrypt with Info-ZIP unzip, Python's zipfile, libarchive (bsdtar), 7-Zip and yeka/zip, and checks that zipcrypt reads what yeka/zip writes. See interop.

Things to know:

  • AES entries use AE-1, which keeps the CRC-32 of the plain data. AE-2 sets it to 0 so that it cannot leak information about small files, but archive/zip always writes the CRC-32, so zipcrypt cannot produce AE-2. Open reads both.
  • Call FileHeader.SetMode, or build the header with zip.FileInfoHeader. Without it archive/zip records MS-DOS as the creating system, and some Unix tools, such as Info-ZIP unzip on macOS, then decode non-ASCII names as an old DOS code page.
  • Open returns decrypted data as it reads. Altered AES data is reported as ErrAuthentication at the end, after the bytes have been returned.

Performance

Writing one 64 MiB entry as uncompressed Deflate blocks on an Apple silicon Mac (go test -bench .):

Method Throughput
None 8.5 GB/s
AES256 1.1 GB/s
ZipCrypto 0.5 GB/s

ZipCrypto has to process one byte at a time.

License

MIT

Documentation

Overview

Package zipcrypt adds password protection to archive/zip.

It does not fork archive/zip. Writer wraps a *zip.Writer and registers its own compressors on it; for entries created with Writer.CreateWith they compress at the requested level and then encrypt. Everything else, such as headers, CRC-32, data descriptors, ZIP64 and the central directory, is left to archive/zip. Open decrypts entries read with zip.Reader.

Two encryption methods are supported:

  • ZipCrypto, the traditional PKWARE encryption. It is cryptographically weak, but it is the only method that Windows Explorer and macOS Archive Utility open without extra software.
  • WinZip AES (AE-1) with 128, 192 or 256-bit keys, which 7-Zip, WinZip, WinRAR and similar tools open.
Example
package main

import (
	"archive/zip"
	"bytes"
	"fmt"
	"io"
	"log"

	"github.com/creatorKoo/zipcrypt"
)

func main() {
	// Write an archive with one AES-256 encrypted file.
	var buf bytes.Buffer
	w := zipcrypt.NewWriter(&buf)
	fw, err := w.CreateWith(&zip.FileHeader{Name: "secret.txt", Method: zip.Deflate},
		zipcrypt.Options{Method: zipcrypt.AES256, Password: "golang"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Fprint(fw, "Hello, Gophers!")
	if err := w.Close(); err != nil {
		log.Fatal(err)
	}

	// Read it back with archive/zip and zipcrypt.Open.
	r, err := zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
	if err != nil {
		log.Fatal(err)
	}
	rc, err := zipcrypt.Open(r.File[0], "golang")
	if err != nil {
		log.Fatal(err)
	}
	defer rc.Close()
	b, err := io.ReadAll(rc)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}
Output:
Hello, Gophers!

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrAuthentication = errors.New("zipcrypt: authentication failed")

ErrAuthentication is returned at the end of an AES entry whose data does not match its authentication code.

View Source
var ErrPassword = errors.New("zipcrypt: wrong password")

ErrPassword is returned when the password does not match an entry.

Functions

func Open

func Open(f *zip.File, password string) (io.ReadCloser, error)

Open returns a ReadCloser for the contents of f, decrypting it with password if f is encrypted. Unencrypted entries are opened with f.Open and the password is ignored.

A wrong password is usually reported as ErrPassword. ZipCrypto checks a single byte, so about 1 wrong password in 256 gets past that check; the data then fails to decompress or to match its CRC-32 instead. AES entries report altered data as ErrAuthentication once the end is read, after the decrypted bytes have been returned, as any streaming reader must.

Types

type Method

type Method int

Method selects how an entry is encrypted.

const (
	// None writes the entry unencrypted.
	None Method = iota
	// ZipCrypto is the traditional PKWARE encryption. It is weak, but it is
	// the only method Windows Explorer and macOS Archive Utility can open.
	ZipCrypto
	// AES128, AES192 and AES256 are WinZip AES encryption (AE-1). They need
	// 7-Zip, WinZip, WinRAR or a similar tool to open.
	AES128
	AES192
	AES256
)

func (Method) String

func (m Method) String() string

type Options

type Options struct {
	// Method selects the encryption. The zero value, None, writes the entry
	// unencrypted.
	Method Method

	// Password encrypts the entry. It is required unless Method is None, and
	// must be empty if Method is None.
	Password string

	// Level is the flate level for Deflate entries, as for flate.NewWriter.
	// Zero means 5, the level archive/zip uses.
	Level int

	// NoCompression writes Deflate entries as uncompressed blocks, for data
	// that is already compressed, such as JPEG or MP4. It is as fast as Store
	// and adds 5 bytes per 64 KiB, but unlike Store the entry ends itself:
	// readers that scan an archive from the front, such as macOS ditto, cannot
	// mistake bytes inside it for the end of the entry.
	NoCompression bool
}

Options describes how one entry is written.

Example (NoCompression)

Files that are already compressed, such as photos and videos, gain almost nothing from Deflate. NoCompression writes them at disk speed.

package main

import (
	"archive/zip"
	"bytes"
	"log"

	"github.com/creatorKoo/zipcrypt"
)

func main() {
	var buf bytes.Buffer
	w := zipcrypt.NewWriter(&buf)
	fw, err := w.CreateWith(&zip.FileHeader{Name: "photo.jpg", Method: zip.Deflate},
		zipcrypt.Options{Method: zipcrypt.ZipCrypto, Password: "golang", NoCompression: true})
	if err != nil {
		log.Fatal(err)
	}
	fw.Write([]byte{0xff, 0xd8, 0xff, 0xe0}) // the JPEG data
	if err := w.Close(); err != nil {
		log.Fatal(err)
	}
}

type Writer

type Writer struct {
	*zip.Writer
	// contains filtered or unexported fields
}

Writer is a zip.Writer that can also write encrypted entries. Entries added through the embedded zip.Writer are written unencrypted, as archive/zip would write them.

func NewWriter

func NewWriter(w io.Writer) *Writer

NewWriter returns a Writer writing a zip file to w.

It registers compressors for zip.Store, zip.Deflate and method 99 (AES) on the embedded zip.Writer. Registering another compressor for those methods on it disables encryption for them.

func (*Writer) CreateWith

func (w *Writer) CreateWith(fh *zip.FileHeader, opt Options) (io.Writer, error)

CreateWith adds a file to the archive like zip.Writer.CreateHeader and compresses and encrypts it as opt says. fh.Method must be zip.Store or zip.Deflate. Directories are never encrypted. As with CreateHeader, the file's contents must be written before the next call to CreateWith, CreateHeader or Close, and fh must not be modified or reused afterwards.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL