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
- Re-create the archive without entry encryption (zip without a password / repackage the APK before the encryption step).
- If encryption is intentional, decrypt the archive first with a tool that supports it and process the decrypted copy.
- 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
- Produce archives unencrypted anywhere TinkerZipFile will read them.
- Pre-scan entries for the encryption flag and fail with an actionable message.
- Beware APK 'reinforcement' tools that encrypt entry payloads — run Tinker before that stage.
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
- Invalid General Purpose Bit Flag: ${gpbf}
- Expected ${DEX_IN_JAR_NAME} in ${file}
- CRC mismatch
- Size mismatch
- Entry already exists: {}
AI-assisted analysis of Tencent/tinker@1b7ea02c23 (2026-08-14).
Data as JSON: /api/errors/e227b6498ea381d3.
Report an issue: GitHub.