diff --git a/cms/envs/aws.py b/cms/envs/aws.py
index 72f8d0ca7ce584accb8b05dd38760af5df44b5df..25d646ed1e4f28b1e477a93ffab82d2cef2fbde9 100644
--- a/cms/envs/aws.py
+++ b/cms/envs/aws.py
@@ -447,11 +447,7 @@ MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_AL
 MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS", 15 * 60)
 
 #### PASSWORD POLICY SETTINGS #####
-PASSWORD_MIN_LENGTH = ENV_TOKENS.get("PASSWORD_MIN_LENGTH")
-PASSWORD_MAX_LENGTH = ENV_TOKENS.get("PASSWORD_MAX_LENGTH")
-PASSWORD_COMPLEXITY = ENV_TOKENS.get("PASSWORD_COMPLEXITY", {})
-PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD = ENV_TOKENS.get("PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD")
-PASSWORD_DICTIONARY = ENV_TOKENS.get("PASSWORD_DICTIONARY", [])
+AUTH_PASSWORD_VALIDATORS = ENV_TOKENS.get("AUTH_PASSWORD_VALIDATORS", [])
 
 ### INACTIVITY SETTINGS ####
 SESSION_INACTIVITY_TIMEOUT_IN_SECONDS = AUTH_TOKENS.get("SESSION_INACTIVITY_TIMEOUT_IN_SECONDS")
diff --git a/common/djangoapps/student/forms.py b/common/djangoapps/student/forms.py
index 8ca0a6f4c9317056157cd8df5f09de76bf61b29a..c43b10ca495b3e12b65ad42aed4cd967ee8abd9f 100644
--- a/common/djangoapps/student/forms.py
+++ b/common/djangoapps/student/forms.py
@@ -27,7 +27,7 @@ from openedx.core.djangoapps.user_api import accounts as accounts_settings
 from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
 from student.message_types import PasswordReset
 from student.models import CourseEnrollmentAllowed, email_exists_or_retired
-from util.password_policy_validators import password_max_length, password_min_length, validate_password
+from util.password_policy_validators import edX_validate_password
 
 
 def send_password_reset_email_for_user(user, request):
@@ -193,7 +193,6 @@ class AccountCreationForm(forms.Form):
     """
 
     _EMAIL_INVALID_MSG = _("A properly formatted e-mail is required")
-    _PASSWORD_INVALID_MSG = _("A valid password is required")
     _NAME_TOO_SHORT_MSG = _("Your legal name must be a minimum of two characters long")
 
     # TODO: Resolve repetition
@@ -209,15 +208,9 @@ class AccountCreationForm(forms.Form):
             "max_length": _("Email cannot be more than %(limit_value)s characters long"),
         }
     )
-    password = forms.CharField(
-        min_length=password_min_length(),
-        max_length=password_max_length(),
-        error_messages={
-            "required": _PASSWORD_INVALID_MSG,
-            "min_length": _PASSWORD_INVALID_MSG,
-            "max_length": _PASSWORD_INVALID_MSG,
-        }
-    )
+
+    password = forms.CharField()
+
     name = forms.CharField(
         min_length=accounts_settings.NAME_MIN_LENGTH,
         error_messages={
@@ -288,7 +281,12 @@ class AccountCreationForm(forms.Form):
         """Enforce password policies (if applicable)"""
         password = self.cleaned_data["password"]
         if self.enforce_password_policy:
-            validate_password(password, username=self.cleaned_data.get('username'))
+            # Creating a temporary user object to test password against username
+            # This user should NOT be saved
+            username = self.cleaned_data.get('username')
+            email = self.cleaned_data.get('email')
+            temp_user = User(username=username, email=email) if username else None
+            edX_validate_password(password, temp_user)
         return password
 
     def clean_email(self):
diff --git a/common/djangoapps/student/views/management.py b/common/djangoapps/student/views/management.py
index e01f0fb2a254d694345867241e2c22cdf388774b..47ce54bf309607408d0a8c8be7ec1b46d6ed3f8a 100644
--- a/common/djangoapps/student/views/management.py
+++ b/common/djangoapps/student/views/management.py
@@ -94,7 +94,7 @@ from student.text_me_the_app import TextMeTheAppFragmentView
 from util.bad_request_rate_limiter import BadRequestRateLimiter
 from util.db import outer_atomic
 from util.json_request import JsonResponse
-from util.password_policy_validators import SecurityPolicyError, validate_password
+from util.password_policy_validators import edX_validate_password
 
 log = logging.getLogger("edx.student")
 
@@ -830,7 +830,7 @@ def password_reset_confirm_wrapper(request, uidb36=None, token=None):
         password = request.POST['new_password1']
 
         try:
-            validate_password(password, user=user)
+            edX_validate_password(password, user=user)
         except ValidationError as err:
             # We have a password reset attempt which violates some security
             # policy, or any other validation. Use the existing Django template to communicate that
diff --git a/common/djangoapps/util/password_policy_validators.py b/common/djangoapps/util/password_policy_validators.py
index d7c0138fb39b52ef7a3411ce7f5759b55feb8556..ded8d0040dd0c7d130fd33365b332d2720897c7e 100644
--- a/common/djangoapps/util/password_policy_validators.py
+++ b/common/djangoapps/util/password_policy_validators.py
@@ -1,322 +1,450 @@
 """
