{"record":{"id":"66ae849fea94aae8","repo":"FiloSottile/age","slug":"failed-to-decrypt-and-authenticate-chunk-at-offset","errorCode":null,"errorMessage":"failed to decrypt and authenticate chunk at offset %d: %w","messagePattern":"failed to decrypt and authenticate chunk at offset (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/stream.go","lineNumber":443,"sourceCode":"\t\tchunkSize := min(encSize-chunkOff, encChunkSize)\n\n\t\tcached := r.cache.Load()\n\t\tvar plaintext []byte\n\t\tif cached != nil && cached.off == chunkOff {\n\t\t\tplaintext = cached.data\n\t\t\tcacheUpdate = nil\n\t\t} else {\n\t\t\tif err := readFullAt(r.src, chunk[:chunkSize], chunkOff); err != nil {\n\t\t\t\treturn n, fmt.Errorf(\"failed to read chunk at offset %d: %w\", chunkOff, err)\n\t\t\t}\n\t\t\tnonce := nonceForChunk(chunkIndex)\n\t\t\tif chunkIndex == r.chunks-1 {\n\t\t\t\tsetLastChunkFlag(nonce)\n\t\t\t}\n\t\t\tvar err error\n\t\t\tplaintext, err = r.a.Open(chunk[:0], nonce[:], chunk[:chunkSize], nil)\n\t\t\tif err != nil {\n\t\t\t\treturn n, fmt.Errorf(\"failed to decrypt and authenticate chunk at offset %d: %w\", chunkOff, err)\n\t\t\t}\n\t\t\tcacheUpdate = &cachedChunk{off: chunkOff, data: plaintext}\n\t\t}\n\n\t\tplainChunkOff := int(off - chunkIndex*ChunkSize)\n\t\tcopySize := min(len(plaintext)-plainChunkOff, len(p))\n\t\tcopy(p, plaintext[plainChunkOff:plainChunkOff+copySize])\n\t\tp = p[copySize:]\n\t\toff += int64(copySize)\n\t\tn += copySize\n\t}\n\tif cacheUpdate != nil {\n\t\tr.cache.Store(cacheUpdate)\n\t}\n\tif off == r.size {\n\t\treturn n, io.EOF\n\t}\n\treturn n, nil","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/internal/stream/stream.go#L425-L461","documentation":"DecryptReaderAt.ReadAt read a chunk successfully, but ChaCha20Poly1305 authentication failed when opening it. Because each chunk carries its own AEAD tag, this means the bytes at that chunk offset are not the authentic ciphertext for this key and chunk index. In age's threat model this indicates corruption or tampering, and the read is aborted with any bytes already copied returned as n.","triggerScenarios":"ReadAt(p, off) where aead.Open fails for the chunk at chunkOff: wrong key; ciphertext bytes for that chunk modified; the src content changed after NewDecryptReaderAt validated the final chunk (validation only authenticated the last chunk, not all chunks).","commonSituations":"A file being rewritten in place after opening the reader (e.g. rsync or atomic replace of the target); bit rot on storage; decrypting with the wrong identity; in-place edits by tools unaware of the format.","solutions":["Confirm the key matches the recipient this file was encrypted for.","Take a snapshot/immutable view of the ciphertext while reading; do not modify or re-encrypt the file concurrently.","Verify file integrity (checksum) and restore from a known-good copy if corrupted.","Ensure the size used at construction still matches the current source; a changed file invalidates chunk offsets."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"n, err := dr.ReadAt(p, off)\nif err != nil && !errors.Is(err, io.EOF) {\n    if strings.Contains(err.Error(), \"failed to decrypt and authenticate chunk\") {\n        return fmt.Errorf(\"ciphertext chunk corrupted or wrong key: %w\", err)\n    }\n    return err\n}","preventionTips":["Make the ciphertext read-only while a DecryptReaderAt is open.","Verify the key/identity matches the file's recipient.","Keep checksums to detect bit rot and restore from backups.","Only the final chunk is pre-validated at construction; mid-file corruption surfaces here."],"tags":["stream","crypto","authentication","corrupt-data","go"],"backgroundTag":"aead-authentication-failed","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}