Django 密码与安全
密码是一种用来混淆的技术(把用公开的、标准的信息编码表示的信息通过一种变换手段,将其变为除通信
双方以外其他人所不能读懂的信息编码)。就是将正常的(可识别的)信息转变为无法识别的信息。
但这种无法识别的信息部分是破解(密码泄漏)。
密码的设计需遵守 柯克霍夫原则
:
即使密码系统的任何细节已为人悉知,只要密匙(key,又称密钥或秘钥)未泄漏,它也应是安全的。
对称加密
对称加密算法也叫共享密钥加密算法、单密钥加密算法。采用单密钥的加密方法,同一个密钥可以同时用
作信息的加密和解密,即解密算法为加密算法的逆算法。
AES(Advanced Encryption Standard)是新一代的加密算法标准,速度快,安全级别更高
非对称加密算法
在非对称加密算法中通过公钥加密的数据,只能通过私钥解开。通过私钥加密的数据,只能通过公钥解开。其主要局限在于,这种加密形式的速度相对较低,通常仅在关键时刻才使用该算法。
Hash算法
又称散列算法,用户可以通过Hash算法对目标信息生成一段特定长度的唯一的Hash值,却不
能通过这个Hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。
它是一种单向算法
常见使用场景
密码安全的最佳策略
- 理解什么是密码策略,鼓励用户设置可靠,安全的密码,并且正确地保存。
- 强密码策略的制定,提高系统密码安全性。
- 表单校验、重试机制、限制机制、异常登陆等。
- 定义合理的帐户锁定(Account Lockout)策略。
- 理解什么是密码策略,知晓其可能存在的安全漏洞,密码系统升级。
密码安全的最佳实践
- 强密码策略的制定。
- 数据网络传输时重要的隐私数据做好对应的加密,如用户登陆手机、密码等。
- 根据强密码策略建立好认证表单,进行重试机制、限制机制等前置校验,过于简单的表单大大降低系统安全性,同时会增加RPC请求、sql查询次数。
- 不要写一个view就写一个表单,尽量共用一套校验机制。
- 除了用户密码,也要关注其它隐私数据,思考数据泄漏后果,考虑库中是否需加密存储。
- 留意密码安全报道,知晓系统风险,及时做好对应处理。
密码组件
让我们先看下整个流程(如:django默认的登陆流程):
- 当用户发起一个登陆请求时,会首先进入中间件,执行重要的 (SessionMiddleware、AuthenticationMiddleware)
- 然后进入视图进行表单 AuthenticationForm 校验(用户名、密码长度等),执行authenticate方法。
- 表单校验通过的username,查询数据库,通过Hasher(加密算法)进行密码对比。
- 密码校验成功写session、cookie,跳转到落地页。
- 如果是登陆状态进入到登陆页面,检查到用户时登陆状态,直接跳转到落地页。
Django 如何存储密码?
当创建完一个用户,User 对象的 password 属性会是如下这种格式:
<algorithm>$<iterations>$<salt>$<hash>
如:pbkdf2_sha256$100000$c0rh7K9EeM2D$p5Mst+hdAK8dnlHtKrAgqyrLAJDp5lEMCOpOW4dKROg=
含义分别是:哈希算法 $ 算法迭代次数(在哈希上运行的次数)$ 随机 Salt $ 密码哈希值
Django 如何设置密码?
*Django使用 PASSWORD_HASHERS
设置来选择算法,PASSWORD_HASHERS
是settings配置文件中的一个配置项,这是一个 Django 支持的*哈希算法类列表 ,
第一个条目将被用来存储密码。如果你想使用不同算法,你需要修改 PASSWORD_HASHERS ,
在列表中首选列出你的算法
如:以下是Django默认的加密算法(注:各版本可能不一致)
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',
]
为什么是第一个作为加密算法呢? 如下是获取加密算法函数,可以看到默认就是获取哈希算法类列表第一个作为默认算法。
def get_hasher(algorithm='default'):
""" Return an instance of a loaded password hasher. If algorithm is 'default', return the default hasher. """
if hasattr(algorithm, 'algorithm'):
return algorithm
elif algorithm == 'default':
return get_hashers()[0]
else:
hashers = get_hashers_by_algorithm()
return hashers[algorithm]
DEFAULT_ALGORITHM = "sha1" # Django 1.3 默认算法
def get_hasher(algorithm='default'):
if hasattr(algorithm, 'algorithm'):
return algorithm
if algorithm == 'default':
algorithm = DEFAULT_ALGORITHM
hashers = get_hashers_by_algorithm()
return hashers[algorithm]
注:Django 1.3版本及之前使用sha1算法加密用户密码, 存储格式为(
<algorithm>$<salt>$<hash>
):
1.4及以后版本默认选择settings.PASSWORD_HASHERS[0]
作为加密用户密码的算法(默认为PBKDF2PasswordHasher(pbkdf2_sha256)), NIST (美国国家标准与技术研究院 ) 推荐的机制。
存储格式为(<algorithm>$<iterations>$<salt>$<hash>
):
什么是PBKDF2?
PBKDF2算法通过多次hash来对密码进行加密。原理是通过password和salt进行hash,然后将结果作为salt在
与password进行hash,多次重复此过程,生成最终的密文。盐值的添加也会增加“彩虹表”攻击的难度,
此过程可能达到上千次,逆向破解的难度太大,破解一个密码的时间可能需要几百年,所以PBKDF2算法是安全的.
Hasher该如何选择?
Django提供了很多的PASSWORD_HASHERS,Django自带的Hasher之间的对比如下:
但并非所有都是推荐的,目前最为推荐的是默认的PBKDF2PasswordHasher,因为它足够安全。
同时对于一些易破解的加密算法,如:md5,sha1(因为可以通过 预计算哈希链集、彩虹表 破解)
所以对于一些安全程度较低的加密算法,我们应该去做对应的升级。
Hasher 有哪些方法?
hasher提供的方法如下:
-
密码加密 encode
-
密码校验 verify
-
更改迭代次数(iterations)的判断
Hasher配置iterations次数与数据库密码的中的iterations次数做比较
-
额外进行hash次数
make_password 介绍
通过此应用的格式创建一个哈希密码。它需要一个必需的参数:纯文本密码(字符串或字节)。如果密码参数是 None ,将返回一个不可用的密码(永远不会被 check_password() 通过的密码)。
def make_password(password, salt=None, hasher='default'):
if password is None:
return UNUSABLE_PASSWORD_PREFIX + get_random_string(UNUSABLE_PASSWORD_SUFFIX_LENGTH)
hasher = get_hasher(hasher)
if not salt:
salt = hasher.salt()
return hasher.encode(password, salt)
check_password 介绍
如果你想通过对比纯文本密码和数据库中的哈希密码来验证用户,可以使用 check_password() 快捷函数。它需要2个参数:要检查的纯文本密码和要检查的数据库中用户密码字段的值。如果匹配成功,返回 True ,否则返回 False 。
check_password 做了哪些事?
1. 获取所需的hasher
2. 获取老密码的hasher
3. 是否需要强化算法,增加复杂度
4. 加密算法是否改变判断
5. 密码校验verify
7. 额外进行hash次数
(应用:比如想提高迭代次数提高密码安全性,
更改了Hasher的iterations数量,当检测到与数据库中的数量不一致,
则会额外进行iterations - int(数据库iterations) 次迭代)
6. 更改老的加密算法
(应用:用户登陆时,密码自动升级,只需调整PASSWORD_HASHERS顺序,如:sha1 -> pbkdf2_sha1)
def check_password(password, encoded, setter=None, preferred='default'):
""" Return a boolean of whether the raw password matches the three part encoded digest. If setter is specified, it'll be called when you need to regenerate the password. """
if password is None or not is_password_usable(encoded):
return False
# 1. 获取当前的加密算法
preferred = get_hasher(preferred)
try:
# 2. 获取老的的加密算法(数据库中存储的)
hasher = identify_hasher(encoded)
except ValueError:
return False
# 3. 判断是否更改了加密算法
hasher_changed = hasher.algorithm != preferred.algorithm
# 4. 判断是否更改了迭代次数
must_update = hasher_changed or preferred.must_update(encoded)
# 5. 密码校验
is_correct = hasher.verify(password, encoded)
if not is_correct and not hasher_changed and must_update:
# 6. 额外对加密的密码 进行xxx次hash
hasher.harden_runtime(password, encoded)
if setter and is_correct and must_update:
# 7. 更改老的加密算法为最新的配置
setter(password)
return is_correct
密码升级
用户登陆时,密码自动升级
只需调整PASSWORD_HASHERS顺序,如:sha1 -> pbkdf2_sha1,当用户使用之前的密码校验通过后使用最新的加密算法设置密码
用户为登陆时,密码升级
如果数据库拥有老旧低效的加密算法,比如 MD5 或 SHA1,那么你也许希望自己升级哈希而不是等待用户登录后升级(如果一个用户不再登录站点,数据库一直是老密码,密码就不会升级了)
实现原理:马甲密码进行校验,最新加密算法进行密码设置
具体步骤:
1. 取出数据库中用户密码,把此时加密的密码再进行一次加密,生成pbkdf2_wrapped_sha1的马甲密码, 此时数据库中的马甲密码格式为:pbkdf2_wrapped_sha256$216000$4YfO8YTUgTO9$xxxxxxx
2. PASSWORD_HASHERS 中添加马甲密码类 PBKDF2WrappedSHA1PasswordHasher
3. 当用户登陆时,PBKDF2WrappedSHA1PasswordHasher的encode方法,把密码也加密成马甲密码
4. 然后执行密码校验,两次马甲密码一致,通过密码校验
5. 触发登陆更改密码更改老的加密算法条件。密码改成:pbkdf2_sha256$216000$GS9f0OKOxxQE$xxxxxx
流程图如下所示:
参考以下代码:
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand
from django.contrib.auth.hashers import (
PBKDF2PasswordHasher, SHA1PasswordHasher,
)
class PBKDF2WrappedSHA1PasswordHasher(PBKDF2PasswordHasher):
algorithm = 'pbkdf2_wrapped_sha1'
def encode_sha1_hash(self, sha1_hash, salt, iterations=None):
return super().encode(sha1_hash, salt, iterations)
def encode(self, password, salt, iterations=None):
""" 用户 登陆时候的密码,要进行一次老的sha1 hash运算,然后进行转换成pbkdf2_sha256 hash密码 """
_, _, sha1_hash = SHA1PasswordHasher().encode(password, salt).split('$', 2)
return self.encode_sha1_hash(sha1_hash, salt, iterations)
class Command(BaseCommand):
def handle(self, *args, **options):
""" sha1密码升级 -> pbkdf2_sha1 """
User = get_user_model()
users = User.objects.filter(password__startswith='sha1$')
hasher = PBKDF2WrappedSHA1PasswordHasher()
for user in users:
algorithm, salt, sha1_hash = user.password.split('$', 2)
user.password = hasher.encode_sha1_hash(sha1_hash, salt)
user.save(update_fields=['password'])
Django 如何做密码校验?
内置的密码校验器
Django提供了可插入的密码验证。可以同时配置多个密码验证器。Django中包含一些验证器,也可非常简单的实现自定义验证器。
配置于在settings文件中的AUTH_PASSWORD_VALIDATORS。只用于 Django Admin、command指令创建、修改用户的密码
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
提供的功能分别为:
- CommonPasswordValidator:通过与定义的一些普通的密码校验。common-passwords.txt 定义了近2w个普通密码。
- NumericPasswordValidator:isdigit 进行数字判断
- MinimumLengthValidator:len 进行长度比较
- UserAttributeSimilarityValidator: 与需要对比的用户属性(如:username, email等)进行相似度检查, 使用的python的标准库 Difflib来做文本相似度检查。
注:密码校验,仅在form里才能使用,如果自定义接口做登陆,需要使用春雨封装好的密码校验Form
三方包 django-password-validators
- PasswordCharacterValidator
指定至少包含的数字、字母、大小写数量等,提高密码安全性
如:
{
'NAME': 'django_password_validators.password_character_requirements.password_validation.PasswordCharacterValidator',
'OPTIONS': {
'min_length_digit': 1,
'min_length_alpha': 1,
'min_length_special': 1,
'min_length_lower': 1,
'min_length_upper': 0,
'special_characters': "~!@#$%^&*()_+{}\":;'[]."
}
}
- UniquePasswordsValidator
验证用户是否曾经使用过密码, 不能设置成前面设置过的密码n次(n>=0)
自定义密码校验器
需实现以下方法:
1.AUTH_PASSWORD_VALIDATORS 添加校验器Name(导入路径), OPTIONS (__init__方法参数)
2.类实现validate、get_help_text 方法
3.可以添加 password_changed 方法,会在密码修改成功后调用
文章评论