diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index c628059..59ec803 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -51,8 +51,11 @@ def check_password(password, encoded, setter=None, preferred='default'):
     preferred = get_hasher(preferred)
     hasher = identify_hasher(encoded)
 
-    must_update = hasher.algorithm != preferred.algorithm
+    must_update = (
+        hasher.algorithm != preferred.algorithm or
+        not hasher.is_current(encoded))
     is_correct = hasher.verify(password, encoded)
+    
     if setter and is_correct and must_update:
         setter(password)
     return is_correct
@@ -176,6 +179,13 @@ class BasePasswordHasher(object):
         raise ValueError("Hasher '%s' doesn't specify a library attribute" %
                          self.__class__)
 
+    def is_current(self):
+        """
+        Determines whether or not a password needs to be reencoded based on 
+        arbitrary criteria defined by the Hasher
+        """
+        return True
+
     def salt(self):
         """
         Generates a cryptographically secure nonce salt in ascii
@@ -216,9 +226,13 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
     safely but you must rename the algorithm if you change SHA256.
     """
     algorithm = "pbkdf2_sha256"
-    iterations = 10000
+    iterations = getattr(settings, 'PBKDF2_ITERATIONS', 10000)
     digest = hashlib.sha256
 
+    def is_current(self, encoded):
+        summary = self.safe_summary(encoded)
+        return int(summary['iterations']) == self.iterations
+
     def encode(self, password, salt, iterations=None):
         assert password
         assert salt and '$' not in salt
@@ -267,7 +281,11 @@ class BCryptPasswordHasher(BasePasswordHasher):
     """
     algorithm = "bcrypt"
     library = ("py-bcrypt", "bcrypt")
-    rounds = 12
+    rounds = getattr(settings, 'BCRYPT_ROUNDS', 12)
+
+    def is_current(self, encoded):
+        summary = self.safe_summary(encoded)
+        return int(summary['work factor']) == self.rounds
 
     def salt(self):
         bcrypt = self._load_library()
diff --git a/django/contrib/auth/tests/hashers.py b/django/contrib/auth/tests/hashers.py
index d867a57..c29d32b 100644
--- a/django/contrib/auth/tests/hashers.py
+++ b/django/contrib/auth/tests/hashers.py
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
 from django.conf.global_settings import PASSWORD_HASHERS as default_hashers
 from django.contrib.auth.hashers import (is_password_usable, 
     check_password, make_password, PBKDF2PasswordHasher, load_hashers,
-    PBKDF2SHA1PasswordHasher, get_hasher, identify_hasher, UNUSABLE_PASSWORD)
+    PBKDF2SHA1PasswordHasher, BCryptPasswordHasher, get_hasher, identify_hasher,
+    UNUSABLE_PASSWORD)
 from django.utils import unittest
 from django.utils.unittest import skipUnless
 
@@ -118,6 +119,44 @@ class TestUtilsHashPass(unittest.TestCase):
 'pbkdf2_sha1$10000$seasalt$91JiNKgwADC8j2j86Ije/cc4vfQ=')
         self.assertTrue(hasher.verify('letmein', encoded))
 
+    def test_pbkdf2_is_current_returns_false_if_iterations_differs(self):
+        hasher = PBKDF2PasswordHasher()
+        hasher.iterations = 1000
+        encoded = hasher.encode('letmein', 'seasalt')
+        hasher.iterations = 2000
+        self.assertFalse(hasher.is_current(encoded))
+
+    @skipUnless(bcrypt, "py-bcrypt not installed")
+    def test_bcrypt_is_current_returns_false_if_work_factor_differs(self):
+        hasher = BCryptPasswordHasher()
+        hasher.rounds = 2
+        encoded = hasher.encode('letmein', hasher.salt())
+        hasher.rounds = 3
+        self.assertFalse(hasher.is_current(encoded))
+
+    def test_check_password_setter_called_when_is_current_false(self):
+        class ExceptionSetterCalled(Exception):
+            pass
+        def setter(password):
+            raise ExceptionSetterCalled
+        raw_password = 'letmein'
+        hasher = PBKDF2PasswordHasher()
+        hasher.iterations = 1000
+        encoded = make_password(raw_password, hasher=hasher)
+        hasher.iterations = 2000
+        self.assertRaises(ExceptionSetterCalled,
+            check_password, *(raw_password, encoded), **{ 'setter': setter })
+
+    def test_check_password_setter_not_called_when_is_current_true(self):
+        class ExceptionSetterCalled(Exception):
+            pass
+        def setter(password):
+            raise ExceptionSetterCalled
+        raw_password = 'letmein'
+        hasher = PBKDF2PasswordHasher()
+        encoded = make_password(raw_password, hasher=hasher)
+        self.assertTrue(check_password(raw_password, encoded, setter))
+
     def test_upgrade(self):
         self.assertEqual('pbkdf2_sha256', get_hasher('default').algorithm)
         for algo in ('sha1', 'md5'):
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index a909c12..dc2097e 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -119,6 +119,13 @@ Default: 'auth.User'
 
 The model to use to represent a User. See :ref:`auth-custom-user`.
 
+BCRYPT_ROUNDS
+---------------
+
+Default: '12'
+
+The work factor used for generating bcrypt hashes when using Django's BCryptPasswordHasher.
+
 .. setting:: CACHES
 
 CACHES
@@ -1459,6 +1466,14 @@ The number of days a password reset link is valid for. Used by the
 
 .. setting:: PREPEND_WWW
 
+PBKDF2_ITERATIONS
+---------------
+
+Default: '10000'
+
+The number of iterations used when generating PBKDF2 hashes when using Django's 
+PBKDF2PasswordHasher.
+
 PREPEND_WWW
 -----------
 