-This file exposes a number of password complexity validators which can be optionally added to
+This file exposes a number of password validators which can be optionally added to
 account creation
-
-This file was inspired by the django-passwords project at https://github.com/dstufft/django-passwords
-authored by dstufft (https://github.com/dstufft)
 """
-from __future__ import division
+from __future__ import unicode_literals
 
 import logging
-import string
 import unicodedata
 
 from django.conf import settings
+from django.contrib.auth.password_validation import (
+    get_default_password_validators,
+    validate_password,
+    MinimumLengthValidator as DjangoMinimumLengthValidator,
+)
 from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext_lazy as _
-from django.utils.translation import ungettext_lazy as ungettext
-from Levenshtein import distance
+from django.utils.translation import ugettext as _, ungettext
 from six import text_type
 
 from student.models import PasswordHistory
 
-
 log = logging.getLogger(__name__)
 
-# In description order
-_allowed_password_complexity = [
-    'ALPHABETIC',
-    'UPPER',
-    'LOWER',
-    'NUMERIC',
-    'DIGITS',
-    'PUNCTUATION',
-    'NON ASCII',
-    'WORDS',
-]
-
 
-class SecurityPolicyError(ValidationError):
-    pass
+def password_validators_instruction_texts():
+    """
+    Return a string of instruction texts of all configured validators.
+    Expects at least the MinimumLengthValidator to be defined.
+    """
+    complexity_instructions = []
+    # For clarity in the printed instructions, the minimum length instruction
+    # is separated from the complexity instructions.
+    length_instruction = ''
+    password_validators = get_default_password_validators()
+    for validator in password_validators:
+        if hasattr(validator, 'get_instruction_text'):
+            text = validator.get_instruction_text()
+            if isinstance(validator, MinimumLengthValidator):
+                length_instruction = text
+            else:
+                complexity_instructions.append(text)
+    if complexity_instructions:
+        return _('Your password must contain {length_instruction}, including {complexity_instructions}.').format(
+            length_instruction=length_instruction,
+            complexity_instructions=' & '.join(complexity_instructions)
+        )
+    else:
+        return _('Your password must contain {length_instruction}.'.format(length_instruction=length_instruction))
 
 
-def password_min_length():
+def password_validators_restrictions():
     """
-    Returns minimum required length of a password.
-    Can be overridden by site configuration of PASSWORD_MIN_LENGTH.
+    Return a dictionary of complexity restrictions to be used by mobile users on
+    the registration form
     """
-    min_length = getattr(settings, 'PASSWORD_MIN_LENGTH', None)
-    if min_length is None:
-        return 2  # Note: This default is simply historical
-    return min_length
+    password_validators = get_default_password_validators()
+    complexity_restrictions = dict(validator.get_restriction()
+                                   for validator in password_validators
+                                   if hasattr(validator, 'get_restriction')
+                                   )
+    return complexity_restrictions
 
 
-def password_max_length():
+def edX_validate_password(password, user=None):
     """
-    Returns maximum allowed length of a password. If zero, no maximum.
-    Can be overridden by site configuration of PASSWORD_MAX_LENGTH.
+    EdX's custom password validator for passwords. This function performs the
+    following functions:
+        1) Converts the password to unicode if it is not already
+        2) Calls Django's validate_password method. This calls the validate function
+            in all validators specified in AUTH_PASSWORD_VALIDATORS configuration.
+
+    Parameters:
+        password (str or unicode): the user's password to be validated
+        user (django.contrib.auth.models.User): The user object to use for validating
+        the given password against the username and/or email.
+
+    Returns:
+        None
+
+    Raises:
+        ValidationError if unable to convert password to utf8 or if any of the
+        password validators fail.
     """
-    # Note: The default value here is simply historical
-    max_length = getattr(settings, 'PASSWORD_MAX_LENGTH', None)
-    if max_length is None:
-        return 75  # Note: This default is simply historical
-    return max_length
+    if not isinstance(password, text_type):
+        try:
+            # some checks rely on unicode semantics (e.g. length)
+            password = text_type(password, encoding='utf8')
+        except UnicodeDecodeError:
+            # no reason to get into weeds
+            raise ValidationError([_('Invalid password.')])
 
+    validate_password(password, user)
 
-def password_complexity():
-    """
-    :return: A dict of complexity requirements from settings
+
+def _validate_condition(password, fn, min_count):
     """
-    complexity = {}
-    if settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
-        complexity = getattr(settings, 'PASSWORD_COMPLEXITY', {})
+    Validates the password using the given function. This is performed by
+    iterating through each character in the password and counting up the number
+    of characters that satisfy the function.
 
-    valid_complexity = {x: y for x, y in complexity.iteritems() if x in _allowed_password_complexity}
+    Parameters:
+        password (str): the password
+        fn: the function to be tested against the string.
+        min_count (int): the minimum number of characters that must satisfy the function
 
-    if not password_complexity.logged:
-        invalid = frozenset(complexity.keys()) - frozenset(valid_complexity.keys())
-        for key in invalid:
-            log.warning('Unrecognized %s value in PASSWORD_COMPLEXITY setting.', key)
-        password_complexity.logged = True
+    Return:
+        True if valid_count >= min_count, else False
+    """
+    valid_count = len([c for c in password if fn(c)])
+    return valid_count >= min_count
 
