lostindark/DriverStoreExplorer · error · InvalidOperationException

SHA256 hash of the downloaded file does not match the expect

Error message

SHA256 hash of the downloaded file does not match the expected value.

What it means

Integrity gate after the download completes. ApplyUpdateAsync recomputes SHA256 over the saved zip and compares it (case-insensitive, hex) to versionInfo.Sha256, which GetLatestVersionInfo parses from the GitHub asset's digest field ('sha256:<hex>'). On mismatch the temp zip is deleted and the update aborts — this is the protection against truncated downloads, MITM, or a swapped asset. The check only runs when versionInfo.Sha256 is non-empty, so a release missing the digest field silently skips verification.

Source

Thrown at Rapr/UpdateManager.cs:124

            // Download the zip
            using (var fileStream = new FileStream(tempZipPath, FileMode.Create, FileAccess.Write, FileShare.None))
            {
                await this.httpClient.DownloadAsync(versionInfo.DownloadUrl, fileStream, progress).ConfigureAwait(false);
            }

            // Verify SHA256 hash
            if (!string.IsNullOrEmpty(versionInfo.Sha256))
            {
                using (var sha256 = SHA256.Create())
                using (var fileStream = new FileStream(tempZipPath, FileMode.Open, FileAccess.Read, FileShare.Read))
                {
                    var hashBytes = sha256.ComputeHash(fileStream);
                    var actualHash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);

                    if (!actualHash.Equals(versionInfo.Sha256, StringComparison.OrdinalIgnoreCase))
                    {
                        File.Delete(tempZipPath);
                        throw new InvalidOperationException("SHA256 hash of the downloaded file does not match the expected value.");
                    }
                }
            }

            // Extract zip
            ZipFile.ExtractToDirectory(tempZipPath, tempExtractPath);

            // Find the actual content directory (zip may have a single root folder)
            string sourceDir = tempExtractPath;
            var subDirs = Directory.GetDirectories(tempExtractPath);
            if (subDirs.Length == 1 && Directory.GetFiles(tempExtractPath).Length == 0)
            {
                sourceDir = subDirs[0];
            }

            string appDir = Path.GetFullPath(DSEFormHelper.GetApplicationFolder());
            string currentExePath = Assembly.GetExecutingAssembly().Location;

View on GitHub (pinned to 958fcd481b)

Solutions

  1. Retry the update once — transient truncation is the most common cause and the next attempt often hashes clean.
  2. Verify the published digest: compare versionInfo.Sha256 to the value shown on the GitHub release page asset (shown as a SHA-256 checksum).
  3. Check the temp file size vs. GitHub's reported asset size: Path.Combine(Path.GetTempPath(), "DriverStoreExplorer") — a short file confirms truncation.
  4. Disable any intercepting proxy or AV for the download domain and retry.
  5. If you author releases, ensure GitHub computes and exposes the sha256 digest (asset uploaded as a binary release file yields the digest automatically).

Example fix

// before (single attempt, throws on mismatch)
await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);

// after (retry the download once before surfacing failure)
for (int attempt = 0; attempt < 2; attempt++)
{
    try
    {
        await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
        break;
    }
    catch (InvalidOperationException ex) when (attempt == 0 && ex.Message.Contains("SHA256"))
    {
        // Likely a truncated download; loop will re-download from scratch.
        continue;
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// You cannot pre-validate a hash before download, but you can surface the
// expected digest to the user and confirm the release exposes one.
private static bool ReleaseExposesSha256(VersionInfo info)
    => !string.IsNullOrWhiteSpace(info?.Sha256)
       && info.Sha256.Length == 64
       && System.Text.RegularExpressions.Regex.IsMatch(info.Sha256, @"^[0-9a-fA-F]{64}$");

if (!ReleaseExposesSha256(this.latestVersionInfo))
{
    // Verification would be silently skipped (UpdateManager checks only when non-empty).
    Logger.Warn("Release does not expose a SHA256 digest; integrity check will be bypassed.");
}

Try / catch

// Retry once on a hash mismatch (most often a truncated download), then surface.
int attempts = 0;
while (true)
{
    try
    {
        await this.updateManager.ApplyUpdateAsync(this.latestVersionInfo, progress);
        break;
    }
    catch (InvalidOperationException ex) when (attempts++ == 0 && ex.Message.Contains("SHA256"))
    {
        continue;
    }
}

Prevention

When it happens

Trigger: Line 113–127: bytes downloaded to tempZipPath don't hash to versionInfo.Sha256. Causes: a truncated/partial download (HttpClient.DownloadAsync dropped bytes), a proxy injecting an error page, a release whose digest was computed against a different asset, or an attacker who swapped the zip but could not forge the digest.

Common situations: Flaky connection leaving a short file; corporate proxy returns an HTML block page with 200; the release tag was force-pushed and asset[0] no longer matches the cached digest; the digest field format changed and the hex parsing produced a wrong value; local antivirus rewrote the downloaded file.

Related errors


AI-assisted analysis of lostindark/DriverStoreExplorer@958fcd481b (2026-08-13). Data as JSON: /api/errors/8163375916439c4b. Report an issue: GitHub.