Tencent/tinker · error · ZipException

CRC mismatch

Error message

CRC mismatch

What it means

For STORED (uncompressed) zip entries, AlignedZipOutputStream requires the caller to pre-compute and declare both CRC and size on the ZipEntry before writing, because STORED data is copied verbatim and must be self-describing. closeEntry() compares the CRC of the bytes actually written (maintained in this.crc) against currentEntry.getCrc(); any difference throws 'CRC mismatch'. This mirrors java.util.zip.ZipOutputStream's behavior for STORED entries.

Source

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

     * Closes the current {@code ZipEntry}. Any entry terminal data is written
     * to the underlying stream.
     *
     * @throws IOException
     *             If an error occurs closing the entry.
     */
    public void closeEntry() throws IOException {
        checkOpen();
        if (currentEntry == null) {
            return;
        }
        if (currentEntry.getMethod() == DEFLATED) {
            super.finish();
        }

        // Verify values for STORED types
        if (currentEntry.getMethod() == STORED) {
            if (crc.getValue() != currentEntry.getCrc()) {
                throw new ZipException("CRC mismatch");
            }
            if (currentEntry.getSize() != crcDataSize) {
                throw new ZipException("Size mismatch");
            }
        }

        int curOffset = LOCHDR;

        // Write the DataDescriptor
        if (currentEntry.getMethod() != STORED) {
            curOffset += EXTHDR;
            writeLong(out, EXTSIG);
            currentEntry.setCrc(crc.getValue());
            writeLong(out, currentEntry.getCrc());
            currentEntry.setCompressedSize(def.getTotalOut());
            writeLong(out, currentEntry.getCompressedSize());
            currentEntry.setSize(def.getTotalIn());
            writeLong(out, currentEntry.getSize());

View on GitHub (pinned to 1b7ea02c23)

Solutions

  1. Before writing a STORED entry, compute the CRC over the exact bytes you will write: byte[] data = ...; crc.reset(); crc.update(data); entry.setCrc(crc.getValue()); entry.setSize(data.length); entry.setCompressedSize(data.length).
  2. When cloning entries from a source zip, stream the entry bytes and recompute size/CRC yourself instead of trusting the source entry's metadata if the content may differ.
  3. If sizes/CRCs are not known up front, use DEFLATED (the stream writes a data descriptor and computes values for you) or buffer the content first.

Example fix

// before: STORED entry with stale/absent CRC
zos.putNextEntry(new ZipEntry("assets/blob.bin")); // method defaults may be STORED
zos.write(data); // closeEntry() throws CRC mismatch

// after: declare exact size and CRC for STORED data
CRC32 crc = new CRC32();
crc.update(data);
ZipEntry e = new ZipEntry("assets/blob.bin");
e.setMethod(ZipEntry.STORED);
e.setSize(data.length);
e.setCompressedSize(data.length);
e.setCrc(crc.getValue());
zos.putNextEntry(e);
zos.write(data);
Defensive patterns

Strategy: validation

Validate before calling

// Precompute size+CRC for STORED entries before putNextEntry
java.util.zip.CRC32 crc = new java.util.zip.CRC32();
crc.update(data);
if (entry.getMethod() == java.util.zip.ZipEntry.STORED
        && (entry.getCrc() != crc.getValue() || entry.getSize() != data.length)) {
    entry.setSize(data.length);
    entry.setCompressedSize(data.length);
    entry.setCrc(crc.getValue());
}

Try / catch

try {
    zos.closeEntry();
} catch (java.util.zip.ZipException e) {
    if ("CRC mismatch".equals(e.getMessage())) {
        throw new IllegalStateException("STORED entry " + entry.getName() + " declared a CRC that does not match written bytes", e);
    }
    throw e;
}

Prevention

When it happens

Trigger: putNextEntry(new ZipEntry(name)) with method STORED and a setCrc() value that does not match the bytes subsequently written via write(); or forgetting to set the CRC on an entry copied from another zip after modifying its content.

Common situations: Copying entries between zips (tinker patch building, re-aligning APKs) and keeping the source entry's CRC while writing different bytes; computing CRC over the wrong byte range (e.g. including/excluding a padding byte); using STORED with data whose size/CRC are only known after compression elsewhere.

Related errors


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