-    return valid_complexity
 
+class MinimumLengthValidator(DjangoMinimumLengthValidator):
+    def get_instruction_text(self):
+        return ungettext(
+            'at least %(min_length)d character',
+            'at least %(min_length)d characters',
+            self.min_length
+        ) % {'min_length': self.min_length}
 
-# Declare static variable for the function above, which helps avoid issuing multiple log warnings.
-# We don't instead keep a cached version of the complexity rules, because that might trip up unit tests.
-password_complexity.logged = False
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_length', self.min_length
 
 
-def _password_complexity_descriptions(which=None):
+class MaximumLengthValidator(object):
     """
-    which: A list of which complexities to describe, None if you want the configured ones
-    :return: A list of complexity descriptions
+    Validate whether the password is shorter than a maximum length.
+
+    Parameters:
+        max_length (int): the maximum number of characters to require in the password.
     """
-    descs = []
-    complexity = password_complexity()
-    if which is None:
-        which = complexity.keys()
-
-    for key in _allowed_password_complexity:  # we iterate over allowed keys so that we get the order right
-        value = complexity.get(key, 0) if key in which else 0
-        if not value:
-            continue
-
-        if key == 'ALPHABETIC':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} letter', '{num} letters', value).format(num=value))
-        elif key == 'UPPER':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} uppercase letter', '{num} uppercase letters', value).format(num=value))
-        elif key == 'LOWER':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} lowercase letter', '{num} lowercase letters', value).format(num=value))
-        elif key == 'DIGITS':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} digit', '{num} digits', value).format(num=value))
-        elif key == 'NUMERIC':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} number', '{num} numbers', value).format(num=value))
-        elif key == 'PUNCTUATION':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} punctuation mark', '{num} punctuation marks', value).format(num=value))
-        elif key == 'NON ASCII':  # note that our definition of non-ascii is non-letter, non-digit, non-punctuation
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} symbol', '{num} symbols', value).format(num=value))
-        elif key == 'WORDS':
-            # Translators: This appears in a list of password requirements
-            descs.append(ungettext('{num} word', '{num} words', value).format(num=value))
-        else:
-            raise Exception('Unexpected complexity value {}'.format(key))
+    def __init__(self, max_length=75):
+        self.max_length = max_length
+
+    def validate(self, password, user=None):
+        if len(password) > self.max_length:
+            raise ValidationError(
+                ungettext(
+                    'This password is too long. It must contain no more than %(max_length)d character.',
+                    'This password is too long. It must contain no more than %(max_length)d characters.',
+                    self.max_length
+                ),
+                code='password_too_long',
+                params={'max_length': self.max_length},
+            )
+
+    def get_help_text(self):
+        return ungettext(
+            'Your password must contain no more than %(max_length)d character.',
+            'Your password must contain no more than %(max_length)d characters.',
+            self.max_length
+        ) % {'max_length': self.max_length}
 
-    return descs
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'max_length', self.max_length
 
 
-def password_instructions():
+class AlphabeticValidator(object):
     """
-    :return: A string suitable for display to the user to tell them what password to enter
+    Validate whether the password contains at least min_alphabetic letters.
+
+    Parameters:
+        min_alphabetic (int): the minimum number of alphabetic characters to require
+            in the password. Must be >= 0.
     """
