Index: django/contrib/admin/templates/widget/password.html
===================================================================
--- django/contrib/admin/templates/widget/password.html	(revision 0)
+++ django/contrib/admin/templates/widget/password.html	(revision 0)
@@ -0,0 +1,8 @@
+{% load i18n %}
+<p> 
+   {{ bound_field.form_fields.0 }} <span class="help">{% trans "Enter new password (or leave blank to keep existing password)." %}</span><br />
+   {{ bound_field.form_fields.1 }} <span class="help">{% trans "Confirm new password." %}</span>
+</p>
+<p>
+   {{ bound_field.form_fields.2 }}
+</p>
\ No newline at end of file
Index: django/contrib/auth/models.py
===================================================================
--- django/contrib/auth/models.py	(revision 3467)
+++ django/contrib/auth/models.py	(working copy)
@@ -19,6 +19,13 @@
         return hsh == sha.new(salt+raw_password).hexdigest()
     raise ValueError, "Got unknown password algorithm type in password."
 
+def encrypt_password(raw_password):
+    import sha, random
+    algo = 'sha1'
+    salt = sha.new(str(random.random())).hexdigest()[:5]
+    hsh = sha.new(salt+raw_password).hexdigest()
+    return '%s$%s$%s' % (algo, salt, hsh)
+
 class SiteProfileNotAvailable(Exception):
     pass
 
@@ -91,7 +98,7 @@
     first_name = models.CharField(_('first name'), maxlength=30, blank=True)
     last_name = models.CharField(_('last name'), maxlength=30, blank=True)
     email = models.EmailField(_('e-mail address'), blank=True)
-    password = models.CharField(_('password'), maxlength=128, help_text=_("Use '[algo]$[salt]$[hexdigest]'"))
+    password = models.PasswordField(_('password'), maxlength=128, show_raw=True, encrypt_func=encrypt_password, help_text=_("Above is the encrypted password. Use '[algo]$[salt]$[hexdigest]'."))
     is_staff = models.BooleanField(_('staff status'), help_text=_("Designates whether the user can log into this admin site."))
     is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user can log into the Django admin. Unselect this instead of deleting accounts."))
     is_superuser = models.BooleanField(_('superuser status'), help_text=_("Designates that this user has all permissions without explicitly assigning them."))
@@ -138,11 +145,7 @@
         return full_name.strip()
 
     def set_password(self, raw_password):
