diff --git a/imagecache.module b/imagecache.module
index 4f31428..e545498 100644
--- a/imagecache.module
+++ b/imagecache.module
@@ -45,6 +45,11 @@ define('IMAGECACHE_STORAGE_DEFAULT', 1);
  */
 define('IMAGECACHE_STORAGE_OVERRIDE', 2);
 
+/**
+ * The name of the query parameter for image derivative tokens.
+ */
+define('IMAGECACHE_DERIVATIVE_TOKEN', 'itok');
+
 /*********************************************************************************************
  * Drupal Hooks
  *********************************************************************************************/
@@ -342,7 +347,7 @@ function imagecache_action_definition($action) {
  */
 function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = FALSE, $absolute = TRUE) {
   $args = array(
-    'query' => empty($bypass_browser_cache) ? NULL : time(),
+    'query' => empty($bypass_browser_cache) ? array() : array(time() => ''),
     // Little hack to avoid having language_url_rewrite() prefix the path with the
     // language code, but preserve the domain rewriting.
     'language' => (object) array('language' => '', 'domain' => $GLOBALS['language']->domain),
@@ -350,7 +355,8 @@ function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = F
   $file_directory = file_directory_path();
 
   // Determine the path of the derivative inside the files directory.
-  $derivative_path = 'imagecache/'. $presetname .'/'. _imagecache_strip_file_directory($filepath);
+  $imagepath = _imagecache_strip_file_directory($filepath);
+  $derivative_path = 'imagecache/'. $presetname .'/'. $imagepath;
 
   // Then construct a full path and see if anyone wants to alter it.
   $altered_path = $old_path = $file_directory .'/'. $derivative_path;
@@ -361,6 +367,10 @@ function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = F
     // ...but use url() so our $bypass_browser_cache parameter is honored.
     return url($altered_path, $args);
   }
+  // The token query is added even if the 'image_allow_insecure_derivatives'
+  // variable is TRUE, so that the emitted links remain valid if it is changed
+  // back to the default FALSE.
+  $args['query'][IMAGECACHE_DERIVATIVE_TOKEN] = imagecache_style_path_token($presetname, $imagepath);
 
   // It was unchanged so use the download method's prefix.
   $prefix = array(
@@ -373,6 +383,47 @@ function imagecache_create_url($presetname, $filepath, $bypass_browser_cache = F
 }
 
 /**
+ * Generates a token to protect an image style derivative.
+ *
+ * This prevents unauthorized generation of an image style derivative,
+ * which can be costly both in CPU time and disk space.
+ *
+ * @param $presetname
+ *   The name of the image style preset.
+ * @param $path
+ *   The path of the image.
+ *
+ * @return
+ *   An eight-character token which can be used to protect image style
+ *   derivatives against denial-of-service attacks.
+ */
+function imagecache_style_path_token($presetname, $path) {
+  // Drupal 6 may not yet have drupal_get_hash_salt().
+  global $db_query;
+  $salt = hash('sha256', serialize($db_query));
+  // Return the first eight characters.
+  return substr(_imagecache_hmac_base64($presetname . ':' . $path, drupal_get_private_key() . $salt), 0, 8);
+}
+
+function _imagecache_hmac_base64($data, $key) {
+  // Keys longer than 64 bytes are first hashed.
+  if (strlen($key) > 64) {
+    $key = sha1($key);
+  }
+
+  $key = str_pad($key, 64, chr(0x00));
+  $ipad = str_repeat(chr(0x36), 64);
+  $opad = str_repeat(chr(0x5c), 64);
+
+  $ipad = $ipad ^ $key;
+  $opad = $opad ^ $key;
+
+  $hmac = base64_encode(sha1($opad . pack("H*", sha1($ipad . $data))));
+  // Modify the hmac so it's safe to use in URLs.
+  return strtr($hmac, array('+' => '-', '/' => '_', '=' => ''));
+}
+
+/**
  * Return a file system location that points to the location of a derivative
  * of the original image at @p $path, transformed with the given @p $preset.
  * Keep in mind that the image might not yet exist and won't be created.
@@ -435,9 +486,16 @@ function imagecache_cache_private() {
  *   ImageCache generate images but not send them to a browser.
  */
 function _imagecache_cache($presetname, $path) {
-  if (!$preset = imagecache_preset_by_name($presetname)) {
-    // Send a 404 if we don't know of a preset.
-    header("HTTP/1.0 404 Not Found");
+  $preset = imagecache_preset_by_name($presetname);
+
+  $valid = !empty($preset);
+  if (!variable_get('image_allow_insecure_derivatives', FALSE)) {
+    $valid = $valid && isset($_GET[IMAGECACHE_DERIVATIVE_TOKEN]) && $_GET[IMAGECACHE_DERIVATIVE_TOKEN] === imagecache_style_path_token($preset['presetname'], $path);
+  }
+
+  if (!$valid) {
+    // Send a 403 if the presented token isn't invalid.
+    header('HTTP/1.0 403 Forbidden');
     exit;
   }
 