-    min_length = password_min_length()
-    reqs = _password_complexity_descriptions()
+    def __init__(self, min_alphabetic=0):
+        self.min_alphabetic = min_alphabetic
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: c.isalpha(), self.min_alphabetic):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_alphabetic)d letter.',
+                'Your password must contain at least %(min_alphabetic)d letters.',
+                self.min_alphabetic
+            ),
+            code='too_few_alphabetic_char',
+            params={'min_alphabetic': self.min_alphabetic},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            'Your password must contain at least %(min_alphabetic)d letter.',
+            'Your password must contain at least %(min_alphabetic)d letters.',
+            self.min_alphabetic
+        ) % {'min_alphabetic': self.min_alphabetic}
+
+    def get_instruction_text(self):
+        if self.min_alphabetic > 0:
+            return ungettext(
+                '%(num)d letter',
+                '%(num)d letters',
+                self.min_alphabetic
+            ) % {'num': self.min_alphabetic}
+        else:
+            return ''
 
-    if not reqs:
-        return ungettext('Your password must contain at least {num} character.',
-                         'Your password must contain at least {num} characters.',
-                         min_length).format(num=min_length)
-    else:
-        return ungettext('Your password must contain at least {num} character, including {requirements}.',
-                         'Your password must contain at least {num} characters, including {requirements}.',
-                         min_length).format(num=min_length, requirements=' & '.join(reqs))
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_alphabetic', self.min_alphabetic
 
 
-def validate_password(password, user=None, username=None, password_reset=True):
+class NumericValidator(object):
     """
-    Checks user-provided password against our current site policy.
-
-    Raises a ValidationError or SecurityPolicyError depending on the type of error.
+    Validate whether the password contains at least min_numeric numbers.
 
-    Arguments:
-        password: The user-provided password as a string
-        user: A User model object, if available. Required to check against security policy.
-        username: The user-provided username, if available. Taken from 'user' if not provided.
-        password_reset: Whether to run validators that only make sense in a password reset
-         context (like PasswordHistory).
+    Parameters:
+        min_numeric (int): the minimum number of numeric characters to require
+            in the password. Must be >= 0.
     """
-    if not isinstance(password, text_type):
-        try:
-            password = text_type(password, encoding='utf8')  # some checks rely on unicode semantics (e.g. length)
-        except UnicodeDecodeError:
-            raise ValidationError(_('Invalid password.'))  # no reason to get into weeds
+    def __init__(self, min_numeric=0):
+        self.min_numeric = min_numeric
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: c.isnumeric(), self.min_numeric):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_numeric)d number.',
+                'Your password must contain at least %(min_numeric)d numbers.',
+                self.min_numeric
+            ),
+            code='too_few_numeric_char',
+            params={'min_numeric': self.min_numeric},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            "Your password must contain at least %(min_numeric)d number.",
+            "Your password must contain at least %(min_numeric)d numbers.",
+            self.min_numeric
+        ) % {'min_numeric': self.min_numeric}
+
+    def get_instruction_text(self):
+        if self.min_numeric > 0:
+            return ungettext(
+                '%(num)d number',
+                '%(num)d numbers',
+                self.min_numeric
+            ) % {'num': self.min_numeric}
+        else:
+            return ''
 
-    username = username or (user and user.username)
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_numeric', self.min_numeric
 
-    if user and password_reset:
-        _validate_password_security(password, user)
 
-    _validate_password_dictionary(password)
-    _validate_password_against_username(password, username)
+class UppercaseValidator(object):
+    """
+    Validate whether the password contains at least min_upper uppercase letters.
 
-    # Some messages are composable, so we'll add them together here
-    errors = [_validate_password_length(password)]
-    errors += _validate_password_complexity(password)
-    errors = filter(None, errors)
+    Parameters:
+        min_upper (int): the minimum number of uppercase characters to require
+            in the password. Must be >= 0.
+    """
+    def __init__(self, min_upper=0):
+        self.min_upper = min_upper
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: c.isupper(), self.min_upper):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_upper)d uppercase letter.',
+                'Your password must contain at least %(min_upper)d uppercase letters.',
+                self.min_upper
+            ),
+            code='too_few_uppercase_char',
+            params={'min_upper': self.min_upper},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            "Your password must contain at least %(min_upper)d uppercase letter.",
+            "Your password must contain at least %(min_upper)d uppercase letters.",
+            self.min_upper
+        ) % {'min_upper': self.min_upper}
+
+    def get_instruction_text(self):
+        if self.min_upper > 0:
+            return ungettext(
+                '%(num)d uppercase letter',
+                '%(num)d uppercase letters',
+                self.min_upper
+            ) % {'num': self.min_upper}
+        else:
+            return ''
 
-    if errors:
-        msg = _('Enter a password with at least {requirements}.').format(requirements=' & '.join(errors))
-        raise ValidationError(msg)
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_upper', self.min_upper
 
 
-def _validate_password_security(password, user):
+class LowercaseValidator(object):
     """
-    Check password reuse and similar operational security policy considerations.
+    Validate whether the password contains at least min_lower lowercase letters.
+
+    Parameters:
+        min_lower (int): the minimum number of lowercase characters to require
+            in the password. Must be >= 0.
     """
