diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -2810,6 +2810,7 @@
     'PhabricatorFilesManagementEncodeWorkflow' => 'applications/files/management/PhabricatorFilesManagementEncodeWorkflow.php',
     'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
     'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php',
+    'PhabricatorFilesManagementIntegrityWorkflow' => 'applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php',
     'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
     'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php',
     'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
@@ -7941,6 +7942,7 @@
     'PhabricatorFilesManagementEncodeWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow',
+    'PhabricatorFilesManagementIntegrityWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow',
     'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
diff --git a/src/applications/files/engine/PhabricatorFileStorageEngine.php b/src/applications/files/engine/PhabricatorFileStorageEngine.php
--- a/src/applications/files/engine/PhabricatorFileStorageEngine.php
+++ b/src/applications/files/engine/PhabricatorFileStorageEngine.php
@@ -336,7 +336,7 @@
     $known_integrity = $file->getIntegrityHash();
     if ($known_integrity !== null) {
       $new_integrity = $this->newIntegrityHash($formatted_data, $format);
-      if ($known_integrity !== $new_integrity) {
+      if (!phutil_hashes_are_identical($known_integrity, $new_integrity)) {
         throw new PhabricatorFileIntegrityException(
           pht(
             'File data integrity check failed. Dark forces have corrupted '.
diff --git a/src/applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php b/src/applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/files/management/PhabricatorFilesManagementIntegrityWorkflow.php
@@ -0,0 +1,325 @@
+<?php
+
+final class PhabricatorFilesManagementIntegrityWorkflow
+  extends PhabricatorFilesManagementWorkflow {
+
+  protected function didConstruct() {
+    $this
+      ->setName('integrity')
+      ->setSynopsis(pht('Verify or recalculate file integrity hashes.'))
+      ->setArguments(
+        array(
+          array(
+            'name'      => 'all',
+            'help'      => pht('Affect all files.'),
+          ),
+          array(
+            'name' => 'strip',
+            'help' => pht(
+              'DANGEROUS. Strip integrity hashes from files. This makes '.
+              'files vulnerable to corruption or tampering.'),
+          ),
+          array(
+            'name' => 'corrupt',
+            'help' => pht(
+              'Corrupt integrity hashes for given files. This is intended '.
+              'for debugging.'),
+          ),
+          array(
+            'name' => 'compute',
+            'help' => pht(
+              'Compute and update integrity hashes for files which do not '.
+              'yet have them.'),
+          ),
+          array(
+            'name' => 'overwrite',
+            'help' => pht(
+              'DANGEROUS. Recompute and update integrity hashes, overwriting '.
+              'invalid hashes. This may mark corrupt or dangerous files as '.
+              'valid.'),
+          ),
+          array(
+            'name' => 'force',
+            'short' => 'f',
+            'help' => pht(
+              'Execute dangerous operations without prompting for '.
+              'confirmation.'),
+          ),
+          array(
+            'name'      => 'names',
+            'wildcard'  => true,
+          ),
+        ));
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $modes = array();
+
+    $is_strip = $args->getArg('strip');
+    if ($is_strip) {
+      $modes[] = 'strip';
+    }
+
+    $is_corrupt = $args->getArg('corrupt');
+    if ($is_corrupt) {
+      $modes[] = 'corrupt';
+    }
+
+    $is_compute = $args->getArg('compute');
+    if ($is_compute) {
+      $modes[] = 'compute';
+    }
+
+    $is_overwrite = $args->getArg('overwrite');
+    if ($is_overwrite) {
+      $modes[] = 'overwrite';
+    }
+
+    $is_verify = !$modes;
+    if ($is_verify) {
+      $modes[] = 'verify';
+    }
+
+    if (count($modes) > 1) {
+      throw new PhutilArgumentUsageException(
+        pht(
+          'You have selected multiple operation modes (%s). Choose a '.
+          'single mode to operate in.',
+          implode(', ', $modes)));
+    }
+
+    $is_force = $args->getArg('force');
+    if (!$is_force) {
+      $prompt = null;
+      if ($is_strip) {
+        $prompt = pht(
+          'Stripping integrity hashes is dangerous and makes files '.
+          'vulnerable to corruption or tampering.');
+      }
+
+      if ($is_corrupt) {
+        $prompt = pht(
+          'Corrupting integrity hashes will prevent files from being '.
+          'accessed. This mode is intended only for development and '.
+          'debugging.');
+      }
+
+      if ($is_overwrite) {
+        $prompt = pht(
+          'Overwriting integrity hashes is dangerous and may mark files '.
+          'which have been corrupted or tampered with as safe.');
+      }
+
+      if ($prompt) {
+        $this->logWarn(pht('DANGEROUS'), $prompt);
+
+        if (!phutil_console_confirm(pht('Continue anyway?'))) {
+          throw new PhutilArgumentUsageException(pht('Aborted workflow.'));
+        }
+      }
+    }
+
+    $iterator = $this->buildIterator($args);
+    if (!$iterator) {
+      throw new PhutilArgumentUsageException(
+        pht(
+          'Either specify a list of files to affect, or use "--all" to '.
+          'affect all files.'));
+    }
+
+    $failure_count = 0;
+    $total_count = 0;
+
+    foreach ($iterator as $file) {
+      $total_count++;
+      $display_name = $file->getMonogram();
+
+      $old_hash = $file->getIntegrityHash();
+
+      if ($is_strip) {
+        if ($old_hash === null) {
+          $this->logInfo(
+            pht('SKIPPED'),
+            pht(
+              'File "%s" does not have an integrity hash to strip.',
+              $display_name));
+        } else {
+          $file
+            ->setIntegrityHash(null)
+            ->save();
+
+          $this->logWarn(
+            pht('STRIPPED'),
+            pht(
+              'Stripped integrity hash for "%s".',
+              $display_name));
+        }
+
+        continue;
+      }
+
+      $need_hash = ($is_verify && $old_hash) ||
+                   ($is_compute && ($old_hash === null)) ||
+                   ($is_corrupt) ||
+                   ($is_overwrite);
+      if ($need_hash) {
+        try {
+          $new_hash = $file->newIntegrityHash();
+        } catch (Exception $ex) {
+          $failure_count++;
+
+          $this->logFail(
+            pht('ERROR'),
+            pht(
+              'Unable to compute integrity hash for file "%s": %s',
+              $display_name,
+              $ex->getMessage()));
+
+          continue;
+        }
+      } else {
+        $new_hash = null;
+      }
+
+      // NOTE: When running in "corrupt" mode, we only corrupt the hash if
+      // we're able to compute a valid hash. Some files, like chunked files,
+      // do not support integrity hashing so corrupting them would create an
+      // unusual state.
+
+      if ($is_corrupt) {
+        if ($new_hash === null) {
+          $this->logInfo(
+            pht('IGNORED'),
+            pht(
+              'Storage for file "%s" does not support integrity hashing.',
+              $display_name));
+        } else {
+          $file
+            ->setIntegrityHash('<corrupted>')
+            ->save();
+
+          $this->logWarn(
+            pht('CORRUPTED'),
+            pht(
+              'Corrupted integrity hash for file "%s".',
+              $display_name));
+        }
+
+        continue;
+      }
+
+      if ($is_verify) {
+        if ($old_hash === null) {
+          $this->logInfo(
+            pht('NONE'),
+            pht(
+              'File "%s" has no stored integrity hash.',
+              $display_name));
+        } else if ($new_hash === null) {
+          $failure_count++;
+
+          $this->logWarn(
+            pht('UNEXPECTED'),
+            pht(
+              'Storage for file "%s" does not support integrity hashing, '.
+              'but the file has an integrity hash.',
+              $display_name));
+        } else if (phutil_hashes_are_identical($old_hash, $new_hash)) {
+          $this->logOkay(
+            pht('VALID'),
+            pht(
+              'File "%s" has a valid integrity hash.',
+              $display_name));
+        } else {
+          $failure_count++;
+
+          $this->logFail(
+            pht('MISMATCH'),
+            pht(
+              'File "%s" has an invalid integrity hash!',
+              $display_name));
+        }
+
+        continue;
+      }
+
+      if ($is_compute) {
+        if ($old_hash !== null) {
+          $this->logInfo(
+            pht('SKIP'),
+            pht(
+              'File "%s" already has an integrity hash.',
+              $display_name));
+        } else if ($new_hash === null) {
+          $this->logInfo(
+            pht('IGNORED'),
+            pht(
+              'Storage for file "%s" does not support integrity hashing.',
+              $display_name));
+        } else {
+          $file
+            ->setIntegrityHash($new_hash)
+            ->save();
+
+          $this->logOkay(
+            pht('COMPUTE'),
+            pht(
+              'Computed and stored integrity hash for file "%s".',
+              $display_name));
+        }
+
+        continue;
+      }
+
+      if ($is_overwrite) {
+        $same_hash = ($old_hash !== null) &&
+                     ($new_hash !== null) &&
+                     phutil_hashes_are_identical($old_hash, $new_hash);
+
+        if ($new_hash === null) {
+          $this->logInfo(
+            pht('IGNORED'),
+            pht(
+              'Storage for file "%s" does not support integrity hashing.',
+              $display_name));
+        } else if ($same_hash) {
+          $this->logInfo(
+            pht('UNCHANGED'),
+            pht(
+              'File "%s" already has the correct integrity hash.',
+              $display_name));
+        } else {
+          $file
+            ->setIntegrityHash($new_hash)
+            ->save();
+
+          $this->logOkay(
+            pht('OVERWRITE'),
+            pht(
+              'Overwrote integrity hash for file "%s".',
+              $display_name));
+        }
+
+        continue;
+      }
+    }
+
+    if ($failure_count) {
+      $this->logFail(
+        pht('FAIL'),
+        pht(
+          'Processed %s file(s), encountered %s error(s).',
+          new PhutilNumber($total_count),
+          new PhutilNumber($failure_count)));
+    } else {
+      $this->logOkay(
+        pht('DONE'),
+        pht(
+          'Processed %s file(s) with no errors.',
+          new PhutilNumber($total_count)));
+    }
+
+    return 0;
+  }
+
+}
diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php
--- a/src/applications/files/storage/PhabricatorFile.php
+++ b/src/applications/files/storage/PhabricatorFile.php
@@ -493,9 +493,7 @@
 
     $engine_class = get_class($engine);
 
-    $key = $this->getStorageFormat();
-    $format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
-      ->setFile($this);
+    $format = $this->newStorageFormat();
 
     $data_iterator = array($data);
     $formatted_iterator = $format->newWriteIterator($data_iterator);
@@ -756,9 +754,7 @@
   public function getFileDataIterator($begin = null, $end = null) {
     $engine = $this->instantiateStorageEngine();
 
-    $key = $this->getStorageFormat();
-    $format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
-      ->setFile($this);
+    $format = $this->newStorageFormat();
 
     $iterator = $engine->getRawFileDataIterator(
       $this,
@@ -1238,6 +1234,21 @@
     return idx($this->metadata, self::METADATA_INTEGRITY);
   }
 
+  public function newIntegrityHash() {
+    $engine = $this->instantiateStorageEngine();
+
+    if ($engine->isChunkEngine()) {
+      return null;
+    }
+
+    $format = $this->newStorageFormat();
+
+    $storage_handle = $this->getStorageHandle();
+    $data = $engine->readFile($storage_handle);
+
+    return $engine->newIntegrityHash($data, $format);
+  }
+
   /**
    * Write the policy edge between this file and some object.
    *
@@ -1406,6 +1417,16 @@
     return $this->assertAttachedKey($this->transforms, $key);
   }
 
+  public function newStorageFormat() {
+    $key = $this->getStorageFormat();
+    $template = PhabricatorFileStorageFormat::requireFormat($key);
+
+    $format = id(clone $template)
+      ->setFile($this);
+
+    return $format;
+  }
+
 
 /* -(  PhabricatorApplicationTransactionInterface  )------------------------- */
 
diff --git a/src/infrastructure/management/PhabricatorManagementWorkflow.php b/src/infrastructure/management/PhabricatorManagementWorkflow.php
--- a/src/infrastructure/management/PhabricatorManagementWorkflow.php
+++ b/src/infrastructure/management/PhabricatorManagementWorkflow.php
@@ -31,4 +31,40 @@
       PhabricatorConsoleContentSource::SOURCECONST);
   }
 
+  protected function logInfo($label, $message) {
+    $this->logRaw(
+      tsprintf(
+        "**<bg:blue> %s </bg>** %s\n",
+        $label,
+        $message));
+  }
+
+  protected function logOkay($label, $message) {
+    $this->logRaw(
+      tsprintf(
+        "**<bg:green> %s </bg>** %s\n",
+        $label,
+        $message));
+  }
+
+  protected function logWarn($label, $message) {
+    $this->logRaw(
+      tsprintf(
+        "**<bg:yellow> %s </bg>** %s\n",
+        $label,
+        $message));
+  }
+
+  protected function logFail($label, $message) {
+    $this->logRaw(
+      tsprintf(
+        "**<bg:red> %s </bg>** %s\n",
+        $label,
+        $message));
+  }
+
+  private function logRaw($message) {
+    fprintf(STDERR, '%s', $message);
+  }
+
 }