{"record":{"id":"8942309dac9a1339","repo":"restic/restic","slug":"decrypting-blob-v-from-pack-v-failed-w","errorCode":null,"errorMessage":"decrypting blob %v from pack %v failed: %w","messagePattern":"decrypting blob (.+?) from pack (.+?) failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/repository/repository.go","lineNumber":1326,"sourceCode":"\n\tbuf, err := b.rd.ReadFull(int(entry.Length))\n\tif err != nil {\n\t\tdebug.Log(\"    read error %v\", err)\n\t\treturn packBlobValue{}, fmt.Errorf(\"readFull: %w\", err)\n\t}\n\n\tb.currentOffset = entry.Offset + entry.Length\n\n\tif int(entry.Length) <= b.key.NonceSize() {\n\t\tdebug.Log(\"%v\", b.blobs)\n\t\treturn packBlobValue{}, fmt.Errorf(\"invalid blob length %v\", entry)\n\t}\n\n\t// decryption errors are likely permanent, give the caller a chance to skip them\n\tnonce, ciphertext := buf[:b.key.NonceSize()], buf[b.key.NonceSize():]\n\tplaintext, err := b.key.Open(ciphertext[:0], nonce, ciphertext, nil)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"decrypting blob %v from pack %v failed: %w\", h, b.packID.String(), err)\n\t}\n\tif err == nil && entry.IsCompressed() {\n\t\t// DecodeAll will allocate a slice if it is not large enough since it\n\t\t// knows the decompressed size (because we're using EncodeAll)\n\t\tb.decode, err = b.dec.DecodeAll(plaintext, b.decode[:0])\n\t\tplaintext = b.decode\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"decompressing blob %v from pack %v failed: %w\", h, b.packID.String(), err)\n\t\t}\n\t}\n\tif err == nil {\n\t\tid := restic.Hash(plaintext)\n\t\tif !id.Equal(entry.ID) {\n\t\t\tdebug.Log(\"read blob %v/%v from pack %v: wrong data returned, hash is %v\",\n\t\t\t\th.Type, h.ID, b.packID.String(), id)\n\t\t\terr = fmt.Errorf(\"read blob %v from pack %v: wrong data returned, hash is %v\",\n\t\t\t\th, b.packID.String(), id)\n\t\t}","sourceCodeStart":1308,"sourceCodeEnd":1344,"githubUrl":"https://github.com/restic/restic/blob/a80be1478a4c537f8396e0db2b05120aa78f11e0/internal/repository/repository.go#L1308-L1344","documentation":"key.Open failed while decrypting a blob read from a pack: the ciphertext did not authenticate under the repository key. The code comments that decryption errors are 'likely permanent' and gives the caller a chance to skip them, because the usual causes are persistent: bitrot in pack data, wrong repository password/key, or a pack overwritten with different bytes. Emitted from packBlobIterator.Next and delivered to the handleBlobFn callback of LoadBlobsFromPack.","triggerScenarios":"Reading a blob (restore, dump, prune repack, verify) from a pack whose bytes were corrupted after upload; opening a repository with the wrong password so the derived key cannot authenticate any blob; two restic instances writing the same pack ID with different keys/content on a non-atomic backend.","commonSituations":"Wrong password after a restore of credentials or a keyfile mix-up between repos; failing disks/RAM corrupting objects in a local or SFTP repo; storage services that mangle objects (rare but seen with buggy gateways).","solutions":["Confirm you are using the correct password and repository URL for this repo","Run restic check --read-data to confirm and scope pack-level damage","Check hardware: SMART for disks, memtest for RAM, when multiple packs fail to decrypt","Recover the affected pack from a replica/second copy, or re-backup the source data"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := repo.LoadBlobsFromPack(ctx, packID, handles, func(h restic.BlobHandle, buf []byte, err error) error {\n    if err != nil {\n        if strings.Contains(err.Error(), \"decrypting blob\") {\n            log.Printf(\"permanent: blob %v fails authentication in pack %s — likely wrong key or damaged pack\", h, packID)\n        }\n        return err // caller may skip: return nil after logging in scan scenarios\n    }\n    return nil\n})","preventionTips":["Store the repo password in a managed secret and record which password belongs to which repo","Verify the password right after repository creation and after credential restores (restic snapshots is a cheap smoke test)","Keep restic copy replicas so damaged packs have a fallback"],"tags":["go","restic","crypto","corruption","authentication"],"backgroundTag":null,"analyzedSha":"a80be1478a4c537f8396e0db2b05120aa78f11e0","analyzedAt":"2026-08-15T15:30:29.928Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}