-    # Check reuse
-    if not PasswordHistory.is_allowable_password_reuse(user, password):
-        if user.is_staff:
-            num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STAFF_PASSWORDS_BEFORE_REUSE']
+    def __init__(self, min_lower=0):
+        self.min_lower = min_lower
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: c.islower(), self.min_lower):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_lower)d lowercase letter.',
+                'Your password must contain at least %(min_lower)d lowercase letters.',
+                self.min_lower
+            ),
+            code='too_few_lowercase_char',
+            params={'min_lower': self.min_lower},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            "Your password must contain at least %(min_lower)d lowercase letter.",
+            "Your password must contain at least %(min_lower)d lowercase letters.",
+            self.min_lower
+        ) % {'min_lower': self.min_lower}
+
+    def get_instruction_text(self):
+        if self.min_lower > 0:
+            return ungettext(
+                '%(num)d lowercase letter',
+                '%(num)d lowercase letters',
+                self.min_lower
+            ) % {'num': self.min_lower}
         else:
-            num_distinct = settings.ADVANCED_SECURITY_CONFIG['MIN_DIFFERENT_STUDENT_PASSWORDS_BEFORE_REUSE']
-        raise SecurityPolicyError(ungettext(
-            "You are re-using a password that you have used recently. "
-            "You must have {num} distinct password before reusing a previous password.",
-            "You are re-using a password that you have used recently. "
-            "You must have {num} distinct passwords before reusing a previous password.",
-            num_distinct
-        ).format(num=num_distinct))
-
-    # Check reset frequency
-    if PasswordHistory.is_password_reset_too_soon(user):
-        num_days = settings.ADVANCED_SECURITY_CONFIG['MIN_TIME_IN_DAYS_BETWEEN_ALLOWED_RESETS']
-        raise SecurityPolicyError(ungettext(
-            "You are resetting passwords too frequently. Due to security policies, "
-            "{num} day must elapse between password resets.",
-            "You are resetting passwords too frequently. Due to security policies, "
-            "{num} days must elapse between password resets.",
-            num_days
-        ).format(num=num_days))
-
-
-def _validate_password_length(value):
-    """
-    Validator that enforces minimum length of a password
-    """
-    min_length = password_min_length()
-    max_length = password_max_length()
+            return ''
 
-    if min_length and len(value) < min_length:
-        # This is an error that can be composed with other requirements, so just return a fragment
-        # Translators: This appears in a list of password requirements
-        return ungettext(
-            "{num} character",
-            "{num} characters",
-            min_length
-        ).format(num=min_length)
-    elif max_length and len(value) > max_length:
-        raise ValidationError(ungettext(
-            "Enter a password with at most {num} character.",
-            "Enter a password with at most {num} characters.",
-            max_length
-        ).format(num=max_length))
-
-
-def _validate_password_complexity(value):
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_lower', self.min_lower
+
+
+class PunctuationValidator(object):
     """
-    Validator that enforces minimum complexity
+    Validate whether the password contains at least min_punctuation punctuation characters
+    as defined by unicode categories.
+
+    Parameters:
+        min_punctuation (int): the minimum number of punctuation characters to require
+            in the password. Must be >= 0.
     """
-    complexities = password_complexity()
-    if not complexities:
-        return []
-
-    # Sets are here intentionally
-    uppercase, lowercase, digits, non_ascii, punctuation = set(), set(), set(), set(), set()
-    alphabetic, numeric = [], []
-
-    for character in value:
-        if character.isupper():
-            uppercase.add(character)
-        elif character.islower():
-            lowercase.add(character)
-        elif character.isdigit():
-            digits.add(character)
-        elif character in string.punctuation:
-            punctuation.add(character)
+    def __init__(self, min_punctuation=0):
+        self.min_punctuation = min_punctuation
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: 'P' in unicodedata.category(c), self.min_punctuation):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_punctuation)d punctuation character.',
+                'Your password must contain at least %(min_punctuation)d punctuation characters.',
+                self.min_punctuation
+            ),
+            code='too_few_punctuation_characters',
+            params={'min_punctuation': self.min_punctuation},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            "Your password must contain at least %(min_punctuation)d punctuation character.",
+            "Your password must contain at least %(min_punctuation)d punctuation characters.",
+            self.min_punctuation
+        ) % {'min_punctuation': self.min_punctuation}
+
+    def get_instruction_text(self):
+        if self.min_punctuation > 0:
+            return ungettext(
+                '%(num)d punctuation character',
+                '%(num)d punctuation characters',
+                self.min_punctuation
+            ) % {'num': self.min_punctuation}
         else:
