Why ZIP file names break: CP437 and the UTF-8 flag

Why a file named 한글.txt comes out of a ZIP as ╟╤▒█.txt: the CP437 default in PKWARE's APPNOTE, general purpose bit 11, the Info-ZIP 0x7075 field, and how Python and .NET read names.

DevelopmentEncodingFile formats

Unzip an archive made on a Korean or Japanese Windows machine somewhere else, and 한글.txt may come out as ╟╤▒█.txt. The compressed data is fine. The break happens when the file name is read, and the cause is that a ZIP file-name field may say nothing about its encoding.

A file name is just bytes

The ZIP specification is PKWARE’s APPNOTE.TXT. This post follows version 6.3.10, revised in November 2022. Each file’s name is stored as bytes in its local file header and again in its central directory header. A two-byte general purpose bit flag in the same header tells a reader how to interpret those bytes:

  • If bit 11 (Language encoding flag, EFS) is set, the file name and comment MUST be UTF-8.
  • If it is clear, the name and comment SHOULD follow the original ZIP character encoding, which Appendix D describes as IBM PC code page 437 (CP437).

APPNOTE’s revision history shows that Unicode file-name storage was added in 6.3.0, dated September 2006. Appendix D says that ZIP previously supported only CP437, so names in other languages could not be stored properly.

Same bytes, different names

CP437 has no Hangul and no kana. The Python zipfile documentation says these archives come from “legacy implementations which produce archives with names in the current locale encoding or code page (mostly on Windows).” Windows code page 949 is used for Korean and 932 for Japanese. Names written this way still have bit 11 clear, so the specification’s default reading is CP437. The result is the wrong set of characters.

Readers do not all behave alike:

  • Python zipfile decodes names with the flag clear as CP437. Since Python 3.11, metadata_encoding lets the caller select another codec when reading. If the flag is set, the flag takes precedence over this setting.
  • .NET ZipArchive is documented to decode names with bit 11 clear using the current system default code page when entryNameEncoding is null. This statement covers only that documented case. The details can vary by implementation and version, so check the documentation for the version you use.

The same file can therefore show different names depending on the library and machine that opens it.

The following example places a CP949 name in a ZIP whose bit 11 is clear, then reads it with Python. The name appears once in the local header and once in the central directory, so replace changes both copies.

import io
import zipfile

buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as z:
    z.writestr("XXXX.txt", b"hi")  # ASCII name, so bit 11 stays clear

data = buf.getvalue().replace(
    b"XXXX.txt",
    "한글.txt".encode("cp949"),
)

print(zipfile.ZipFile(io.BytesIO(data)).namelist())
print(
    zipfile.ZipFile(
        io.BytesIO(data),
        metadata_encoding="cp949",
    ).namelist()
)

Execution output:

['╟╤▒█.txt']
['한글.txt']

This was run with Python 3.14.7 on macOS 15.6.1 arm64.

The CP949 bytes for 한글 are C7 D1 B1 DB. CP437 maps those bytes to ╟ ╤ ▒ █. The data is not damaged. The reader used the wrong character table for the name.

0x7075: a UTF-8 name next to the header name

APPNOTE also documents the Info-ZIP Unicode Path extra field, whose header ID is 0x7075. The header’s name field keeps the legacy encoding while the extra field carries a UTF-8 copy of the name.

The extra field includes the CRC-32 of the header name field. If another tool renames the entry without updating the extra field and the CRC no longer matches, the extra field SHOULD be ignored and the header name used.

Whether a writer stores file names with bit 11 or with 0x7075 is up to the implementation. Readers SHOULD support both filename-storage methods.

APPNOTE describes the trade-off. Bit 11 takes less space, but older ZIP programs may fail to extract the files. 0x7075 takes more space and is designed to keep the archive readable by older programs. A program that does not know the field falls back to the legacy header name, which may itself display incorrectly.

Getting the names back

  • When bit 11 is clear, the archive may not record which encoding was used for the name. APPNOTE reserves a 0x0008 extra field for encoding information, but its layout is still undefined. The reader has to guess. For Korean names, try CP949 first; for Japanese, try CP932, the Windows variant of Shift_JIS.
  • In Python, pass that encoding as metadata_encoding. In .NET, pass it as entryNameEncoding; according to the documentation, it is used for entries with bit 11 clear.
  • Bit 11 covers file names and comments only. It says nothing about the encoding of text files inside the archive or of the password.
  • When creating archives, write UTF-8 names and set bit 11. Python zipfile does this by default for names containing non-ASCII characters. .NET ZipArchive does the same when entryNameEncoding is null.

The ZIP tool reads names without bit 11 by trying UTF-8, EUC-KR, Shift_JIS and then IBM437, or lets you select the encoding yourself. Saving again writes a ZIP with UTF-8 names and bit 11 set. If the file contents are garbled rather than the names, use the garbled text fixer.

Specifications and documentation used

Links and content were checked on 1 October 2026.

Back to the blog