{"record":{"id":"7b71446ba30bf203","repo":"FiloSottile/age","slug":"last-chunk-is-empty-try-age-v1-0-0-and-please-co","errorCode":null,"errorMessage":"last chunk is empty, try age v1.0.0, and please consider reporting this","messagePattern":"last chunk is empty, try age v1\\.0\\.0, and please consider reporting this","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/stream.go","lineNumber":132,"sourceCode":"// readChunk reads the next chunk of ciphertext from r.src and makes it available\n// in r.unread. last is true if the chunk was marked as the end of the message.\n// readChunk must not be called again after returning a last chunk or an error.\nfunc (r *DecryptReader) readChunk() (last bool, err error) {\n\tif len(r.unread) != 0 {\n\t\tpanic(\"stream: internal error: readChunk called with dirty buffer\")\n\t}\n\n\tin := r.buf[:]\n\tn, err := io.ReadFull(r.src, in)\n\tswitch {\n\tcase err == io.EOF:\n\t\t// A message can't end without a marked chunk. This message is truncated.\n\t\treturn false, io.ErrUnexpectedEOF\n\tcase err == io.ErrUnexpectedEOF:\n\t\t// The last chunk can be short, but not empty unless it's the first and\n\t\t// only chunk.\n\t\tif !nonceIsZero(&r.nonce) && n == r.a.Overhead() {\n\t\t\treturn false, errors.New(\"last chunk is empty, try age v1.0.0, and please consider reporting this\")\n\t\t}\n\t\tin = in[:n]\n\t\tlast = true\n\t\tsetLastChunkFlag(&r.nonce)\n\tcase err != nil:\n\t\treturn false, err\n\t}\n\n\toutBuf := make([]byte, 0, ChunkSize)\n\tout, err := r.a.Open(outBuf, r.nonce[:], in, nil)\n\tif err != nil && !last {\n\t\t// Check if this was a full-length final chunk.\n\t\tlast = true\n\t\tsetLastChunkFlag(&r.nonce)\n\t\tout, err = r.a.Open(outBuf, r.nonce[:], in, nil)\n\t}\n\tif err != nil {\n\t\treturn false, errors.New(\"failed to decrypt and authenticate payload chunk, file may be corrupted or tampered with\")","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/stream/stream.go#L114-L150","documentation":"During chunk decryption, if the underlying read returned io.ErrUnexpectedEOF and the resulting decrypted chunk is empty (only the 16-byte overhead) but the nonce is not zero — i.e. it is not the first chunk — the message is malformed: an encrypted stream cannot end on an empty non-initial chunk. The error hints at age v1.0.0, which had a bug writing empty final chunks.","triggerScenarios":"stream.Reader.readChunk hits io.ErrUnexpectedEOF while reading a chunk whose plaintext size equals only the AEAD overhead (empty chunk) and the chunk nonce is non-zero (not the first chunk).","commonSituations":"Decrypting files produced by age v1.0.0 which could emit an empty last chunk; ciphertext truncated at a chunk boundary after the first chunk.","solutions":["Try decrypting with age v1.0.0, which tolerates empty last chunks (the message says so explicitly).","Re-encrypt the plaintext with a current age version and redistribute the file.","If truncation is suspected, re-transfer the ciphertext and compare checksums with the sender."],"exampleFix":"// before (decrypting with current age)\n// fails on age v1.0.0 ciphertexts with empty last chunk\n// after\n// decrypt with age v1.0.0 binary, then re-encrypt:\n// age-v1.0.0 -d -i key.txt file.age > plain && age -r recipient > file.age < plain","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"if _, err := io.Copy(out, r); err != nil {\n    if strings.Contains(err.Error(), \"last chunk is empty\") {\n        return fmt.Errorf(\"file produced by buggy age v1.0.0: %w\", err)\n    }\n    return err\n}","preventionTips":["Keep age updated to a version newer than v1.0.0 for both encrypting and decrypting parties.","Re-encrypt any legacy ciphertext created with v1.0.0.","Compare file checksums with the sender to detect truncation."],"tags":["streaming","chunking","compatibility"],"backgroundTag":"empty-last-chunk","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}