第 0021 题: 通常,登陆某个网站或者 APP,需要使用用户名和密码。密码是如何加密后存储起来的呢?请使用 Python 对密码加密。
阅读资料 用户密码的存储与 Python 示例
阅读资料 Hashing Strings with Python
阅读资料 Python’s safest method to store and retrieve passwords from a database
加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密)。加密技术的要点是加密算法,加密算法可以分为对称加密、不对称加密和不可逆加密三类算法。
按照安全性由低到高,有这样几种选择:
密码明文直接存储在系统中
这种方法下密码的安全性比系统本身还低,管理员能查看所有用户的密码明文。除非是做恶意网站故意套取用户密码,否则不要用这种方式
密码明文经过转换后再存储
与直接存储明文的方式没有本质区别,任何知道或破解出转换方法的人都可以逆转换得到密码明文
密码经过对称加密后再存储
密码明文的安全性等同于加密密钥本身的安全性。对称加密的密钥可同时用于加密与解密。一般它会直接出现在加密代码中,破解的可能性相当大。而且系统管理员很可能知道密钥,进而算出密码原文
密码经过非对称加密后再存储
密码的安全性等同于私钥的安全性。密码明文经过公钥加密。要还原密码明文,必须要相应的私钥才行。因此只要保证私钥的安全,密码明文就安全。私钥可以由某个受信任的人或机构来掌管,身份验证只需要用公钥就可以了
实际上,这也是 HTTPS/SSL 的理论基础。这里的关键是 私钥的安全 ,如果私钥泄露,那密码明文就危险了。
以上 4 种方法的共同特点是可以从存储的密码形式还原到密码明文。
所以密码最好是以不可还原明文的方式来保存。通常利用哈希算法的单向性来保证明文以 不可还原的有损方式 进行存储。
这类方法的各个具体操作方式按安全性由低到高依次为:
使用自己独创的哈希算法对密码进行哈希,存储哈希过的值
哈希算法复杂,独创对理论要求很高。一般独创的哈希算法肯定没有公开经过时间检验的算法质量高,天才另算
使用 MD5 或 SHA-1 哈希算法
MD5 和 SHA-1 已破解。虽不能还原明文,但很容易找到能生成相同哈希值的替代明文。而且这两个算法速度较快,暴力破解相对省时,建议不要使用它们。
使用更安全的 SHA-256 等成熟算法
更加复杂的算法增加了暴力破解的难度。但如果遇到简单密码,用彩虹字典的暴力破解法,很快就能得到密码原文
加入随机 salt 的哈希算法
密码原文(或经过 hash 后的值)和随机生成的 salt 字符串混淆,然后再进行 hash,最后把 hash 值和 salt 值一起存储。验证密码的时候只要用 salt 再与密码原文做一次相同步骤的运算,比较结果与存储的 hash 值就可以了。这样一来哪怕是简单的密码,在进过 salt 混淆后产生的也是很不常见的字符串,根本不会出现在彩虹字典中。salt 越长暴力破解的难度越大
所以加入随机salt的哈希算法是一个安全性比较好的加密算法,能够废掉彩虹表,加入随机salt,每一个用户哪怕密码一样密文值也不一样。下面使用python实现这种加密方法。
0021.密码加密.py
#coding: utf-8
import random
from hashlib import sha256
from hmac import HMAC
def set_password(raw_password, salt=None):
if salt is None:
salt = sha256(str(random.random())).hexdigest()[-8:]
if isinstance(raw_password, unicode):
raw_password = raw_password.encode('utf-8')
# password = sha256('%s%s' % (salt, raw_password)).hexdigest()
password = HMAC(salt, raw_password, sha256).hexdigest()
return ('%s$%s' % (salt, password))
def check_password(raw_password, enc_password):
salt = enc_password.split('$')[0]
return enc_password == set_password(raw_password, salt)
if __name__ == '__main__':
raw_password1 = 'hehe'
raw_password2 = 'test'
salt = '12345678'
enc_password1 = set_password(raw_password1)
enc_password2 = set_password(raw_password2, salt)
print 'enc_password1:%s' % enc_password1
print 'enc_password2:%s' % enc_password2
print check_password(raw_password1, enc_password1)
print check_password(raw_password2, enc_password1)