EByte Case Study Editing a PNG File With Every Chunk CRC Recomputed
A PNG file is an 8-byte signature followed by chunks, and every chunk ends with a CRC-32 of its type and data. Change one byte of a comment by hand and the image is broken, because that CRC no longer matches. EByte 0.4.0 adds checksums to its schemas: a field can be declared as the CRC-32, Adler-32 or one of three CRC-16 variants of other fields, and EByte checks it on decode and writes it on encode. This case study tried it on a real PNG with the released ebyte 0.4.0 Linux binary, downloaded from GitHub. Its checksum matched SHA256SUMS before anything ran.
The image is 4 by 3 pixels in three coloured stripes, saved by Pillow with a text comment and a 72 DPI setting. It’s 148 bytes in five chunks: IHDR with the size and colour type, tEXt with the comment “made with Pillow for EByte”, pHYs with the resolution, IDAT with the compressed pixels and the empty IEND.
The Schema
The png schema uses the repeating blocks from 0.2.0, the derived sizes from 0.3.0 and one new line:
{"name": "length", "type": "u32", "derive": {"size_of": "data"}},
{"name": "type", "type": "string", "encoding": "ascii", "length": 4},
{"name": "data", "type": "bytes", "length": {"field": "length"}},
{"name": "crc", "type": "u32", "derive": {"checksum": "crc32", "of": {"from": "type", "to": "data"}}}
inspect showed the five chunks with their lengths (13, 34, 9, 24 and 0) and CRCs. The last one, 2923585666, is ae426082 in hex, the CRC every PNG’s IEND chunk carries. Decoding gave a document with only types and data in it, and encoding it unchanged gave back all 148 bytes.
Two Edits
In the JSON, the comment became “stripes, checked by EByte 0.4.0 with every CRC recomputed”, 57 characters instead of 26. The pHYs data, 2835 pixels per metre in both directions, became 11811, which is 300 DPI. Nothing else was touched.
The encoded file is 179 bytes. EByte rewrote the tEXt length and the CRCs of both edited chunks. To check this without trusting EByte, a short Python script walked the chunks and compared every CRC with zlib.crc32: no mismatches. Pillow opened the result and reported the new comment, 300 DPI (299.9994, from rounding to whole pixels per metre), the same 4 by 3 size and the same red top-left pixel.
Finding a Damaged Byte
Flipping one bit is the classic test for a checksum. In a copy of the original, byte 112, inside the compressed pixel data, had its lowest bit flipped:
{"diagnostic_version":1,"stage":"decode","code":"checksum_mismatch","path":"/chunks/3/crc","offset":132,"field_start":132,"expected":"stored checksum equals computed checksum","observed":"stored value differs"}
Chunk 3 is IDAT, and offset 132 is where its CRC starts. Pillow’s message for the same file was “broken data stream when reading image file”. A second copy with one bit flipped in the comment, at byte 50, failed at /chunks/1/crc at offset 75. Pillow couldn’t identify that file as an image at all. Both times EByte named the chunk. It doesn’t say which byte inside the chunk is wrong; a CRC can’t tell you that.
Where It Stops
A checksum covers bytes as they’re stored. PNG’s pixel data is zlib-compressed, and EByte doesn’t decompress anything, so the pixels are an opaque hex string. To show what that means, IHDR’s width was changed from 4 to 5 in the JSON. EByte encoded it with a correct CRC, and zlib.crc32 agreed, but Pillow refused it with “unrecognized data stream contents”, because the compressed rows still describe 4 pixels each. The file is internally consistent and still wrong.
The same rule decides ZIP. The one-file stored ZIP schema now derives both copies of the CRC-32 and every size and offset, because stored data isn’t compressed. The general zip schema can’t, because a ZIP CRC covers the uncompressed file. The schema reference lists the five algorithms and their check values, and the schema library has the PNG schema and sample.