-            non_ascii.add(character)
-
-        if character.isalpha():
-            alphabetic.append(character)
-        if 'N' in unicodedata.category(character):  # Check to see if the unicode category contains a 'N'umber
-            numeric.append(character)
-
-    words = set(value.split())
-
-    errors = []
-    if len(uppercase) < complexities.get("UPPER", 0):
-        errors.append('UPPER')
-    if len(lowercase) < complexities.get("LOWER", 0):
-        errors.append('LOWER')
-    if len(digits) < complexities.get("DIGITS", 0):
-        errors.append('DIGITS')
-    if len(punctuation) < complexities.get("PUNCTUATION", 0):
-        errors.append('PUNCTUATION')
-    if len(non_ascii) < complexities.get("NON ASCII", 0):
-        errors.append('NON ASCII')
-    if len(words) < complexities.get("WORDS", 0):
-        errors.append('WORDS')
-    if len(numeric) < complexities.get("NUMERIC", 0):
-        errors.append('NUMERIC')
-    if len(alphabetic) < complexities.get("ALPHABETIC", 0):
-        errors.append('ALPHABETIC')
-
-    if errors:
-        return _password_complexity_descriptions(errors)
-    else:
-        return []
-
+            return ''
 
-def _validate_password_against_username(password, username):
-    if not username:
-        return
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_punctuation', self.min_punctuation
 
-    if password == username:
-        # Translators: This message is shown to users who enter a password matching
-        # the username they enter(ed).
-        raise ValidationError(_(u"Password cannot be the same as the username."))
 
-
-def _validate_password_dictionary(value):
+class SymbolValidator(object):
     """
-    Insures that the password is not too similar to a defined set of dictionary words
+    Validate whether the password contains at least min_symbol symbols as defined by unicode categories.
+
+    Parameters:
+        min_symbol (int): the minimum number of symbols to require
+            in the password. Must be >= 0.
     """
-    if not settings.FEATURES.get('ENFORCE_PASSWORD_POLICY', False):
-        return
-
-    password_max_edit_distance = getattr(settings, "PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD", None)
-    password_dictionary = getattr(settings, "PASSWORD_DICTIONARY", None)
-
-    if password_max_edit_distance and password_dictionary:
-        for word in password_dictionary:
-            edit_distance = distance(value, text_type(word))
-            if edit_distance <= password_max_edit_distance:
-                raise ValidationError(_("Password is too similar to a dictionary word."),
-                                      code="dictionary_word")
+    def __init__(self, min_symbol=0):
+        self.min_symbol = min_symbol
+
+    def validate(self, password, user=None):
+        if _validate_condition(password, lambda c: 'S' in unicodedata.category(c), self.min_symbol):
+            return
+        raise ValidationError(
+            ungettext(
+                'Your password must contain at least %(min_symbol)d symbol.',
+                'Your password must contain at least %(min_symbol)d symbols.',
+                self.min_symbol
+            ),
+            code='too_few_symbols',
+            params={'min_symbol': self.min_symbol},
+        )
+
+    def get_help_text(self):
+        return ungettext(
+            "Your password must contain at least %(min_symbol)d symbol.",
+            "Your password must contain at least %(min_symbol)d symbols.",
+            self.min_symbol
+        ) % {'min_symbol': self.min_symbol}
+
+    def get_instruction_text(self):
+        if self.min_symbol > 0:
+            return ungettext(
+                '%(num)d symbol',
+                '%(num)d symbols',
+                self.min_symbol
+            ) % {'num': self.min_symbol}
+        else:
+            return ''
+
+    def get_restriction(self):
+        """
+        Returns a key, value pair for the restrictions related to the Validator
+        """
+        return 'min_symbol', self.min_symbol
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index d6ae2d06af6c502950afc340c754fe3fb92b88fe..4f56c9930d82f4a17ece4cfc34a03a74cfe6b0d9 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -652,11 +652,7 @@ MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_AL
 MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS", 15 * 60)
 
 #### PASSWORD POLICY SETTINGS #####
-PASSWORD_MIN_LENGTH = ENV_TOKENS.get("PASSWORD_MIN_LENGTH")
-PASSWORD_MAX_LENGTH = ENV_TOKENS.get("PASSWORD_MAX_LENGTH")
-PASSWORD_COMPLEXITY = ENV_TOKENS.get("PASSWORD_COMPLEXITY", {})
-PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD = ENV_TOKENS.get("PASSWORD_DICTIONARY_EDIT_DISTANCE_THRESHOLD")
-PASSWORD_DICTIONARY = ENV_TOKENS.get("PASSWORD_DICTIONARY", [])
+AUTH_PASSWORD_VALIDATORS = ENV_TOKENS.get("AUTH_PASSWORD_VALIDATORS", [])
 
 ### INACTIVITY SETTINGS ####
 SESSION_INACTIVITY_TIMEOUT_IN_SECONDS = AUTH_TOKENS.get("SESSION_INACTIVITY_TIMEOUT_IN_SECONDS")
diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py
index 70594981c37d179c958d4a1b55773595b866c381..1f811dec70d11ea1ce20b2468c9d4aadc4c5e118 100644
--- a/lms/envs/devstack.py
+++ b/lms/envs/devstack.py
@@ -142,9 +142,6 @@ FEATURES['ENABLE_MAX_FAILED_LOGIN_ATTEMPTS'] = False
 FEATURES['SQUELCH_PII_IN_LOGS'] = False
 FEATURES['PREVENT_CONCURRENT_LOGINS'] = False
 FEATURES['ADVANCED_SECURITY'] = False
-PASSWORD_MIN_LENGTH = None
-PASSWORD_COMPLEXITY = {}
-
 
 ########################### Milestones #################################
 FEATURES['MILESTONES_APP'] = True
diff --git a/openedx/core/djangoapps/password_policy/compliance.py b/openedx/core/djangoapps/password_policy/compliance.py
index 2c20364f11b8942930e634325e29f1dd6c2ba936..53326b9957c1d1434a10fa7d38561e40f4741629 100644
--- a/openedx/core/djangoapps/password_policy/compliance.py
+++ b/openedx/core/djangoapps/password_policy/compliance.py
@@ -8,7 +8,7 @@ from django.conf import settings
 from django.utils.translation import ugettext as _
 
 from util.date_utils import DEFAULT_SHORT_DATE_FORMAT, strftime_localized
-from util.password_policy_validators import validate_password
+from util.password_policy_validators import edX_validate_password
 
 
 class NonCompliantPasswordException(Exception):
@@ -104,7 +104,7 @@ def _check_user_compliance(user, password):
     Returns a boolean indicating whether or not the user is compliant with password policy rules.
     """
     try:
-        validate_password(password, user=user, password_reset=False)
+        edX_validate_password(password, user=user)
         return True
     except Exception:  # pylint: disable=broad-except
         # If anything goes wrong, we should assume the password is not compliant but we don't necessarily
diff --git a/openedx/core/djangoapps/user_api/accounts/api.py b/openedx/core/djangoapps/user_api/accounts/api.py
index 50ec3920ff9dc42b899f18099ce43ec56c360a5b..25b62aabb3b6c0828d220532314adb9f96e236c1 100644
--- a/openedx/core/djangoapps/user_api/accounts/api.py
+++ b/openedx/core/djangoapps/user_api/accounts/api.py
@@ -18,7 +18,7 @@ from student.models import User, UserProfile, Registration, email_exists_or_reti
 from student import forms as student_forms
 from student import views as student_views
 from util.model_utils import emit_setting_changed_event
-from util.password_policy_validators import validate_password
+from util.password_policy_validators import edX_validate_password
 
 from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
 from openedx.core.djangoapps.user_api import errors, accounts, forms, helpers
@@ -327,7 +327,7 @@ def create_account(username, password, email):
     # Validate the username, password, and email
     # This will raise an exception if any of these are not in a valid format.
     _validate_username(username)
-    _validate_password(password, username)
+    _validate_password(password, username, email)
     _validate_email(email)
 
     # Create the user account, setting them to "inactive" until they activate their account.
@@ -494,17 +494,17 @@ def get_confirm_email_validation_error(confirm_email, email):
     return _validate(_validate_confirm_email, errors.AccountEmailInvalid, confirm_email, email)
 
 
-def get_password_validation_error(password, username=None):
+def get_password_validation_error(password, username=None, email=None):
     """Get the built-in validation error message for when
     the password is invalid in some way.
 
     :param password: The proposed password (unicode).
     :param username: The username associated with the user's account (unicode).
-    :param default: The message to default to in case of no error.
+    :param email: The email associated with the user's account (unicode).
     :return: Validation error message.
 
     """
-    return _validate(_validate_password, errors.AccountPasswordInvalid, password, username)
+    return _validate(_validate_password, errors.AccountPasswordInvalid, password, username, email)
 
 
 def get_country_validation_error(country):
@@ -643,15 +643,17 @@ def _validate_confirm_email(confirm_email, email):
         raise errors.AccountEmailInvalid(accounts.REQUIRED_FIELD_CONFIRM_EMAIL_MSG)
 
 
-def _validate_password(password, username=None):
+def _validate_password(password, username=None, email=None):
     """Validate the format of the user's password.
 
     Passwords cannot be the same as the username of the account,
-    so we take `username` as an argument.
+    so we create a temp_user using the username and email to test the password against.
+    This user is never saved.
 
     Arguments:
         password (unicode): The proposed password.
         username (unicode): The username associated with the user's account.
