一、源码分析
Django 发布的 1.4 版本中包含了一些安全方面的重要提升。其中一个是使用 PBKDF2 密码加密算法代替了 SHA1 。另外一个特性是你可以添加自己的密码加密方法。
Django 会使用你提供的第一个密码加密方法(在你的 setting.py 文件里要至少有一个方法)
PASSWORD_HASHERS = ['django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher','django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher','django.contrib.auth.hashers.BCryptPasswordHasher',
]
我们先一睹自带的PBKDF2PasswordHasher加密方式。
class BasePasswordHasher(object):"""Abstract base class for password hashersWhen creating your own hasher, you need to override algorithm,verify(), encode() and safe_summary().PasswordHasher objects are immutable."""algorithm = Nonelibrary = Nonedef _load_library(self):if self.library is not None:if isinstance(self.library, (tuple, list)):name, mod_path = self.libraryelse:name = mod_path = self.librarytry:module = importlib.import_module(mod_path)except ImportError:raise ValueError("Couldn't load %s password algorithm ""library" % name)return moduleraise ValueError("Hasher '%s' doesn't specify a library attribute" %self.__class__)def salt(self):"""Generates a cryptographically secure nonce salt in ascii"""return get_random_string()def verify(self, password, encoded):"""Checks if the given password is correct"""raise NotImplementedError()def encode(self, password, salt):"""Creates an encoded database valueThe result is normally formatted as "algorithm$salt$hash" andmust be fewer than 128 characters."""raise NotImplementedError()def safe_summary(self, encoded):"""Returns a summary of safe valuesThe result is a dictionary and will be used where the password fieldmust be displayed to construct a safe representation of the password."""raise NotImplementedError()class PBKDF2PasswordHasher(BasePasswordHasher):"""Secure password hashing using the PBKDF2 algorithm (recommended)Configured to use PBKDF2 + HMAC + SHA256.The result is a 64 byte binary string. Iterations may be changedsafely but you must rename the algorithm if you change SHA256."""algorithm = "pbkdf2_sha256"iterations = 36000digest = hashlib.sha256def encode(self, password, salt, iterations=None):assert password is not Noneassert salt and '$' not in saltif not iterations:iterations = self.iterationshash = pbkdf2(password, salt, iterations, digest=self.digest)hash = base64.b64encode(hash).decode('ascii').strip()return "%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)def verify(self, password, encoded):algorithm, iterations, salt, hash = encoded.split('$', 3)assert algorithm == self.algorithmencoded_2 = self.encode(password, salt, int(iterations))return constant_time_compare(encoded, encoded_2)def safe_summary(self, encoded):algorithm, iterations, salt, hash = encoded.split('$', 3)assert algorithm == self.algorithmreturn OrderedDict([(_('algorithm'), algorithm),(_('iterations'), iterations),(_('salt'), mask_hash(salt)),(_('hash'), mask_hash(hash)),])def must_update(self, encoded):algorithm, iterations, salt, hash = encoded.split('$', 3)return int(iterations) != self.iterationsdef harden_runtime(self, password, encoded):algorithm, iterations, salt, hash = encoded.split('$', 3)extra_iterations = self.iterations - int(iterations)if extra_iterations > 0:self.encode(password, salt, extra_iterations)
正如你看到那样,你必须继承自BasePasswordHasher,并且重写 verify() , encode() 以及 safe_summary() 方法。
Django 是使用 PBKDF 2算法与36,000次的迭代使得它不那么容易被暴力破解法轻易攻破。密码用下面的格式储存:
algorithm$number of iterations$salt$password hash”
例:pbkdf2_sha256$36000$Lx7auRCc8FUI$eG9lX66cKFTos9sEcihhiSCjI6uqbr9ZrO+Iq3H9xDU=
二、自定义密码加密方法
1、在settings.py中加入自定义的加密算法:
PASSWORD_HASHERS = ['myproject.hashers.MyMD5PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher','django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher','django.contrib.auth.hashers.Argon2PasswordHasher','django.contrib.auth.hashers.BCryptSHA256PasswordHasher','django.contrib.auth.hashers.BCryptPasswordHasher',
]
2、再来看MyMD5PasswordHasher,这个是我自定义的加密方式,就是基本的md5,而django的MD5PasswordHasher是加盐的:
from django.contrib.auth.hashers import BasePasswordHasher,MD5PasswordHasherfrom django.contrib.auth.hashers import mask_hashimport hashlibclass MyMD5PasswordHasher(MD5PasswordHasher):algorithm = "mymd5"def encode(self, password, salt):assert password is not Nonehash = hashlib.md5(password).hexdigest().upper()return hashdef verify(self, password, encoded):encoded_2 = self.encode(password, '')return encoded.upper() == encoded_2.upper()def safe_summary(self, encoded):return OrderedDict([(_('algorithm'), algorithm),(_('salt'), ''),(_('hash'), mask_hash(hash)),])
之后可以在数据库中看到,密码确实使用了自定义的加密方式。
3、修改认证方式
AUTHENTICATION_BACKENDS = ('framework.mybackend.MyBackend', #新加'django.contrib.auth.backends.ModelBackend','guardian.backends.ObjectPermissionBackend',
)
4、再来看自定义的认证方式
framework.mybackend.py:
import hashlibfrom pro import modelsfrom django.contrib.auth.backends import ModelBackendclass MyBackend(ModelBackend):def authenticate(self, username=None, password=None):try:user = models.M_User.objects.get(username=username)print userexcept Exception:print 'no user'return Noneif hashlib.md5(password).hexdigest().upper() == user.password:return userreturn Nonedef get_user(self, user_id):try:return models.M_User.objects.get(id=user_id)except Exception:return None
当然经过这些修改后最终的安全性比起django自带的降低很多,但是需求就是这样的,必须满足。