diff --git a/crop.module b/crop.module
index 0aad0d1..b3ede09 100644
--- a/crop.module
+++ b/crop.module
@@ -8,6 +8,9 @@
  */
 
 use \Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\crop\Entity\Crop;
+use Drupal\image\Entity\ImageStyle;
 use \Drupal\media_entity\MediaBundleInterface;
 use \Drupal\file\FileInterface;
 
@@ -108,3 +111,47 @@ function crop_file_delete(FileInterface $file) {
     $crop->delete();
   }
 }
+
+/**
+ * Implements hook_file_url_alter().
+ */
+function crop_file_url_alter(&$uri) {
+  // Process only files that are stored in "styles" directory.
+  if (strpos($uri, PublicStream::basePath() . '/styles/') !== FALSE) {
+    // Handle the case of multiple "/styles/" in URI.
+    $uri_parts = explode('/styles/', $uri);
+    $image_style_part = end($uri_parts);
+    // Match image style, schema, file subdirectory and file name.
+    preg_match("/(.*?)\/(.*?)\/(.*+)/", $image_style_part, $match);
+    // Get the image style ID.
+    $image_style = $match[1];
+    // Get the file path without query parameter.
+    $parsed_uri = parse_url($match[3]);
+    // Get the file URI using parsed schema and file path.
+    $file_uri = $match[2] . '://' . $parsed_uri['path'];
+
+    /** @var \Drupal\image\Entity\ImageStyle $image_style */
+    if (!$image_style = ImageStyle::load($image_style)) {
+      return;
+    }
+
+    $crop_type = NULL;
+    // Find whether matched image style uses "crop_type" effect.
+    /* @var  \Drupal\image\ImageEffectInterface $effect */
+    foreach ($image_style->getEffects() as $uuid => $effect) {
+      $data_effect = $image_style->getEffect($uuid)->getConfiguration()['data'];
+      if (isset($data_effect['crop_type'])) {
+        $crop_type = $data_effect['crop_type'];
+        break;
+      }
+    }
+
+    // In case the image style uses "crop_type" effect, load the crop entity.
+    if ($crop_type && $crop = Crop::findCrop($file_uri, $crop_type)) {
+      // Found a crop for this image, append a hash of it to the URL,
+      // so that browsers reload the image and CDNs and proxies can be bypassed.
+      $shortened_hash = substr(md5(implode($crop->position()) . implode($crop->anchor())), 0, 8);
+      $uri .= '&h=' . $shortened_hash;
+    }
+  }
+}
diff --git a/src/Tests/CropFunctionalTest.php b/src/Tests/CropFunctionalTest.php
index 2c8f14c..8dd93b3 100644
--- a/src/Tests/CropFunctionalTest.php
+++ b/src/Tests/CropFunctionalTest.php
@@ -2,6 +2,10 @@
 
 namespace Drupal\crop\Tests;
 
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\crop\Entity\Crop;
+use Drupal\crop\Entity\CropType;
+use Drupal\file\Entity\File;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -16,7 +20,7 @@ class CropFunctionalTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = ['crop'];
+  public static $modules = ['crop', 'file'];
 
   /**
    * Admin user.
@@ -79,13 +83,15 @@ class CropFunctionalTest extends WebTestBase {
     $this->assertUrl('admin/config/media/crop/add');
 
     // Create crop type.
+    $crop_type_id = strtolower($this->randomMachineName());
     $edit = [
-      'id' => strtolower($this->randomMachineName()),
+      'id' => $crop_type_id,
       'label' => $this->randomMachineName(),
       'description' => $this->randomGenerator->sentences(10),
     ];
     $this->drupalPostForm('admin/config/media/crop/add', $edit, t('Save crop type'));
     $this->assertRaw(t('The crop type %name has been added.', ['%name' => $edit['label']]));
+    $this->cropType = CropType::load($crop_type_id);
     $this->assertUrl('admin/config/media/crop');
     $label = $this->xpath("//td[contains(concat(' ',normalize-space(@class),' '),' menu-label ')]");
     $this->assert(strpos($label[0]->asXML(), $edit['label']) !== FALSE, 'Crop type label found on listing page.');
@@ -110,6 +116,10 @@ class CropFunctionalTest extends WebTestBase {
     $effect_configuration = $this->testStyle->getEffects()->getIterator()->current()->getConfiguration();
     $this->assertEqual($effect_configuration['data'], ['crop_type' => $edit['id']], 'Manual crop effect uses correct image style.');
 
+    // Tests the image URI is extended with shortened hash in case of image
+    // style and corresponding crop existence.
+    $this->doTestFileUriAlter();
+
     // Try to access edit form as anonymous user.
     $this->drupalLogout();
     $this->drupalGet('admin/config/media/crop/manage/' . $edit['id']);
@@ -130,4 +140,56 @@ class CropFunctionalTest extends WebTestBase {
     $this->assertText(t('No crop types available.'));
   }
 
+  /**
+   * Asserts a shortened hash is added to the file URI.
+   *
+   * Tests crop_file_url_alter().
+   */
+  protected function doTestFileUriAlter() {
+    // Get the test file.
+    file_unmanaged_copy(drupal_get_path('module', 'crop') . '/tests/files/sarajevo.png', PublicStream::basePath());
+    $file_uri = 'public://sarajevo.png';
+    $file = File::create(['uri' => $file_uri, 'status' => FILE_STATUS_PERMANENT]);
+    $file->save();
+
+    /** @var \Drupal\crop\CropInterface $crop */
+    $values = [
+      'type' => $this->cropType->id(),
+      'entity_id' => $file->id(),
+      'entity_type' => $file->getEntityTypeId(),
+      'uri' => 'public://sarajevo.png',
+      'x' => '100',
+      'y' => '150',
+      'width' => '200',
+      'height' => '250',
+    ];
+    $crop = Crop::create($values);
+    $crop->save();
+
+    // Build an image style derivative for the file URI.
+    $image_style_uri = $this->testStyle->buildUri($file_uri);
+    // Build an image style URL.
+    $image_style_url = $this->testStyle->buildUrl($image_style_uri);
+    // This triggers crop_file_url_alter().
+    $altered_image_style_url = file_create_url($image_style_url);
+
+    $shortened_hash = substr(md5(implode($crop->position()) . implode($crop->anchor())), 0, 8);
+    $this->assertTrue(strpos($altered_image_style_url, $shortened_hash) !== FALSE, 'The image style URL contains a shortened hash.');
+
+    // Update the crop to assert the hash has changed.
+    $crop->setPosition('80', '80')->save();
+    $old_hash = $shortened_hash;
+    $new_hash = substr(md5(implode($crop->position()) . implode($crop->anchor())), 0, 8);
+    $altered_image_style_url = file_create_url($image_style_url);
+    $this->assertFalse(strpos($altered_image_style_url, $old_hash) !== FALSE, 'The image style URL does not contain the old hash.');
+    $this->assertTrue(strpos($altered_image_style_url, $new_hash) !== FALSE, 'The image style URL contains an updated hash.');
+
+    // Delete the file and the crop entity associated,
+    // the crop entity are auto cleaned by crop_file_delete().
+    $file->delete();
+
+    // Check if crop entity are correctly deleted.
+    $this->assertFalse(Crop::cropExists($file_uri), 'The Crop entity are correctly deleted after file delete.');
+  }
+
 }