-        import sha, random
-        algo = 'sha1'
-        salt = sha.new(str(random.random())).hexdigest()[:5]
-        hsh = sha.new(salt+raw_password).hexdigest()
-        self.password = '%s$%s$%s' % (algo, salt, hsh)
+        self.password = encrypt_pasword(raw_password)
 
     def check_password(self, raw_password):
         """
Index: django/db/backends/ado_mssql/creation.py
===================================================================
--- django/db/backends/ado_mssql/creation.py	(revision 3467)
+++ django/db/backends/ado_mssql/creation.py	(working copy)
@@ -14,6 +14,7 @@
     'ManyToManyField':   None,
     'NullBooleanField':  'bit',
     'OneToOneField':     'int',
+    'PasswordField':     'varchar(%(maxlength)s)',
     'PhoneNumberField':  'varchar(20)',
     'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
     'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
Index: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(revision 3467)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -18,6 +18,7 @@
     'ManyToManyField':   None,
     'NullBooleanField':  'bool',
     'OneToOneField':     'integer',
+    'PasswordField':     'varchar(%(maxlength)s)',
     'PhoneNumberField':  'varchar(20)',
     'PositiveIntegerField': 'integer UNSIGNED',
     'PositiveSmallIntegerField': 'smallint UNSIGNED',
Index: django/db/backends/oracle/creation.py
===================================================================
--- django/db/backends/oracle/creation.py	(revision 3467)
+++ django/db/backends/oracle/creation.py	(working copy)
@@ -14,6 +14,7 @@
     'ManyToManyField':   None,
     'NullBooleanField':  'integer',
     'OneToOneField':     'integer',
+    'PasswordField':     'varchar(%(maxlength)s)',
     'PhoneNumberField':  'varchar(20)',
     'PositiveIntegerField': 'integer',
     'PositiveSmallIntegerField': 'smallint',
Index: django/db/backends/postgresql/creation.py
===================================================================
--- django/db/backends/postgresql/creation.py	(revision 3467)
+++ django/db/backends/postgresql/creation.py	(working copy)
@@ -18,6 +18,7 @@
     'ManyToManyField':   None,
     'NullBooleanField':  'boolean',
     'OneToOneField':     'integer',
+    'PasswordField':     'varchar(%(maxlength)s)',
     'PhoneNumberField':  'varchar(20)',
     'PositiveIntegerField': 'integer CHECK ("%(column)s" >= 0)',
     'PositiveSmallIntegerField': 'smallint CHECK ("%(column)s" >= 0)',
Index: django/db/backends/sqlite3/creation.py
===================================================================
--- django/db/backends/sqlite3/creation.py	(revision 3467)
+++ django/db/backends/sqlite3/creation.py	(working copy)
@@ -17,6 +17,7 @@
     'ManyToManyField':              None,
     'NullBooleanField':             'bool',
     'OneToOneField':                'integer',
+    'PasswordField':                'varchar(%(maxlength)s)',
     'PhoneNumberField':             'varchar(20)',
     'PositiveIntegerField':         'integer unsigned',
     'PositiveSmallIntegerField':    'smallint unsigned',
Index: django/db/models/fields/__init__.py
===================================================================
--- django/db/models/fields/__init__.py	(revision 3467)
+++ django/db/models/fields/__init__.py	(working copy)
@@ -385,6 +385,76 @@
                 raise validators.ValidationError, gettext_lazy("This field cannot be null.")
         return str(value)
 
+class PasswordField(CharField):
+    def __init__(self, *args, **kwargs):
+        self.show_raw = kwargs.pop('show_raw', False)
+        self.encrypt_func = kwargs.pop('encrypt_func', None)
+        if not self.show_raw:
+            # Since raw will be a hidden field in this case, we can't use maxlength.
+            # The other solution is adding maxlength=None to HiddenField.__init__ in django.forms.
+            kwargs.pop('maxlength')
+        super(PasswordField, self).__init__(*args, **kwargs)
+
+    def get_manipulator_field_objs(self):
+        return [forms.PasswordField,
+                forms.PasswordField,
+                self.show_raw and forms.TextField or forms.HiddenField]
+
+    def get_manipulator_field_names(self, name_prefix):
+        return [name_prefix + self.name,
+                name_prefix + self.name + '_confirm',
+                name_prefix + self.name + '_raw']
+
+    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
+        field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
+        if not self.blank:
+            # TODO: Making this work in a related context is over my head.
+            v = validators.AlwaysMatchesOtherField(field_list[1].field_name, gettext_lazy('New password and confirm password fields must match.'))
+            field_list[0].validator_list.append(v)
+            field_list[0].is_required = field_list[1].is_required = False
+            #field_list[0].maxlength = field_list[1].maxlength = self.maxlength
+            field_list[0].length = field_list[1].length = 10
+            field_list[2].length = 60
+        return field_list
+
+    def get_manipulator_new_data(self, new_data, rel=False):
+        change, confirm, raw = self.get_manipulator_field_names('')
+        if rel:
+            # TODO: I don't know if this rel stuff is right.
+            change = new_data.get(change, [None])[0]
+            confirm = new_data.get(confirm, [None])[0]
+            raw = new_data.get(raw, [None])[0]
+        else:
+            change = new_data.get(change, None)
+            confirm = new_data.get(confirm, None)
+            raw = new_data.get(raw, None)
+        # The change == confirm test should be done by the validator, but
+        # it can't hurt to check again.
+        if change and change == confirm:
+            return self.encrypt(change)
+        if raw is not None:
+            return raw
+        return self.get_default()
+
+    def flatten_data(self,follow, obj = None):
+        val = self._get_val_from_obj(obj)
+        change, confirm, raw = self.get_manipulator_field_names('')
+        return {raw: val,
+                change: '',
+                confirm: ''}
+    
+    def encrypt(self, raw_password):
+        """
+        Attempt to encrypt password using the encrypt_func function.
+        The function should take only one argument: the raw password.
+        """
+        if callable(self.encrypt_func):
+            try:
+                return self.encrypt_func(raw_password)
+            except TypeError:  # Most likely the function received incorrect parameters.
+                pass
+        return raw_password
+
 # TODO: Maybe move this into contrib, because it's specialized.
 class CommaSeparatedIntegerField(CharField):
     def get_manipulator_field_objs(self):
Index: docs/model-api.txt
===================================================================
--- docs/model-api.txt	(revision 3467)
+++ docs/model-api.txt	(working copy)
@@ -322,6 +322,38 @@
 
 The admin represents this as a ``<select>`` box with "Unknown", "Yes" and "No" choices.
 
+``PasswordField``
+~~~~~~~~~~~~~~~~~  
+
+A ``PasswordField`` is similar to ``TextField`` but the characters that are
+entered are masked, typically by asterisks (*), when entered into a form. Note
+that though the data is masked on entry, it is sent as clear text to the
+server and stored as plain text in the database. Additional measures (such as
+using HTTPS) are needed to ensure the security of data sent from a form.
+
+The user is required to enter the same password in a password confirmation box
+to ensure the password was correctly entered.
+
+Has a two optional arguments:
+
+    ======================  ===================================================
+    Argument                Description
+    ======================  ===================================================
+    ``show_raw``            Either ``True`` or ``False``. Shows the existing
+                            password value as stored in the database in a text
+                            input box. Defaults to ``False``, which passes the
+                            existing password as a hidden input value.
+
+    ``encrypt_func``        An optional function to encrypt a new passwords.
+                            The function should take one argument (the raw
+                            password) and return the encrypted password.
+                            This happens on the server when a new password is
+                            entered.
+    ======================  ===================================================
+
+If ``show_raw`` is set to ``True``, the user can enter a password in to the raw
+text input box without it being processed by ``encrypt_func``.
+
 ``PhoneNumberField``
 ~~~~~~~~~~~~~~~~~~~~
 
