{"record":{"id":"df4e7505142f077a","repo":"nextcloud/server","slug":"computed-algorithm-hash-is-incorrect-computedha","errorCode":null,"errorMessage":"Computed $algorithm hash is incorrect ($computedHash).","messagePattern":"Computed \\$algorithm hash is incorrect \\(\\$computedHash\\)\\.","errorType":"exception","errorClass":"Sabre\\DAV\\Exception\\BadRequest","httpStatus":400,"severity":"error","filePath":"apps/dav/lib/BulkUpload/MultipartRequestParser.php","lineNumber":250,"sourceCode":"\t * Compute the MD5 or checksum hash of the next x bytes.\n\t * TODO: Drop $md5 argument when the latest desktop client that uses it is no longer supported.\n\t */\n\tprivate function validateHash(int $length, string $fileMd5Header, string $checksumHeader): void {\n\t\tif ($checksumHeader !== '') {\n\t\t\t[$algorithm, $hash] = explode(':', $checksumHeader, 2);\n\t\t} elseif ($fileMd5Header !== '') {\n\t\t\t$algorithm = 'md5';\n\t\t\t$hash = $fileMd5Header;\n\t\t} else {\n\t\t\tthrow new BadRequest('No hash provided.');\n\t\t}\n\n\t\t$context = hash_init($algorithm);\n\t\thash_update_stream($context, $this->stream, $length);\n\t\tfseek($this->stream, -$length, SEEK_CUR);\n\t\t$computedHash = hash_final($context);\n\t\tif ($hash !== $computedHash) {\n\t\t\tthrow new BadRequest(\"Computed $algorithm hash is incorrect ($computedHash).\");\n\t\t}\n\t}\n}\n","sourceCodeStart":232,"sourceCodeEnd":254,"githubUrl":"https://github.com/nextcloud/server/blob/ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3/apps/dav/lib/BulkUpload/MultipartRequestParser.php#L232-L254","documentation":"validateHash() hashes exactly Content-Length bytes of the part with the declared algorithm (md5 from X-File-MD5, or the algorithm prefix of OC-Checksum) and compares strings with ===; a mismatch throws HTTP 400 including the server-side computed digest. The bytes received hashed to something other than what the header claims.","triggerScenarios":"Hash computed over different bytes than sent (length/charset drift between hashing and writing), algorithm label not matching the digest ('OC-Checksum: SHA1:...' holding an MD5), digest cached from an older file version, hex case mismatch (comparison is case-sensitive), or genuine in-transit corruption.","commonSituations":"Clients caching checksums and reusing them after the file changed; hashing base64-encoded instead of raw bytes; uppercase digests from some tools; proxies mangling binary bodies.","solutions":["Recompute the hash immediately before sending, over the exact bytes of the part body, with the algorithm matching the header label","Use lowercase hex (PHP hash()/hash_file() output) - the server compares raw strings case-sensitively","Compare the server's computed value from the error message with your local digest; if both are stable but different, byte-diff the uploaded payload to find where content diverges (proxy or encoding mangling)"],"exampleFix":"// before\n\"OC-Checksum: SHA1:\" . md5($content) // wrong: md5 labeled as SHA1\n\n// after\n\"OC-Checksum: SHA1:\" . hash('sha1', $content) // label matches digest, lowercase hex","handlingStrategy":"validation","validationCode":"// Compute digest and length from the same final bytes, label correctly\n$raw = $part['content'];\n$part['headers']['content-length'] = (string)strlen($raw);\n$part['headers']['oc-checksum'] = 'SHA1:' . hash('sha1', $raw); // label matches algorithm, lowercase hex","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never reuse cached digests after a file changes - rehash before every upload","Use lowercase hex digests; the comparison is case-sensitive ===","Keep the algorithm prefix of OC-Checksum consistent with how the digest was computed"],"tags":["php","webdav","nextcloud","bulk-upload","multipart","checksum","integrity"],"backgroundTag":"checksum-mismatch","analyzedSha":"ecdeb153ffdf227235c9a7e2d13dbe0f9c817bc3","analyzedAt":"2026-08-17T01:36:13.386Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}