+        email (unicode): The email associated with the user's account.
 
     Returns:
         None
@@ -662,12 +664,12 @@ def _validate_password(password, username=None):
     """
     try:
         _validate_type(password, basestring, accounts.PASSWORD_BAD_TYPE_MSG)
-
-        validate_password(password, username=username)
+        temp_user = User(username=username, email=email) if username else None
+        edX_validate_password(password, user=temp_user)
     except errors.AccountDataBadType as invalid_password_err:
         raise errors.AccountPasswordInvalid(text_type(invalid_password_err))
     except ValidationError as validation_err:
-        raise errors.AccountPasswordInvalid(validation_err.message)
+        raise errors.AccountPasswordInvalid(' '.join(validation_err.messages))
 
 
 def _validate_country(country):
diff --git a/openedx/core/djangoapps/user_api/api.py b/openedx/core/djangoapps/user_api/api.py
index 4a321347b09d49473d1a6d31e3a0a6bc344f8e11..3a349409d58e76f7efe9fd04d880ac398c38044c 100644
--- a/openedx/core/djangoapps/user_api/api.py
+++ b/openedx/core/djangoapps/user_api/api.py
@@ -18,7 +18,7 @@ from openedx.features.enterprise_support.api import enterprise_customer_for_requ
 from student.forms import get_registration_extension_form
 from student.models import UserProfile
 from util.password_policy_validators import (
-    password_complexity, password_instructions, password_max_length, password_min_length
+    password_validators_instruction_texts, password_validators_restrictions
 )
 
 
@@ -118,9 +118,10 @@ def get_login_session_form(request):
         "password",
         label=password_label,
         field_type="password",
-        restrictions={
-            "max_length": password_max_length(),
-        }
+        # The following restriction contains the assumption that the max password length will never exceed 5000
+        # characters. The point of this restriction on the login page is to prevent any sort of attacks
+        # involving sending massive passwords.
+        restrictions={'max_length': 5000}
     )
 
     form_desc.add_field(
@@ -419,22 +420,12 @@ class RegistrationFormFactory(object):
         # meant to hold the user's password.
         password_label = _(u"Password")
 
-        restrictions = {
-            "min_length": password_min_length(),
-            "max_length": password_max_length(),
-        }
-
-        complexities = password_complexity()
-        for key, value in complexities.iteritems():
-            api_key = key.lower().replace(' ', '_')
-            restrictions[api_key] = value
-
         form_desc.add_field(
             "password",
             label=password_label,
             field_type="password",
-            instructions=password_instructions(),
-            restrictions=restrictions,
+            instructions=password_validators_instruction_texts(),
+            restrictions=password_validators_restrictions(),
             required=required
         )
 
diff --git a/openedx/core/djangoapps/user_api/helpers.py b/openedx/core/djangoapps/user_api/helpers.py
index 66f0a9dfc90a5c253690e5b52b6dd43a6b53ac9f..60784e66dbca168d66895d844ae974d3d73ac266 100644
--- a/openedx/core/djangoapps/user_api/helpers.py
+++ b/openedx/core/djangoapps/user_api/helpers.py
@@ -125,8 +125,8 @@ class FormDescription(object):
 
     ALLOWED_RESTRICTIONS = {
         "text": ["min_length", "max_length"],
-        "password": ["min_length", "max_length", "upper", "lower", "digits", "punctuation", "non_ascii", "words",
-                     "numeric", "alphabetic"],
+        "password": ["min_length", "max_length", "min_upper", "min_lower",
+                     "min_punctuation", "min_symbol", "min_numeric", "min_alphabetic"],
         "email": ["min_length", "max_length", "readonly"],
     }
 
diff --git a/openedx/core/djangoapps/user_api/validation/views.py b/openedx/core/djangoapps/user_api/validation/views.py
index e5a5a41fedddba3d391cad142318db73772182f7..16812159a186a131b47db7d07c5b32d186bdf5b3 100644
--- a/openedx/core/djangoapps/user_api/validation/views.py
+++ b/openedx/core/djangoapps/user_api/validation/views.py
@@ -144,14 +144,15 @@ class RegistrationValidationView(APIView):
         return invalid_email_error or email_exists_error
 
     def confirm_email_handler(self, request):
-        email = request.data.get('email', None)
+        email = request.data.get('email')
         confirm_email = request.data.get('confirm_email')
         return get_confirm_email_validation_error(confirm_email, email)
 
     def password_handler(self, request):
-        username = request.data.get('username', None)
+        username = request.data.get('username')
+        email = request.data.get('email')
         password = request.data.get('password')
-        return get_password_validation_error(password, username)
+        return get_password_validation_error(password, username, email)
 
     def country_handler(self, request):
         country = request.data.get('country')