diff --git a/include/class.file.php b/include/class.file.php
index a8e10eaf2181e7e092b3d63b822367e96183ee39..801f01097fbc3801072d00d8addefd1ac2325493 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -354,7 +354,7 @@ class AttachmentFile {
             if (!$bk->upload($file['tmp_name']))
                 return false;
         }
-        elseif (!$bk->write($file['data'])) {
+        elseif (!$bk->write($file['data']) || !$bk->flush()) {
             // XXX: Fallthrough to default backend if different?
             return false;
         }
@@ -384,12 +384,24 @@ class AttachmentFile {
      * True if the migration was successful and false otherwise.
      */
     function migrate($bk) {
-        $before = hash_init('sha1');
-        $after = hash_init('sha1');
 
         // Copy the file to the new backend and hash the contents
         $target = FileStorageBackend::lookup($bk, $this);
         $source = $this->open();
+
+        // Initialize hashing algorithm to verify uploaded contents
+        $algos = $target->getNativeHashAlgos();
+        $common_algo = 'sha1';
+        if ($algos && is_array($algos)) {
+            $supported = hash_algos();
+            foreach ($algos as $a) {
+                if (in_array(strtolower($a), $supported)) {
+                    $common_algo = strtolower($a);
+                    break;
+                }
+            }
+        }
+        $before = hash_init($common_algo);
         // TODO: Make this resumable so that if the file cannot be migrated
         //      in the max_execution_time, the migration can be continued
         //      the next time the cron runs
@@ -397,14 +409,20 @@ class AttachmentFile {
             hash_update($before, $block);
             $target->write($block);
         }
+        $target->flush();
+
+        // Ask the backend to generate its own hash if at all possible
+        if (!($target_hash = $target->getHashDigest($common_algo))) {
+            $after = hash_init($common_algo);
+            // Verify that the hash of the target file matches the hash of
+            // the source file
+            $target = FileStorageBackend::lookup($bk, $this);
+            while ($block = $target->read())
+                hash_update($after, $block);
+            $target_hash = hash_final($after);
+        }
 
-        // Verify that the hash of the target file matches the hash of the
-        // source file
-        $target = FileStorageBackend::lookup($bk, $this);
-        while ($block = $target->read())
-            hash_update($after, $block);
-
-        if (hash_final($before) != hash_final($after)) {
+        if (hash_final($before) != $target_hash) {
             $target->unlink();
             return false;
         }
@@ -618,6 +636,15 @@ class FileStorageBackend {
         return false;
     }
 
+    /**
+     * Called after all the blocks are sent to the ::write() method. This
+     * method should return boolean FALSE if flushing the data was
+     * somehow inhibited.
+     */
+    function flush() {
+        return true;
+    }
+
     /**
      * Upload a file to the backend. This method is preferred over ::write()
      * for files which are uploaded or are otherwise available out of
@@ -665,6 +692,26 @@ class FileStorageBackend {
     function unlink() {
         return false;
     }
+
+    /**
+     * Fetches a list of hash algorithms that are supported transparently
+     * through the ::write() and ::upload() methods. After writing or
+     * uploading file content, the ::getHashDigest($algo) method can be
+     * called to get a hash of the remote content without fetching the
+     * entire data stream to verify the content locally.
+     */
+    function getNativeHashAlgos() {
+        return array();
+    }
+
+    /**
+     * Returns a hash of the content calculated remotely by the storage
+     * backend. If this method fails, the hash chould be calculated by
+     * downloading the content and hashing locally
+     */
+    function getHashDigest($algo) {
+        return false;
+    }
 }