Tencent/tinker · error · ZipException

Invalid General Purpose Bit Flag: ${gpbf}

Error message

Invalid General Purpose Bit Flag: ${gpbf}

What it means

Before returning an input stream, TinkerZipFile re-reads the entry's local file header and checks the General Purpose Bit Flag against GPBF_UNSUPPORTED_MASK, which in this fork equals GPBF_ENCRYPTED_FLAG. Any entry whose local header marks it as encrypted causes ZipException('Invalid General Purpose Bit Flag: <gpbf>') — this library performs no decryption. Note it reads the flag from the local header, so the central directory's flags are not what triggers it.

Source

Thrown at third-party/tinker-ziputils/src/main/java/com/tencent/tinker/ziputils/ziputil/TinkerZipFile.java:355

            return null;
        }
        // Create an InputStream at the right part of the file.
        RandomAccessFile localRaf = raf;
        synchronized (localRaf) {
            // We don't know the entry data's start position. All we have is the
            // position of the entry's local header.
            // http://www.pkware.com/documents/casestudies/APPNOTE.TXT
            RAFStream rafStream = new RAFStream(localRaf, entry.localHeaderRelOffset);
            DataInputStream is = new DataInputStream(rafStream);
            final int localMagic = Integer.reverseBytes(is.readInt());
            if (localMagic != LOCSIG) {
                throwZipException(filename, localRaf.length(), entry.getName(), entry.localHeaderRelOffset, "Local File Header", localMagic);
            }
            is.skipBytes(2);
            // At position 6 we find the General Purpose Bit Flag.
            int gpbf = Short.reverseBytes(is.readShort()) & 0xffff;
            if ((gpbf & TinkerZipFile.GPBF_UNSUPPORTED_MASK) != 0) {
                throw new ZipException("Invalid General Purpose Bit Flag: " + gpbf);
            }
            // Offset 26 has the file name length, and offset 28 has the extra field length.
            // These lengths can differ from the ones in the central header.
            is.skipBytes(18);
            int fileNameLength = Short.reverseBytes(is.readShort()) & 0xffff;
            int extraFieldLength = Short.reverseBytes(is.readShort()) & 0xffff;
            is.close();
            // Skip the variable-size file name and extra field data.
            rafStream.skip(fileNameLength + extraFieldLength);
            /*if (entry.compressionMethod == ZipEntry.STORED) {
                rafStream.endOffset = rafStream.offset + entry.size;
                return rafStream;
            } else {
                rafStream.endOffset = rafStream.offset + entry.compressedSize;
                int bufSize = Math.max(1024, (int) Math.min(entry.getSize(), 65535L));
                return new ZipInflaterInputStream(rafStream, new Inflater(true), bufSize, entry);
            }*/
            if (entry.compressionMethod == TinkerZipEntry.STORED) {

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Re-create the archive without entry encryption (zip without a password / repackage the APK before the encryption step).
  2. If encryption is intentional, decrypt the archive first with a tool that supports it and process the decrypted copy.
  3. Pre-scan archives for encrypted entries (central directory bit 0) and route them away from TinkerZipFile with a clear user-facing error.

Example fix

// before
InputStream is = zf.getInputStream(entry); // throws if entry is encrypted

// after
if ((entry.getGeneralPurposeBit() & 0x1) != 0) { // central-dir hint
    throw new IOException("Archive contains encrypted entry: " + entry.getName());
}
InputStream is = zf.getInputStream(entry);
Defensive patterns

Strategy: validation

Validate before calling

// central directory flag bit 0 == encrypted; cheap pre-scan of the entry set
static boolean hasEncryptedEntries(TinkerZipFile zf) {
    Enumeration<? extends TinkerZipEntry> en = zf.entries();
    while (en.hasMoreElements()) {
        TinkerZipEntry e = en.nextElement();
        if ((e.getGeneralPurposeBit() & 0x1) != 0) {
            return true;
        }
    }
    return false;
}

Try / catch

try {
    return zf.getInputStream(entry);
} catch (ZipException e) {
    if (e.getMessage().startsWith("Invalid General Purpose Bit Flag")) {
        throw new IOException("Entry '" + entry.getName() + "' is encrypted; decrypt the archive first", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling getInputStream() on an entry that was encrypted when the zip was created (password-protected zip, or an APK whose entries were encrypted by a signing/packing tool that sets bit 0 of the GPBF in the local header).

Common situations: Feeding a password-protected archive to a pipeline that expects plain zips; 'app reinforcement' tools that encrypt dex/resources inside the APK; archives produced by Windows Explorer's 'Encrypt' option or 7-Zip AES.

Related errors


AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14). Data as JSON: /api/errors/e227b6498ea381d3. Report an issue: GitHub.