diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index f0ef124..deb6cb9 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -1,7 +1,6 @@
 from django import forms
 from django.forms.util import flatatt
 from django.template import loader
-from django.utils.encoding import smart_str
 from django.utils.http import int_to_base36
 from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext, ugettext_lazy as _
@@ -26,8 +25,6 @@ class ReadOnlyPasswordHashWidget(forms.Widget):
 
         final_attrs = self.build_attrs(attrs)
 
-        encoded = smart_str(encoded)
-
         if len(encoded) == 32 and '$' not in encoded:
             algorithm = 'unsalted_md5'
         else:
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 52d204c..11a6313 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -40,9 +40,6 @@ def check_password(password, encoded, setter=None, preferred='default'):
         return False
 
     preferred = get_hasher(preferred)
-    raw_password = password
-    password = smart_str(password)
-    encoded = smart_str(encoded)
 
     if len(encoded) == 32 and '$' not in encoded:
         hasher = get_hasher('unsalted_md5')
@@ -53,7 +50,7 @@ def check_password(password, encoded, setter=None, preferred='default'):
     must_update = hasher.algorithm != preferred.algorithm
     is_correct = hasher.verify(password, encoded)
     if setter and is_correct and must_update:
-        setter(raw_password)
+        setter(password)
     return is_correct
 
 
@@ -69,11 +66,9 @@ def make_password(password, salt=None, hasher='default'):
         return UNUSABLE_PASSWORD
 
     hasher = get_hasher(hasher)
-    password = smart_str(password)
 
     if not salt:
         salt = hasher.salt()
-    salt = smart_str(salt)
 
     return hasher.encode(password, salt)
 
@@ -291,7 +286,7 @@ class SHA1PasswordHasher(BasePasswordHasher):
     def encode(self, password, salt):
         assert password
         assert salt and '$' not in salt
-        hash = hashlib.sha1(salt + password).hexdigest()
+        hash = hashlib.sha1(smart_str(salt + password)).hexdigest()
         return "%s$%s$%s" % (self.algorithm, salt, hash)
 
     def verify(self, password, encoded):
@@ -319,7 +314,7 @@ class MD5PasswordHasher(BasePasswordHasher):
     def encode(self, password, salt):
         assert password
         assert salt and '$' not in salt
-        hash = hashlib.md5(salt + password).hexdigest()
+        hash = hashlib.md5(smart_str(salt + password)).hexdigest()
         return "%s$%s$%s" % (self.algorithm, salt, hash)
 
     def verify(self, password, encoded):
@@ -353,7 +348,7 @@ class UnsaltedMD5PasswordHasher(BasePasswordHasher):
         return ''
 
     def encode(self, password, salt):
-        return hashlib.md5(password).hexdigest()
+        return hashlib.md5(smart_str(password)).hexdigest()
 
     def verify(self, password, encoded):
         encoded_2 = self.encode(password, '')
diff --git a/django/utils/crypto.py b/django/utils/crypto.py
index 9d6486c..0fce060 100644
--- a/django/utils/crypto.py
+++ b/django/utils/crypto.py
@@ -22,6 +22,7 @@ except NotImplementedError:
     using_sysrandom = False
 
 from django.conf import settings
+from django.utils.encoding import smart_str
 
 
 _trans_5c = b"".join([chr(x ^ 0x5C) for x in xrange(256)])
@@ -137,6 +138,8 @@ def pbkdf2(password, salt, iterations, dklen=0, digest=None):
     assert iterations > 0
     if not digest:
         digest = hashlib.sha256
+    password = smart_str(password)
+    salt = smart_str(salt)
     hlen = digest().digest_size
     if not dklen:
         dklen = hlen
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index 0d86a52..696f332 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -128,6 +128,15 @@ If you were using the ``data`` parameter in a PUT request without a
 ``content_type``, you must encode your data before passing it to the test
 client and set the ``content_type`` argument.
 
+String types of hasher method parameters
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you have written a :ref:`custom password hasher <auth_password_storage>`,
+your ``encode()``, ``verify()`` or ``safe_summary()`` methods should accept
+Unicode parameters (``password``, ``salt`` or ``encoded``). If any of the
+hashing methods need byte strings, you can use the
+:func:`~django.utils.encoding.smart_str` utility to encode the strings.
+
 Features deprecated in 1.5
 ==========================
 
